diff --git a/README.markdown b/README.markdown index 0342b6a39..ef3f3b278 100644 --- a/README.markdown +++ b/README.markdown @@ -462,6 +462,8 @@ Generated code will be placed in the Gradle build directory. The default behavior is `useMapType=false`, which makes it generate the code for protobuf `map>> 3) { + case 1: + message.paths.push(reader.string()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): FieldMask { + return { + paths: typeof (object) === "string" + ? object.split(",").filter(Boolean) + : Array.isArray(object?.paths) + ? object.paths.map(String) + : [], + }; + }, + + toJSON(message: FieldMask): string { + return message.paths.join(","); + }, + + fromPartial, I>>(object: I): FieldMask { + const message = createBaseFieldMask() as any; + message.paths = object.paths?.map((e) => e) || []; + return message; + }, + + wrap(paths: readonly string[]): FieldMask { + const result = createBaseFieldMask() as any; + + result.paths = paths; + + return result; + }, + + unwrap(message: any): string[] { + return message.paths; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; diff --git a/integration/use-readonly-types/google/protobuf/struct.ts b/integration/use-readonly-types/google/protobuf/struct.ts new file mode 100644 index 000000000..4d7127953 --- /dev/null +++ b/integration/use-readonly-types/google/protobuf/struct.ts @@ -0,0 +1,469 @@ +/* eslint-disable */ +import * as _m0 from "protobufjs/minimal"; + +export const protobufPackage = "google.protobuf"; + +/** + * `NullValue` is a singleton enumeration to represent the null value for the + * `Value` type union. + * + * The JSON representation for `NullValue` is JSON `null`. + */ +export enum NullValue { + /** NULL_VALUE - Null value. */ + NULL_VALUE = 0, + UNRECOGNIZED = -1, +} + +export function nullValueFromJSON(object: any): NullValue { + switch (object) { + case 0: + case "NULL_VALUE": + return NullValue.NULL_VALUE; + case -1: + case "UNRECOGNIZED": + default: + return NullValue.UNRECOGNIZED; + } +} + +export function nullValueToJSON(object: NullValue): string { + switch (object) { + case NullValue.NULL_VALUE: + return "NULL_VALUE"; + case NullValue.UNRECOGNIZED: + default: + return "UNRECOGNIZED"; + } +} + +/** + * `Struct` represents a structured data value, consisting of fields + * which map to dynamically typed values. In some languages, `Struct` + * might be supported by a native representation. For example, in + * scripting languages like JS a struct is represented as an + * object. The details of that representation are described together + * with the proto support for the language. + * + * The JSON representation for `Struct` is JSON object. + */ +export interface Struct { + /** Unordered map of dynamically typed values. */ + readonly fields: { [key: string]: any | undefined }; +} + +export interface Struct_FieldsEntry { + readonly key: string; + readonly value: any | undefined; +} + +/** + * `Value` represents a dynamically typed value which can be either + * null, a number, a string, a boolean, a recursive struct value, or a + * list of values. A producer of value is expected to set one of these + * variants. Absence of any variant indicates an error. + * + * The JSON representation for `Value` is JSON value. + */ +export interface Value { + /** Represents a null value. */ + readonly nullValue: + | NullValue + | undefined; + /** Represents a double value. */ + readonly numberValue: + | number + | undefined; + /** Represents a string value. */ + readonly stringValue: + | string + | undefined; + /** Represents a boolean value. */ + readonly boolValue: + | boolean + | undefined; + /** Represents a structured value. */ + readonly structValue: + | { readonly [key: string]: any } + | undefined; + /** Represents a repeated `Value`. */ + readonly listValue: ReadonlyArray | undefined; +} + +/** + * `ListValue` is a wrapper around a repeated field of values. + * + * The JSON representation for `ListValue` is JSON array. + */ +export interface ListValue { + /** Repeated field of dynamically typed values. */ + readonly values: readonly any[]; +} + +function createBaseStruct(): Struct { + return { fields: {} }; +} + +export const Struct = { + encode(message: Struct, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + Object.entries(message.fields).forEach(([key, value]) => { + if (value !== undefined) { + Struct_FieldsEntry.encode({ key: key as any, value }, writer.uint32(10).fork()).ldelim(); + } + }); + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Struct { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseStruct() as any; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + const entry1 = Struct_FieldsEntry.decode(reader, reader.uint32()); + if (entry1.value !== undefined) { + message.fields[entry1.key] = entry1.value; + } + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Struct { + return { + fields: isObject(object.fields) + ? Object.entries(object.fields).reduce<{ [key: string]: any | undefined }>((acc, [key, value]) => { + acc[key] = value as any | undefined; + return acc; + }, {}) + : {}, + }; + }, + + toJSON(message: Struct): unknown { + const obj: any = {}; + obj.fields = {}; + if (message.fields) { + Object.entries(message.fields).forEach(([k, v]) => { + obj.fields[k] = v; + }); + } + return obj; + }, + + fromPartial, I>>(object: I): Struct { + const message = createBaseStruct() as any; + message.fields = Object.entries(object.fields ?? {}).reduce<{ [key: string]: any | undefined }>( + (acc, [key, value]) => { + if (value !== undefined) { + acc[key] = value; + } + return acc; + }, + {}, + ); + return message; + }, + + wrap(object: { [key: string]: any } | undefined): Struct { + const struct = createBaseStruct(); + if (object !== undefined) { + Object.keys(object).forEach((key) => { + struct.fields[key] = object[key]; + }); + } + return struct; + }, + + unwrap(message: Struct): { [key: string]: any } { + const object: { [key: string]: any } = {}; + Object.keys(message.fields).forEach((key) => { + object[key] = message.fields[key]; + }); + return object; + }, +}; + +function createBaseStruct_FieldsEntry(): Struct_FieldsEntry { + return { key: "", value: undefined }; +} + +export const Struct_FieldsEntry = { + encode(message: Struct_FieldsEntry, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.key !== "") { + writer.uint32(10).string(message.key); + } + if (message.value !== undefined) { + Value.encode(Value.wrap(message.value), writer.uint32(18).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Struct_FieldsEntry { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseStruct_FieldsEntry() as any; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.key = reader.string(); + break; + case 2: + message.value = Value.unwrap(Value.decode(reader, reader.uint32())); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Struct_FieldsEntry { + return { key: isSet(object.key) ? String(object.key) : "", value: isSet(object?.value) ? object.value : undefined }; + }, + + toJSON(message: Struct_FieldsEntry): unknown { + const obj: any = {}; + message.key !== undefined && (obj.key = message.key); + message.value !== undefined && (obj.value = message.value); + return obj; + }, + + fromPartial, I>>(object: I): Struct_FieldsEntry { + const message = createBaseStruct_FieldsEntry() as any; + message.key = object.key ?? ""; + message.value = object.value ?? undefined; + return message; + }, +}; + +function createBaseValue(): Value { + return { + nullValue: undefined, + numberValue: undefined, + stringValue: undefined, + boolValue: undefined, + structValue: undefined, + listValue: undefined, + }; +} + +export const Value = { + encode(message: Value, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.nullValue !== undefined) { + writer.uint32(8).int32(message.nullValue); + } + if (message.numberValue !== undefined) { + writer.uint32(17).double(message.numberValue); + } + if (message.stringValue !== undefined) { + writer.uint32(26).string(message.stringValue); + } + if (message.boolValue !== undefined) { + writer.uint32(32).bool(message.boolValue); + } + if (message.structValue !== undefined) { + Struct.encode(Struct.wrap(message.structValue), writer.uint32(42).fork()).ldelim(); + } + if (message.listValue !== undefined) { + ListValue.encode(ListValue.wrap(message.listValue), writer.uint32(50).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Value { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseValue() as any; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.nullValue = reader.int32() as any; + break; + case 2: + message.numberValue = reader.double(); + break; + case 3: + message.stringValue = reader.string(); + break; + case 4: + message.boolValue = reader.bool(); + break; + case 5: + message.structValue = Struct.unwrap(Struct.decode(reader, reader.uint32())); + break; + case 6: + message.listValue = ListValue.unwrap(ListValue.decode(reader, reader.uint32())); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Value { + return { + nullValue: isSet(object.nullValue) ? nullValueFromJSON(object.nullValue) : undefined, + numberValue: isSet(object.numberValue) ? Number(object.numberValue) : undefined, + stringValue: isSet(object.stringValue) ? String(object.stringValue) : undefined, + boolValue: isSet(object.boolValue) ? Boolean(object.boolValue) : undefined, + structValue: isObject(object.structValue) ? object.structValue : undefined, + listValue: Array.isArray(object.listValue) ? [...object.listValue] : undefined, + }; + }, + + toJSON(message: Value): unknown { + const obj: any = {}; + message.nullValue !== undefined && + (obj.nullValue = message.nullValue !== undefined ? nullValueToJSON(message.nullValue) : undefined); + message.numberValue !== undefined && (obj.numberValue = message.numberValue); + message.stringValue !== undefined && (obj.stringValue = message.stringValue); + message.boolValue !== undefined && (obj.boolValue = message.boolValue); + message.structValue !== undefined && (obj.structValue = message.structValue); + message.listValue !== undefined && (obj.listValue = message.listValue); + return obj; + }, + + fromPartial, I>>(object: I): Value { + const message = createBaseValue() as any; + message.nullValue = object.nullValue ?? undefined; + message.numberValue = object.numberValue ?? undefined; + message.stringValue = object.stringValue ?? undefined; + message.boolValue = object.boolValue ?? undefined; + message.structValue = object.structValue ?? undefined; + message.listValue = object.listValue ?? undefined; + return message; + }, + + wrap(value: any): Value { + const result = createBaseValue() as any; + + if (value === null) { + result.nullValue = NullValue.NULL_VALUE; + } else if (typeof value === "boolean") { + result.boolValue = value; + } else if (typeof value === "number") { + result.numberValue = value; + } else if (typeof value === "string") { + result.stringValue = value; + } else if (Array.isArray(value)) { + result.listValue = value; + } else if (typeof value === "object") { + result.structValue = value; + } else if (typeof value !== "undefined") { + throw new Error("Unsupported any value type: " + typeof value); + } + + return result; + }, + + unwrap(message: Value): string | number | boolean | Object | null | Array | undefined { + if (message?.stringValue !== undefined) { + return message.stringValue; + } else if (message?.numberValue !== undefined) { + return message.numberValue; + } else if (message?.boolValue !== undefined) { + return message.boolValue; + } else if (message?.structValue !== undefined) { + return message.structValue; + } else if (message?.listValue !== undefined) { + return message.listValue; + } else if (message?.nullValue !== undefined) { + return null; + } + return undefined; + }, +}; + +function createBaseListValue(): ListValue { + return { values: [] }; +} + +export const ListValue = { + encode(message: ListValue, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + for (const v of message.values) { + Value.encode(Value.wrap(v!), writer.uint32(10).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): ListValue { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseListValue() as any; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.values.push(Value.unwrap(Value.decode(reader, reader.uint32()))); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): ListValue { + return { values: Array.isArray(object?.values) ? [...object.values] : [] }; + }, + + toJSON(message: ListValue): unknown { + const obj: any = {}; + if (message.values) { + obj.values = message.values.map((e) => e); + } else { + obj.values = []; + } + return obj; + }, + + fromPartial, I>>(object: I): ListValue { + const message = createBaseListValue() as any; + message.values = object.values?.map((e) => e) || []; + return message; + }, + + wrap(value: ReadonlyArray | undefined): ListValue { + const result = createBaseListValue() as any; + + result.values = value ?? []; + + return result; + }, + + unwrap(message: any): Array { + return message.values; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; + +function isObject(value: any): boolean { + return typeof value === "object" && value !== null; +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/integration/use-readonly-types/parameters.txt b/integration/use-readonly-types/parameters.txt new file mode 100644 index 000000000..9cea675ca --- /dev/null +++ b/integration/use-readonly-types/parameters.txt @@ -0,0 +1 @@ +useReadonlyTypes=true diff --git a/integration/use-readonly-types/use-readonly-types-test.ts b/integration/use-readonly-types/use-readonly-types-test.ts new file mode 100644 index 000000000..cee1ab067 --- /dev/null +++ b/integration/use-readonly-types/use-readonly-types-test.ts @@ -0,0 +1,57 @@ +import { Entity } from "./use-readonly-types"; + +describe("use-readonly-types", () => { + it("generates types correctly", () => { + const m: Entity = { + intVal: 42, + intArray: [42, 43], + stringVal: "hello", + stringArray: ["hello", "world"], + subEntity: { subVal: 42 }, + subEntityArray: [{ subVal: 42 }, { subVal: 43 }], + fieldMask: ["the", "mask"], + listValue: ["the", "list"], + structValue: { the: "struct" }, + }; + const jsonFromObject = Entity.toJSON(m); + const entityFromJSON = Entity.fromJSON(jsonFromObject); + expect(entityFromJSON).toEqual(m); + const encoded = Entity.encode(m).finish(); + const decoded = Entity.decode(encoded); + expect(decoded).toEqual({ + intVal: m.intVal, + intArray: m.intArray, + stringVal: m.stringVal, + stringArray: m.stringArray, + subEntity: m.subEntity, + subEntityArray: m.subEntityArray, + fieldMask: m.fieldMask, + listValue: m.listValue, + structValue: m.structValue, + }); + const jsonFromDecoded = Entity.toJSON(decoded); + expect(jsonFromDecoded).toEqual(jsonFromObject); + const partial = Entity.fromPartial({ + intVal: m.intVal, + intArray: m.intArray, + stringVal: m.stringVal, + stringArray: m.stringArray, + subEntity: m.subEntity, + subEntityArray: m.subEntityArray, + fieldMask: m.fieldMask, + listValue: m.listValue, + structValue: m.structValue, + }); + expect(partial).toEqual({ + intVal: m.intVal, + intArray: m.intArray, + stringVal: m.stringVal, + stringArray: m.stringArray, + subEntity: m.subEntity, + subEntityArray: m.subEntityArray, + fieldMask: m.fieldMask, + listValue: m.listValue, + structValue: m.structValue, + }); + }); +}); diff --git a/integration/use-readonly-types/use-readonly-types.bin b/integration/use-readonly-types/use-readonly-types.bin new file mode 100644 index 000000000..a37b0b21e Binary files /dev/null and b/integration/use-readonly-types/use-readonly-types.bin differ diff --git a/integration/use-readonly-types/use-readonly-types.proto b/integration/use-readonly-types/use-readonly-types.proto new file mode 100644 index 000000000..a3a34809c --- /dev/null +++ b/integration/use-readonly-types/use-readonly-types.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +import "google/protobuf/field_mask.proto"; +import "google/protobuf/struct.proto"; + +message Entity { + int32 intVal = 1; + string stringVal = 2; + repeated int32 intArray = 3; + repeated string stringArray = 4; + SubEntity subEntity = 5; + repeated SubEntity subEntityArray = 6; + optional int32 optionalIntVal = 7; + google.protobuf.FieldMask fieldMask = 8; + google.protobuf.ListValue listValue = 9; + google.protobuf.Struct structValue = 10; +} + +message SubEntity { + int32 subVal = 1; +} + +/* + +google.protobuf.FieldMask ⇆ string[] +google.protobuf.ListValue ⇆ any[] +google.protobuf.Struct ⇆ { [key: string]: any } +*/ \ No newline at end of file diff --git a/integration/use-readonly-types/use-readonly-types.ts b/integration/use-readonly-types/use-readonly-types.ts new file mode 100644 index 000000000..0a84a927d --- /dev/null +++ b/integration/use-readonly-types/use-readonly-types.ts @@ -0,0 +1,256 @@ +/* eslint-disable */ +import * as _m0 from "protobufjs/minimal"; +import { FieldMask } from "./google/protobuf/field_mask"; +import { ListValue, Struct } from "./google/protobuf/struct"; + +export const protobufPackage = ""; + +export interface Entity { + readonly intVal: number; + readonly stringVal: string; + readonly intArray: readonly number[]; + readonly stringArray: readonly string[]; + readonly subEntity: SubEntity | undefined; + readonly subEntityArray: readonly SubEntity[]; + readonly optionalIntVal?: number | undefined; + readonly fieldMask: readonly string[] | undefined; + readonly listValue: ReadonlyArray | undefined; + readonly structValue: { readonly [key: string]: any } | undefined; +} + +export interface SubEntity { + readonly subVal: number; +} + +function createBaseEntity(): Entity { + return { + intVal: 0, + stringVal: "", + intArray: [], + stringArray: [], + subEntity: undefined, + subEntityArray: [], + optionalIntVal: undefined, + fieldMask: undefined, + listValue: undefined, + structValue: undefined, + }; +} + +export const Entity = { + encode(message: Entity, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.intVal !== 0) { + writer.uint32(8).int32(message.intVal); + } + if (message.stringVal !== "") { + writer.uint32(18).string(message.stringVal); + } + writer.uint32(26).fork(); + for (const v of message.intArray) { + writer.int32(v); + } + writer.ldelim(); + for (const v of message.stringArray) { + writer.uint32(34).string(v!); + } + if (message.subEntity !== undefined) { + SubEntity.encode(message.subEntity, writer.uint32(42).fork()).ldelim(); + } + for (const v of message.subEntityArray) { + SubEntity.encode(v!, writer.uint32(50).fork()).ldelim(); + } + if (message.optionalIntVal !== undefined) { + writer.uint32(56).int32(message.optionalIntVal); + } + if (message.fieldMask !== undefined) { + FieldMask.encode(FieldMask.wrap(message.fieldMask), writer.uint32(66).fork()).ldelim(); + } + if (message.listValue !== undefined) { + ListValue.encode(ListValue.wrap(message.listValue), writer.uint32(74).fork()).ldelim(); + } + if (message.structValue !== undefined) { + Struct.encode(Struct.wrap(message.structValue), writer.uint32(82).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Entity { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseEntity() as any; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.intVal = reader.int32(); + break; + case 2: + message.stringVal = reader.string(); + break; + case 3: + if ((tag & 7) === 2) { + const end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) { + message.intArray.push(reader.int32()); + } + } else { + message.intArray.push(reader.int32()); + } + break; + case 4: + message.stringArray.push(reader.string()); + break; + case 5: + message.subEntity = SubEntity.decode(reader, reader.uint32()); + break; + case 6: + message.subEntityArray.push(SubEntity.decode(reader, reader.uint32())); + break; + case 7: + message.optionalIntVal = reader.int32(); + break; + case 8: + message.fieldMask = FieldMask.unwrap(FieldMask.decode(reader, reader.uint32())); + break; + case 9: + message.listValue = ListValue.unwrap(ListValue.decode(reader, reader.uint32())); + break; + case 10: + message.structValue = Struct.unwrap(Struct.decode(reader, reader.uint32())); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Entity { + return { + intVal: isSet(object.intVal) ? Number(object.intVal) : 0, + stringVal: isSet(object.stringVal) ? String(object.stringVal) : "", + intArray: Array.isArray(object?.intArray) ? object.intArray.map((e: any) => Number(e)) : [], + stringArray: Array.isArray(object?.stringArray) ? object.stringArray.map((e: any) => String(e)) : [], + subEntity: isSet(object.subEntity) ? SubEntity.fromJSON(object.subEntity) : undefined, + subEntityArray: Array.isArray(object?.subEntityArray) + ? object.subEntityArray.map((e: any) => SubEntity.fromJSON(e)) + : [], + optionalIntVal: isSet(object.optionalIntVal) ? Number(object.optionalIntVal) : undefined, + fieldMask: isSet(object.fieldMask) ? FieldMask.unwrap(FieldMask.fromJSON(object.fieldMask)) : undefined, + listValue: Array.isArray(object.listValue) ? [...object.listValue] : undefined, + structValue: isObject(object.structValue) ? object.structValue : undefined, + }; + }, + + toJSON(message: Entity): unknown { + const obj: any = {}; + message.intVal !== undefined && (obj.intVal = Math.round(message.intVal)); + message.stringVal !== undefined && (obj.stringVal = message.stringVal); + if (message.intArray) { + obj.intArray = message.intArray.map((e) => Math.round(e)); + } else { + obj.intArray = []; + } + if (message.stringArray) { + obj.stringArray = message.stringArray.map((e) => e); + } else { + obj.stringArray = []; + } + message.subEntity !== undefined && + (obj.subEntity = message.subEntity ? SubEntity.toJSON(message.subEntity) : undefined); + if (message.subEntityArray) { + obj.subEntityArray = message.subEntityArray.map((e) => e ? SubEntity.toJSON(e) : undefined); + } else { + obj.subEntityArray = []; + } + message.optionalIntVal !== undefined && (obj.optionalIntVal = Math.round(message.optionalIntVal)); + message.fieldMask !== undefined && (obj.fieldMask = FieldMask.toJSON(FieldMask.wrap(message.fieldMask))); + message.listValue !== undefined && (obj.listValue = message.listValue); + message.structValue !== undefined && (obj.structValue = message.structValue); + return obj; + }, + + fromPartial, I>>(object: I): Entity { + const message = createBaseEntity() as any; + message.intVal = object.intVal ?? 0; + message.stringVal = object.stringVal ?? ""; + message.intArray = object.intArray?.map((e) => e) || []; + message.stringArray = object.stringArray?.map((e) => e) || []; + message.subEntity = (object.subEntity !== undefined && object.subEntity !== null) + ? SubEntity.fromPartial(object.subEntity) + : undefined; + message.subEntityArray = object.subEntityArray?.map((e) => SubEntity.fromPartial(e)) || []; + message.optionalIntVal = object.optionalIntVal ?? undefined; + message.fieldMask = object.fieldMask ?? undefined; + message.listValue = object.listValue ?? undefined; + message.structValue = object.structValue ?? undefined; + return message; + }, +}; + +function createBaseSubEntity(): SubEntity { + return { subVal: 0 }; +} + +export const SubEntity = { + encode(message: SubEntity, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.subVal !== 0) { + writer.uint32(8).int32(message.subVal); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): SubEntity { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSubEntity() as any; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.subVal = reader.int32(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): SubEntity { + return { subVal: isSet(object.subVal) ? Number(object.subVal) : 0 }; + }, + + toJSON(message: SubEntity): unknown { + const obj: any = {}; + message.subVal !== undefined && (obj.subVal = Math.round(message.subVal)); + return obj; + }, + + fromPartial, I>>(object: I): SubEntity { + const message = createBaseSubEntity() as any; + message.subVal = object.subVal ?? 0; + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; + +function isObject(value: any): boolean { + return typeof value === "object" && value !== null; +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/src/main.ts b/src/main.ts index 6ca0bf753..d083f73cf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -751,7 +751,7 @@ function generateInterfaceDeclaration( const name = maybeSnakeToCamel(fieldDesc.name, options); const type = toTypeName(ctx, messageDesc, fieldDesc); const q = isOptionalProperty(fieldDesc, messageDesc.options, options) ? "?" : ""; - chunks.push(code`${name}${q}: ${type}, `); + chunks.push(code`${ctx.options.useReadonlyTypes ? "readonly " : ""}${name}${q}: ${type}, `); }); chunks.push(code`}`); @@ -874,9 +874,10 @@ function generateDecode(ctx: Context, fullName: string, messageDesc: DescriptorP ): ${fullName} { const reader = input instanceof ${Reader} ? input : new ${Reader}(input); let end = length === undefined ? reader.len : reader.pos + length; - const message = ${createBase}; `); + chunks.push(code`const message = ${createBase}${options.useReadonlyTypes ? " as any" : ""};`); + if (options.unknownFields) { chunks.push(code`(message as any)._unknownFields = {}`); } @@ -1592,7 +1593,7 @@ function generateFromPartial(ctx: Context, fullName: string, messageDesc: Descri createBase = code`Object.create(${createBase}) as ${fullName}`; } - chunks.push(code`const message = ${createBase};`); + chunks.push(code`const message = ${createBase}${options.useReadonlyTypes ? " as any" : ""};`); // add a check for each incoming field messageDesc.field.forEach((field) => { @@ -1740,7 +1741,7 @@ function generateWrap(ctx: Context, fullProtoTypeName: string, fieldNames: Struc if (isAnyValueTypeName(fullProtoTypeName)) { if (ctx.options.oneof === OneofOption.UNIONS) { chunks.push(code`wrap(value: any): Value { - const result = createBaseValue(); + const result = createBaseValue()${ctx.options.useReadonlyTypes ? " as any" : ""}; if (value === null) { result.kind = {$case: '${fieldNames.nullValue}', ${fieldNames.nullValue}: NullValue.NULL_VALUE}; @@ -1762,7 +1763,7 @@ function generateWrap(ctx: Context, fullProtoTypeName: string, fieldNames: Struc }`); } else { chunks.push(code`wrap(value: any): Value { - const result = createBaseValue(); + const result = createBaseValue()${ctx.options.useReadonlyTypes ? " as any" : ""}; if (value === null) { result.${fieldNames.nullValue} = NullValue.NULL_VALUE; @@ -1786,8 +1787,10 @@ function generateWrap(ctx: Context, fullProtoTypeName: string, fieldNames: Struc } if (isListValueTypeName(fullProtoTypeName)) { - chunks.push(code`wrap(value: Array | undefined): ListValue { - const result = createBaseListValue(); + chunks.push(code`wrap(value: ${ + ctx.options.useReadonlyTypes ? "ReadonlyArray" : "Array" + } | undefined): ListValue { + const result = createBaseListValue()${ctx.options.useReadonlyTypes ? " as any" : ""}; result.values = value ?? []; @@ -1796,8 +1799,8 @@ function generateWrap(ctx: Context, fullProtoTypeName: string, fieldNames: Struc } if (isFieldMaskTypeName(fullProtoTypeName)) { - chunks.push(code`wrap(paths: string[]): FieldMask { - const result = createBaseFieldMask(); + chunks.push(code`wrap(paths: ${ctx.options.useReadonlyTypes ? "readonly " : ""} string[]): FieldMask { + const result = createBaseFieldMask()${ctx.options.useReadonlyTypes ? " as any" : ""}; result.paths = paths; @@ -1860,13 +1863,13 @@ function generateUnwrap(ctx: Context, fullProtoTypeName: string, fieldNames: Str } if (isListValueTypeName(fullProtoTypeName)) { - chunks.push(code`unwrap(message: ListValue): Array { + chunks.push(code`unwrap(message: ${ctx.options.useReadonlyTypes ? "any" : "ListValue"}): Array { return message.values; }`); } if (isFieldMaskTypeName(fullProtoTypeName)) { - chunks.push(code`unwrap(message: FieldMask): string[] { + chunks.push(code`unwrap(message: ${ctx.options.useReadonlyTypes ? "any" : "FieldMask"}): string[] { return message.paths; }`); } diff --git a/src/options.ts b/src/options.ts index 1cbc67b70..091877aed 100644 --- a/src/options.ts +++ b/src/options.ts @@ -73,6 +73,7 @@ export type Options = { useNumericEnumForJson: boolean; initializeFieldsAsUndefined: boolean; useMapType: boolean; + useReadonlyTypes: boolean; M: { [from: string]: string }; }; @@ -117,6 +118,7 @@ export function defaultOptions(): Options { useNumericEnumForJson: false, initializeFieldsAsUndefined: true, useMapType: false, + useReadonlyTypes: false, M: {}, }; } diff --git a/src/types.ts b/src/types.ts index ef9784ced..458506cb7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -483,13 +483,17 @@ export function valueTypeName(ctx: Context, typeName: string): Code | undefined ? code`string` : code`Uint8Array`; case ".google.protobuf.ListValue": - return code`Array`; + return ctx.options.useReadonlyTypes ? code`ReadonlyArray` : code`Array`; case ".google.protobuf.Value": return code`any`; case ".google.protobuf.Struct": - return code`{[key: string]: any}`; + return ctx.options.useReadonlyTypes ? code`{readonly [key: string]: any}` : code`{[key: string]: any}`; case ".google.protobuf.FieldMask": - return ctx.options.useJsonWireFormat ? code`string` : code`string[]`; + return ctx.options.useJsonWireFormat + ? code`string` + : ctx.options.useReadonlyTypes + ? code`readonly string[]` + : code`string[]`; case ".google.protobuf.Duration": return ctx.options.useJsonWireFormat ? code`string` : undefined; case ".google.protobuf.Timestamp": @@ -596,6 +600,9 @@ export function toTypeName(ctx: Context, messageDesc: DescriptorProto, field: Fi } return code`{ [key: ${keyType} ]: ${valueType} }`; } + if (ctx.options.useReadonlyTypes) { + return code`readonly ${type}[]`; + } return code`${type}[]`; } diff --git a/tests/options-test.ts b/tests/options-test.ts index 45eb58801..5e7954f1e 100644 --- a/tests/options-test.ts +++ b/tests/options-test.ts @@ -49,6 +49,7 @@ describe("options", () => { "useNumericEnumForJson": false, "useOptionals": "none", "usePrototypeForDefaults": false, + "useReadonlyTypes": false, } `); });