From 29652e97c883d8ff8bfbcd37c8f9151535273c7b Mon Sep 17 00:00:00 2001 From: Gavin Mogan Date: Mon, 9 Dec 2024 00:35:50 -0800 Subject: [PATCH] feat: Make sure all types support prefix/suffix --- .../suffix-type/google/protobuf/field_mask.ts | 308 +++++++++ .../suffix-type/google/protobuf/struct.ts | 585 ++++++++++++++++++ integration/suffix-type/suffix-test.ts | 3 + integration/suffix-type/suffix-type.proto | 8 +- integration/suffix-type/suffix-type.ts | 69 ++- src/encode.ts | 16 +- src/generate-struct-wrappers.ts | 100 +-- src/main.ts | 30 +- src/utils.ts | 4 + src/visit.ts | 5 +- 10 files changed, 1057 insertions(+), 71 deletions(-) create mode 100644 integration/suffix-type/google/protobuf/field_mask.ts create mode 100644 integration/suffix-type/google/protobuf/struct.ts diff --git a/integration/suffix-type/google/protobuf/field_mask.ts b/integration/suffix-type/google/protobuf/field_mask.ts new file mode 100644 index 000000000..c4c477c76 --- /dev/null +++ b/integration/suffix-type/google/protobuf/field_mask.ts @@ -0,0 +1,308 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// source: google/protobuf/field_mask.proto + +/* eslint-disable */ +import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; + +export const protobufPackage = "google.protobuf"; + +/** + * `FieldMask` represents a set of symbolic field paths, for example: + * + * paths: "f.a" + * paths: "f.b.d" + * + * Here `f` represents a field in some root message, `a` and `b` + * fields in the message found in `f`, and `d` a field found in the + * message in `f.b`. + * + * Field masks are used to specify a subset of fields that should be + * returned by a get operation or modified by an update operation. + * Field masks also have a custom JSON encoding (see below). + * + * # Field Masks in Projections + * + * When used in the context of a projection, a response message or + * sub-message is filtered by the API to only contain those fields as + * specified in the mask. For example, if the mask in the previous + * example is applied to a response message as follows: + * + * f { + * a : 22 + * b { + * d : 1 + * x : 2 + * } + * y : 13 + * } + * z: 8 + * + * The result will not contain specific values for fields x,y and z + * (their value will be set to the default, and omitted in proto text + * output): + * + * f { + * a : 22 + * b { + * d : 1 + * } + * } + * + * A repeated field is not allowed except at the last position of a + * paths string. + * + * If a FieldMask object is not present in a get operation, the + * operation applies to all fields (as if a FieldMask of all fields + * had been specified). + * + * Note that a field mask does not necessarily apply to the + * top-level response message. In case of a REST get operation, the + * field mask applies directly to the response, but in case of a REST + * list operation, the mask instead applies to each individual message + * in the returned resource list. In case of a REST custom method, + * other definitions may be used. Where the mask applies will be + * clearly documented together with its declaration in the API. In + * any case, the effect on the returned resource/resources is required + * behavior for APIs. + * + * # Field Masks in Update Operations + * + * A field mask in update operations specifies which fields of the + * targeted resource are going to be updated. The API is required + * to only change the values of the fields as specified in the mask + * and leave the others untouched. If a resource is passed in to + * describe the updated values, the API ignores the values of all + * fields not covered by the mask. + * + * If a repeated field is specified for an update operation, new values will + * be appended to the existing repeated field in the target resource. Note that + * a repeated field is only allowed in the last position of a `paths` string. + * + * If a sub-message is specified in the last position of the field mask for an + * update operation, then new value will be merged into the existing sub-message + * in the target resource. + * + * For example, given the target message: + * + * f { + * b { + * d: 1 + * x: 2 + * } + * c: [1] + * } + * + * And an update message: + * + * f { + * b { + * d: 10 + * } + * c: [2] + * } + * + * then if the field mask is: + * + * paths: ["f.b", "f.c"] + * + * then the result will be: + * + * f { + * b { + * d: 10 + * x: 2 + * } + * c: [1, 2] + * } + * + * An implementation may provide options to override this default behavior for + * repeated and message fields. + * + * In order to reset a field's value to the default, the field must + * be in the mask and set to the default value in the provided resource. + * Hence, in order to reset all fields of a resource, provide a default + * instance of the resource and set all fields in the mask, or do + * not provide a mask as described below. + * + * If a field mask is not present on update, the operation applies to + * all fields (as if a field mask of all fields has been specified). + * Note that in the presence of schema evolution, this may mean that + * fields the client does not know and has therefore not filled into + * the request will be reset to their default. If this is unwanted + * behavior, a specific service may require a client to always specify + * a field mask, producing an error if not. + * + * As with get operations, the location of the resource which + * describes the updated values in the request message depends on the + * operation kind. In any case, the effect of the field mask is + * required to be honored by the API. + * + * ## Considerations for HTTP REST + * + * The HTTP kind of an update operation which uses a field mask must + * be set to PATCH instead of PUT in order to satisfy HTTP semantics + * (PUT must only be used for full updates). + * + * # JSON Encoding of Field Masks + * + * In JSON, a field mask is encoded as a single string where paths are + * separated by a comma. Fields name in each path are converted + * to/from lower-camel naming conventions. + * + * As an example, consider the following message declarations: + * + * message Profile { + * User user = 1; + * Photo photo = 2; + * } + * message User { + * string display_name = 1; + * string address = 2; + * } + * + * In proto a field mask for `Profile` may look as such: + * + * mask { + * paths: "user.display_name" + * paths: "photo" + * } + * + * In JSON, the same mask is represented as below: + * + * { + * mask: "user.displayName,photo" + * } + * + * # Field Masks and Oneof Fields + * + * Field masks treat fields in oneofs just as regular fields. Consider the + * following message: + * + * message SampleMessage { + * oneof test_oneof { + * string name = 4; + * SubMessage sub_message = 9; + * } + * } + * + * The field mask can be: + * + * mask { + * paths: "name" + * } + * + * Or: + * + * mask { + * paths: "sub_message" + * } + * + * Note that oneof type names ("test_oneof" in this case) cannot be used in + * paths. + * + * ## Field Mask Verification + * + * The implementation of any API method which has a FieldMask type field in the + * request should verify the included field paths, and return an + * `INVALID_ARGUMENT` error if any path is unmappable. + */ +export interface GRPCPFieldMaskGRPCS { + /** The set of field mask paths. */ + paths: string[]; +} + +function createBaseGRPCPFieldMaskGRPCS(): GRPCPFieldMaskGRPCS { + return { paths: [] }; +} + +export const GRPCPFieldMaskGRPCS: MessageFns & FieldMaskWrapperFns = { + encode(message: GRPCPFieldMaskGRPCS, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + for (const v of message.paths) { + writer.uint32(10).string(v!); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): GRPCPFieldMaskGRPCS { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGRPCPFieldMaskGRPCS(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.paths.push(reader.string()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): GRPCPFieldMaskGRPCS { + return { + paths: typeof object === "string" + ? object.split(",").filter(globalThis.Boolean) + : globalThis.Array.isArray(object?.paths) + ? object.paths.map(globalThis.String) + : [], + }; + }, + + toJSON(message: GRPCPFieldMaskGRPCS): string { + return message.paths.join(","); + }, + + create, I>>(base?: I): GRPCPFieldMaskGRPCS { + return GRPCPFieldMaskGRPCS.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): GRPCPFieldMaskGRPCS { + const message = createBaseGRPCPFieldMaskGRPCS(); + message.paths = object.paths?.map((e) => e) || []; + return message; + }, + + wrap(paths: string[]): GRPCPFieldMaskGRPCS { + const result = createBaseGRPCPFieldMaskGRPCS(); + result.paths = paths; + return result; + }, + + unwrap(message: GRPCPFieldMaskGRPCS): string[] { + return message.paths; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends globalThis.Array ? globalThis.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 }; + +export interface MessageFns { + encode(message: T, writer?: BinaryWriter): BinaryWriter; + decode(input: BinaryReader | Uint8Array, length?: number): T; + fromJSON(object: any): T; + toJSON(message: T): unknown; + create, I>>(base?: I): T; + fromPartial, I>>(object: I): T; +} + +export interface FieldMaskWrapperFns { + wrap(paths: string[]): GRPCPFieldMaskGRPCS; + unwrap(message: GRPCPFieldMaskGRPCS): string[]; +} diff --git a/integration/suffix-type/google/protobuf/struct.ts b/integration/suffix-type/google/protobuf/struct.ts new file mode 100644 index 000000000..d3d894b94 --- /dev/null +++ b/integration/suffix-type/google/protobuf/struct.ts @@ -0,0 +1,585 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// source: google/protobuf/struct.proto + +/* eslint-disable */ +import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; + +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 GRPCPNullValueGRPCS { + /** NULL_VALUE - Null value. */ + NULL_VALUE = 0, + UNRECOGNIZED = -1, +} + +export function gRPCPNullValueGRPCSFromJSON(object: any): GRPCPNullValueGRPCS { + switch (object) { + case 0: + case "NULL_VALUE": + return GRPCPNullValueGRPCS.NULL_VALUE; + case -1: + case "UNRECOGNIZED": + default: + return GRPCPNullValueGRPCS.UNRECOGNIZED; + } +} + +export function gRPCPNullValueGRPCSToJSON(object: GRPCPNullValueGRPCS): string { + switch (object) { + case GRPCPNullValueGRPCS.NULL_VALUE: + return "NULL_VALUE"; + case GRPCPNullValueGRPCS.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 GRPCPStructGRPCS { + /** Unordered map of dynamically typed values. */ + fields: { [key: string]: any | undefined }; +} + +export interface GRPCPStruct_FieldsEntryGRPCS { + key: string; + 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 GRPCPValueGRPCS { + /** Represents a null value. */ + nullValue?: + | GRPCPNullValueGRPCS + | undefined; + /** Represents a double value. */ + numberValue?: + | number + | undefined; + /** Represents a string value. */ + stringValue?: + | string + | undefined; + /** Represents a boolean value. */ + boolValue?: + | boolean + | undefined; + /** Represents a structured value. */ + structValue?: + | { [key: string]: any } + | undefined; + /** Represents a repeated `Value`. */ + listValue?: Array | undefined; +} + +/** + * `ListValue` is a wrapper around a repeated field of values. + * + * The JSON representation for `ListValue` is JSON array. + */ +export interface GRPCPListValueGRPCS { + /** Repeated field of dynamically typed values. */ + values: any[]; +} + +function createBaseGRPCPStructGRPCS(): GRPCPStructGRPCS { + return { fields: {} }; +} + +export const GRPCPStructGRPCS: MessageFns & StructWrapperFns = { + encode(message: GRPCPStructGRPCS, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + Object.entries(message.fields).forEach(([key, value]) => { + if (value !== undefined) { + GRPCPStruct_FieldsEntryGRPCS.encode({ key: key as any, value }, writer.uint32(10).fork()).join(); + } + }); + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): GRPCPStructGRPCS { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGRPCPStructGRPCS(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + const entry1 = GRPCPStruct_FieldsEntryGRPCS.decode(reader, reader.uint32()); + if (entry1.value !== undefined) { + message.fields[entry1.key] = entry1.value; + } + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): GRPCPStructGRPCS { + 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: GRPCPStructGRPCS): unknown { + const obj: any = {}; + if (message.fields) { + const entries = Object.entries(message.fields); + if (entries.length > 0) { + obj.fields = {}; + entries.forEach(([k, v]) => { + obj.fields[k] = v; + }); + } + } + return obj; + }, + + create, I>>(base?: I): GRPCPStructGRPCS { + return GRPCPStructGRPCS.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): GRPCPStructGRPCS { + const message = createBaseGRPCPStructGRPCS(); + 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): GRPCPStructGRPCS { + const struct = createBaseGRPCPStructGRPCS(); + + if (object !== undefined) { + for (const key of Object.keys(object)) { + struct.fields[key] = object[key]; + } + } + return struct; + }, + + unwrap(message: GRPCPStructGRPCS): { [key: string]: any } { + const object: { [key: string]: any } = {}; + if (message.fields) { + for (const key of Object.keys(message.fields)) { + object[key] = message.fields[key]; + } + } + return object; + }, +}; + +function createBaseGRPCPStruct_FieldsEntryGRPCS(): GRPCPStruct_FieldsEntryGRPCS { + return { key: "", value: undefined }; +} + +export const GRPCPStruct_FieldsEntryGRPCS: MessageFns = { + encode(message: GRPCPStruct_FieldsEntryGRPCS, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.key !== "") { + writer.uint32(10).string(message.key); + } + if (message.value !== undefined) { + GRPCPValueGRPCS.encode(GRPCPValueGRPCS.wrap(message.value), writer.uint32(18).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): GRPCPStruct_FieldsEntryGRPCS { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGRPCPStruct_FieldsEntryGRPCS(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.key = reader.string(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.value = GRPCPValueGRPCS.unwrap(GRPCPValueGRPCS.decode(reader, reader.uint32())); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): GRPCPStruct_FieldsEntryGRPCS { + return { + key: isSet(object.key) ? globalThis.String(object.key) : "", + value: isSet(object?.value) ? object.value : undefined, + }; + }, + + toJSON(message: GRPCPStruct_FieldsEntryGRPCS): unknown { + const obj: any = {}; + if (message.key !== "") { + obj.key = message.key; + } + if (message.value !== undefined) { + obj.value = message.value; + } + return obj; + }, + + create, I>>(base?: I): GRPCPStruct_FieldsEntryGRPCS { + return GRPCPStruct_FieldsEntryGRPCS.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): GRPCPStruct_FieldsEntryGRPCS { + const message = createBaseGRPCPStruct_FieldsEntryGRPCS(); + message.key = object.key ?? ""; + message.value = object.value ?? undefined; + return message; + }, +}; + +function createBaseGRPCPValueGRPCS(): GRPCPValueGRPCS { + return { + nullValue: undefined, + numberValue: undefined, + stringValue: undefined, + boolValue: undefined, + structValue: undefined, + listValue: undefined, + }; +} + +export const GRPCPValueGRPCS: MessageFns & AnyValueWrapperFns = { + encode(message: GRPCPValueGRPCS, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + 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) { + GRPCPStructGRPCS.encode(GRPCPStructGRPCS.wrap(message.structValue), writer.uint32(42).fork()).join(); + } + if (message.listValue !== undefined) { + GRPCPListValueGRPCS.encode(GRPCPListValueGRPCS.wrap(message.listValue), writer.uint32(50).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): GRPCPValueGRPCS { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGRPCPValueGRPCS(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.nullValue = reader.int32() as any; + continue; + } + case 2: { + if (tag !== 17) { + break; + } + + message.numberValue = reader.double(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.stringValue = reader.string(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.boolValue = reader.bool(); + continue; + } + case 5: { + if (tag !== 42) { + break; + } + + message.structValue = GRPCPStructGRPCS.unwrap(GRPCPStructGRPCS.decode(reader, reader.uint32())); + continue; + } + case 6: { + if (tag !== 50) { + break; + } + + message.listValue = GRPCPListValueGRPCS.unwrap(GRPCPListValueGRPCS.decode(reader, reader.uint32())); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): GRPCPValueGRPCS { + return { + nullValue: isSet(object.nullValue) ? gRPCPNullValueGRPCSFromJSON(object.nullValue) : undefined, + numberValue: isSet(object.numberValue) ? globalThis.Number(object.numberValue) : undefined, + stringValue: isSet(object.stringValue) ? globalThis.String(object.stringValue) : undefined, + boolValue: isSet(object.boolValue) ? globalThis.Boolean(object.boolValue) : undefined, + structValue: isObject(object.structValue) ? object.structValue : undefined, + listValue: globalThis.Array.isArray(object.listValue) ? [...object.listValue] : undefined, + }; + }, + + toJSON(message: GRPCPValueGRPCS): unknown { + const obj: any = {}; + if (message.nullValue !== undefined) { + obj.nullValue = gRPCPNullValueGRPCSToJSON(message.nullValue); + } + if (message.numberValue !== undefined) { + obj.numberValue = message.numberValue; + } + if (message.stringValue !== undefined) { + obj.stringValue = message.stringValue; + } + if (message.boolValue !== undefined) { + obj.boolValue = message.boolValue; + } + if (message.structValue !== undefined) { + obj.structValue = message.structValue; + } + if (message.listValue !== undefined) { + obj.listValue = message.listValue; + } + return obj; + }, + + create, I>>(base?: I): GRPCPValueGRPCS { + return GRPCPValueGRPCS.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): GRPCPValueGRPCS { + const message = createBaseGRPCPValueGRPCS(); + 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): GRPCPValueGRPCS { + const result = createBaseGRPCPValueGRPCS(); + if (value === null) { + result.nullValue = GRPCPNullValueGRPCS.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 (globalThis.Array.isArray(value)) { + result.listValue = value; + } else if (typeof value === "object") { + result.structValue = value; + } else if (typeof value !== "undefined") { + throw new globalThis.Error("Unsupported any value type: " + typeof value); + } + return result; + }, + + unwrap(message: any): 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 as any; + } else if (message?.listValue !== undefined) { + return message.listValue; + } else if (message?.nullValue !== undefined) { + return null; + } + return undefined; + }, +}; + +function createBaseGRPCPListValueGRPCS(): GRPCPListValueGRPCS { + return { values: [] }; +} + +export const GRPCPListValueGRPCS: MessageFns & ListValueWrapperFns = { + encode(message: GRPCPListValueGRPCS, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + for (const v of message.values) { + GRPCPValueGRPCS.encode(GRPCPValueGRPCS.wrap(v!), writer.uint32(10).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): GRPCPListValueGRPCS { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGRPCPListValueGRPCS(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.values.push(GRPCPValueGRPCS.unwrap(GRPCPValueGRPCS.decode(reader, reader.uint32()))); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): GRPCPListValueGRPCS { + return { values: globalThis.Array.isArray(object?.values) ? [...object.values] : [] }; + }, + + toJSON(message: GRPCPListValueGRPCS): unknown { + const obj: any = {}; + if (message.values?.length) { + obj.values = message.values; + } + return obj; + }, + + create, I>>(base?: I): GRPCPListValueGRPCS { + return GRPCPListValueGRPCS.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): GRPCPListValueGRPCS { + const message = createBaseGRPCPListValueGRPCS(); + message.values = object.values?.map((e) => e) || []; + return message; + }, + + wrap(array: Array | undefined): GRPCPListValueGRPCS { + const result = createBaseGRPCPListValueGRPCS(); + result.values = array ?? []; + return result; + }, + + unwrap(message: GRPCPListValueGRPCS): Array { + if (message?.hasOwnProperty("values") && globalThis.Array.isArray(message.values)) { + return message.values; + } else { + return message as any; + } + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends globalThis.Array ? globalThis.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; +} + +export interface MessageFns { + encode(message: T, writer?: BinaryWriter): BinaryWriter; + decode(input: BinaryReader | Uint8Array, length?: number): T; + fromJSON(object: any): T; + toJSON(message: T): unknown; + create, I>>(base?: I): T; + fromPartial, I>>(object: I): T; +} + +export interface StructWrapperFns { + wrap(object: { [key: string]: any } | undefined): GRPCPStructGRPCS; + unwrap(message: GRPCPStructGRPCS): { [key: string]: any }; +} + +export interface AnyValueWrapperFns { + wrap(value: any): GRPCPValueGRPCS; + unwrap(message: any): string | number | boolean | Object | null | Array | undefined; +} + +export interface ListValueWrapperFns { + wrap(array: Array | undefined): GRPCPListValueGRPCS; + unwrap(message: GRPCPListValueGRPCS): Array; +} diff --git a/integration/suffix-type/suffix-test.ts b/integration/suffix-type/suffix-test.ts index c57fb97dd..c84d5494d 100644 --- a/integration/suffix-type/suffix-test.ts +++ b/integration/suffix-type/suffix-test.ts @@ -4,6 +4,9 @@ describe("suffix", () => { it("generates types correctly", () => { const obj: GRPCPSuffixTypeGRPCS = { createdAt: new Date("2011-10-05T14:48:00.000Z"), + mask: undefined, + struct: undefined, + listValue: undefined, }; expect(obj).toBeTruthy(); diff --git a/integration/suffix-type/suffix-type.proto b/integration/suffix-type/suffix-type.proto index b6242f8ce..ada8b42da 100644 --- a/integration/suffix-type/suffix-type.proto +++ b/integration/suffix-type/suffix-type.proto @@ -1,6 +1,12 @@ syntax = "proto3"; + import "google/protobuf/timestamp.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/field_mask.proto"; message SuffixType { - google.protobuf.Timestamp created_at = 9; + google.protobuf.Timestamp created_at = 1; + google.protobuf.FieldMask mask = 2; + google.protobuf.Struct struct = 3; + google.protobuf.ListValue listValue = 4; } diff --git a/integration/suffix-type/suffix-type.ts b/integration/suffix-type/suffix-type.ts index 943c8a51c..878bf1cd4 100644 --- a/integration/suffix-type/suffix-type.ts +++ b/integration/suffix-type/suffix-type.ts @@ -3,22 +3,36 @@ /* eslint-disable */ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; +import { GRPCPFieldMaskGRPCS } from "./google/protobuf/field_mask"; +import { GRPCPListValueGRPCS, GRPCPStructGRPCS } from "./google/protobuf/struct"; import { GRPCPTimestampGRPCS } from "./google/protobuf/timestamp"; export const protobufPackage = ""; export interface GRPCPSuffixTypeGRPCS { createdAt: Date | undefined; + mask: string[] | undefined; + struct: { [key: string]: any } | undefined; + listValue: Array | undefined; } function createBaseGRPCPSuffixTypeGRPCS(): GRPCPSuffixTypeGRPCS { - return { createdAt: undefined }; + return { createdAt: undefined, mask: undefined, struct: undefined, listValue: undefined }; } export const GRPCPSuffixTypeGRPCS: MessageFns = { encode(message: GRPCPSuffixTypeGRPCS, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { if (message.createdAt !== undefined) { - GRPCPTimestampGRPCS.encode(toTimestamp(message.createdAt), writer.uint32(74).fork()).join(); + GRPCPTimestampGRPCS.encode(toTimestamp(message.createdAt), writer.uint32(10).fork()).join(); + } + if (message.mask !== undefined) { + GRPCPFieldMaskGRPCS.encode(GRPCPFieldMaskGRPCS.wrap(message.mask), writer.uint32(18).fork()).join(); + } + if (message.struct !== undefined) { + GRPCPStructGRPCS.encode(GRPCPStructGRPCS.wrap(message.struct), writer.uint32(26).fork()).join(); + } + if (message.listValue !== undefined) { + GRPCPListValueGRPCS.encode(GRPCPListValueGRPCS.wrap(message.listValue), writer.uint32(34).fork()).join(); } return writer; }, @@ -30,14 +44,38 @@ export const GRPCPSuffixTypeGRPCS: MessageFns = { while (reader.pos < end) { const tag = reader.uint32(); switch (tag >>> 3) { - case 9: { - if (tag !== 74) { + case 1: { + if (tag !== 10) { break; } message.createdAt = fromTimestamp(GRPCPTimestampGRPCS.decode(reader, reader.uint32())); continue; } + case 2: { + if (tag !== 18) { + break; + } + + message.mask = GRPCPFieldMaskGRPCS.unwrap(GRPCPFieldMaskGRPCS.decode(reader, reader.uint32())); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.struct = GRPCPStructGRPCS.unwrap(GRPCPStructGRPCS.decode(reader, reader.uint32())); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.listValue = GRPCPListValueGRPCS.unwrap(GRPCPListValueGRPCS.decode(reader, reader.uint32())); + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -48,7 +86,12 @@ export const GRPCPSuffixTypeGRPCS: MessageFns = { }, fromJSON(object: any): GRPCPSuffixTypeGRPCS { - return { createdAt: isSet(object.createdAt) ? fromJsonTimestamp(object.createdAt) : undefined }; + return { + createdAt: isSet(object.createdAt) ? fromJsonTimestamp(object.createdAt) : undefined, + mask: isSet(object.mask) ? GRPCPFieldMaskGRPCS.unwrap(GRPCPFieldMaskGRPCS.fromJSON(object.mask)) : undefined, + struct: isObject(object.struct) ? object.struct : undefined, + listValue: globalThis.Array.isArray(object.listValue) ? [...object.listValue] : undefined, + }; }, toJSON(message: GRPCPSuffixTypeGRPCS): unknown { @@ -56,6 +99,15 @@ export const GRPCPSuffixTypeGRPCS: MessageFns = { if (message.createdAt !== undefined) { obj.createdAt = message.createdAt.toISOString(); } + if (message.mask !== undefined) { + obj.mask = GRPCPFieldMaskGRPCS.toJSON(GRPCPFieldMaskGRPCS.wrap(message.mask)); + } + if (message.struct !== undefined) { + obj.struct = message.struct; + } + if (message.listValue !== undefined) { + obj.listValue = message.listValue; + } return obj; }, @@ -65,6 +117,9 @@ export const GRPCPSuffixTypeGRPCS: MessageFns = { fromPartial, I>>(object: I): GRPCPSuffixTypeGRPCS { const message = createBaseGRPCPSuffixTypeGRPCS(); message.createdAt = object.createdAt ?? undefined; + message.mask = object.mask ?? undefined; + message.struct = object.struct ?? undefined; + message.listValue = object.listValue ?? undefined; return message; }, }; @@ -103,6 +158,10 @@ function fromJsonTimestamp(o: any): Date { } } +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/encode.ts b/src/encode.ts index 4241521a2..be17e5352 100644 --- a/src/encode.ts +++ b/src/encode.ts @@ -2,7 +2,7 @@ import { Context } from "./context"; import { code, Code, Import } from "ts-poet"; import { messageToTypeName, wrapperTypeName } from "./types"; import { DateOption, LongOption } from "./options"; -import { impProto } from "./utils"; +import { impProto, wrapTypeName } from "./utils"; export function generateEncoder(ctx: Context, typeName: string): Code { const name = wrapperTypeName(typeName); @@ -11,7 +11,7 @@ export function generateEncoder(ctx: Context, typeName: string): Code { } if (name == "Timestamp") { - const TimestampValue = impProto(ctx.options, "google/protobuf/timestamp", name); + const TimestampValue = impProto(ctx.options, "google/protobuf/timestamp", wrapTypeName(ctx.options, name)); let value = code`value`; if ( @@ -25,16 +25,16 @@ export function generateEncoder(ctx: Context, typeName: string): Code { } if (name == "Struct" || name == "Value") { - const StructType = impProto(ctx.options, "google/protobuf/struct", name); + const StructType = impProto(ctx.options, "google/protobuf/struct", wrapTypeName(ctx.options, name)); return code`${StructType}.encode(${StructType}.wrap(value)).finish()`; } if (name == "ListValue") { - const ListValueType = impProto(ctx.options, "google/protobuf/struct", name); + const ListValueType = impProto(ctx.options, "google/protobuf/struct", wrapTypeName(ctx.options, name)); return code`${ListValueType}.encode({values: value ?? []}).finish()`; } - const TypeValue = impProto(ctx.options, "google/protobuf/wrappers", name); + const TypeValue = impProto(ctx.options, "google/protobuf/wrappers", wrapTypeName(ctx.options, name)); switch (name) { case "StringValue": @@ -70,7 +70,7 @@ export function generateDecoder(ctx: Context, typeName: string): Code { let TypeValue: Import; if (name == "Timestamp") { - TypeValue = impProto(ctx.options, "google/protobuf/timestamp", options.typePrefix + name + options.typeSuffix); + TypeValue = impProto(ctx.options, "google/protobuf/timestamp", wrapTypeName(ctx.options, name)); const decoder = code`${TypeValue}.decode(value)`; if ( @@ -84,11 +84,11 @@ export function generateDecoder(ctx: Context, typeName: string): Code { } if (name == "Struct" || name == "ListValue" || name == "Value") { - TypeValue = impProto(ctx.options, "google/protobuf/struct", name); + TypeValue = impProto(ctx.options, "google/protobuf/struct", wrapTypeName(ctx.options, name)); return code`${TypeValue}.unwrap(${TypeValue}.decode(value))`; } - TypeValue = impProto(ctx.options, "google/protobuf/wrappers", name); + TypeValue = impProto(ctx.options, "google/protobuf/wrappers", wrapTypeName(ctx.options, name)); return code`${TypeValue}.decode(value).value`; } diff --git a/src/generate-struct-wrappers.ts b/src/generate-struct-wrappers.ts index 465af21cd..d3d6222d2 100644 --- a/src/generate-struct-wrappers.ts +++ b/src/generate-struct-wrappers.ts @@ -1,5 +1,6 @@ import { Context } from "./context"; import { code, Code } from "ts-poet"; +import { wrapTypeName } from "./utils"; import { isAnyValueTypeName, isFieldMaskTypeName, isListValueTypeName, isStructTypeName } from "./types"; import { OneofOption, Options } from "./options"; @@ -30,16 +31,16 @@ export function isWrapperType(fullProtoTypeName: string): boolean { export function generateWrapDeep(ctx: Context, fullProtoTypeName: string, fieldNames: StructFieldNames): Code[] { const chunks: Code[] = []; if (isStructTypeName(fullProtoTypeName)) { - let setStatement = "struct.fields[key] = Value.wrap(object[key]);"; + let setStatement = `struct.fields[key] = ${wrapTypeName(ctx.options, "Value")}.wrap(object[key]);`; let defaultFields = "struct.fields ??= {};"; if (ctx.options.useMapType) { - setStatement = "struct.fields.set(key, Value.wrap(object[key]));"; + setStatement = `struct.fields.set(key, ${wrapTypeName(ctx.options, "Value")}.wrap(object[key]));`; defaultFields = "struct.fields ??= new Map();"; } if (ctx.options.useOptionals !== "all") defaultFields = ""; - chunks.push(code`wrap(object: {[key: string]: any} | undefined): Struct { - const struct = createBaseStruct(); + chunks.push(code`wrap(object: {[key: string]: any} | undefined): ${wrapTypeName(ctx.options, "Struct")} { + const struct = createBase${wrapTypeName(ctx.options, "Struct")}(); ${defaultFields} if (object !== undefined) { for (const key of Object.keys(object)) { @@ -52,10 +53,10 @@ export function generateWrapDeep(ctx: Context, fullProtoTypeName: string, fieldN if (isAnyValueTypeName(fullProtoTypeName)) { // Turn ts-proto representation --> proto representation - chunks.push(code`wrap(value: any): Value { + chunks.push(code`wrap(value: any): ${wrapTypeName(ctx.options, "Value")} { const result = {} as any; if (value === null) { - result.${fieldNames.nullValue} = NullValue.NULL_VALUE; + result.${fieldNames.nullValue} = ${wrapTypeName(ctx.options, "NullValue")}.NULL_VALUE; } else if (typeof value === 'boolean') { result.${fieldNames.boolValue} = value; } else if (typeof value === 'number') { @@ -63,9 +64,9 @@ export function generateWrapDeep(ctx: Context, fullProtoTypeName: string, fieldN } else if (typeof value === 'string') { result.${fieldNames.stringValue} = value; } else if (${ctx.utils.globalThis}.Array.isArray(value)) { - result.${fieldNames.listValue} = ListValue.wrap(value); + result.${fieldNames.listValue} = ${wrapTypeName(ctx.options, "ListValue")}.wrap(value); } else if (typeof value === 'object') { - result.${fieldNames.structValue} = Struct.wrap(value); + result.${fieldNames.structValue} = ${wrapTypeName(ctx.options, "Struct")}.wrap(value); } else if (typeof value !== 'undefined') { throw new ${ctx.utils.globalThis}.Error('Unsupported any value type: ' + typeof value); } @@ -75,16 +76,16 @@ export function generateWrapDeep(ctx: Context, fullProtoTypeName: string, fieldN if (isListValueTypeName(fullProtoTypeName)) { const maybeReadyOnly = ctx.options.useReadonlyTypes ? "Readonly" : ""; - chunks.push(code`wrap(array: ${maybeReadyOnly}Array | undefined): ListValue { - const result = createBaseListValue()${maybeAsAny(ctx.options)}; - result.values = (array ?? []).map(Value.wrap); + chunks.push(code`wrap(array: ${maybeReadyOnly}Array | undefined): ${wrapTypeName(ctx.options, "ListValue")} { + const result = createBase${wrapTypeName(ctx.options, "ListValue")}()${maybeAsAny(ctx.options)}; + result.values = (array ?? []).map(${wrapTypeName(ctx.options, "Value")}.wrap); return result; }`); } if (isFieldMaskTypeName(fullProtoTypeName)) { - chunks.push(code`wrap(paths: ${maybeReadonly(ctx.options)} string[]): FieldMask { - const result = createBaseFieldMask()${maybeAsAny(ctx.options)}; + chunks.push(code`wrap(paths: ${maybeReadonly(ctx.options)} string[]): ${wrapTypeName(ctx.options, "FieldMask")} { + const result = createBase${wrapTypeName(ctx.options, "FieldMask")}()${maybeAsAny(ctx.options)}; result.paths = paths; return result; }`); @@ -102,7 +103,7 @@ export function generateUnwrapDeep(ctx: Context, fullProtoTypeName: string, fiel const chunks: Code[] = []; if (isStructTypeName(fullProtoTypeName)) { if (ctx.options.useMapType) { - chunks.push(code`unwrap(message: Struct): {[key: string]: any} { + chunks.push(code`unwrap(message: ${wrapTypeName(ctx.options, "Struct")}: {[key: string]: any} { const object: { [key: string]: any } = {}; if (message.fields) { for (const key of message.fields.keys()) { @@ -112,7 +113,7 @@ export function generateUnwrapDeep(ctx: Context, fullProtoTypeName: string, fiel return object; }`); } else { - chunks.push(code`unwrap(message: Struct): {[key: string]: any} { + chunks.push(code`unwrap(message: ${wrapTypeName(ctx.options, "Struct")}): {[key: string]: any} { const object: { [key: string]: any } = {}; if (message.fields) { for (const key of Object.keys(message.fields)) { @@ -131,14 +132,18 @@ export function generateUnwrapDeep(ctx: Context, fullProtoTypeName: string, fiel chunks.push(code`unwrap(message: any): string | number | boolean | Object | null | Array | undefined { if (message?.hasOwnProperty('${fieldNames.stringValue}') && message.${fieldNames.stringValue} !== undefined) { return message.${fieldNames.stringValue}; - } else if (message?.hasOwnProperty('${fieldNames.numberValue}') && message?.${fieldNames.numberValue} !== undefined) { + } else if (message?.hasOwnProperty('${fieldNames.numberValue}') && message?.${ + fieldNames.numberValue + } !== undefined) { return message.${fieldNames.numberValue}; } else if (message?.hasOwnProperty('${fieldNames.boolValue}') && message?.${fieldNames.boolValue} !== undefined) { return message.${fieldNames.boolValue}; - } else if (message?.hasOwnProperty('${fieldNames.structValue}') && message?.${fieldNames.structValue} !== undefined) { - return Struct.unwrap(message.${fieldNames.structValue} as any); + } else if (message?.hasOwnProperty('${fieldNames.structValue}') && message?.${ + fieldNames.structValue + } !== undefined) { + return ${wrapTypeName(ctx.options, "Struct")}.unwrap(message.${fieldNames.structValue} as any); } else if (message?.hasOwnProperty('${fieldNames.listValue}') && message?.${fieldNames.listValue} !== undefined) { - return ListValue.unwrap(message.${fieldNames.listValue}); + return ${wrapTypeName(ctx.options, "ListValue")}.unwrap(message.${fieldNames.listValue}); } else if (message?.hasOwnProperty('${fieldNames.nullValue}') && message?.${fieldNames.nullValue} !== undefined) { return null; } @@ -147,7 +152,9 @@ export function generateUnwrapDeep(ctx: Context, fullProtoTypeName: string, fiel } if (isListValueTypeName(fullProtoTypeName)) { - chunks.push(code`unwrap(message: ${ctx.options.useReadonlyTypes ? "any" : "ListValue"}): Array { + chunks.push(code`unwrap(message: ${ + ctx.options.useReadonlyTypes ? "any" : wrapTypeName(ctx.options, "ListValue") + }): Array { if (message?.hasOwnProperty('values') && ${ctx.utils.globalThis}.Array.isArray(message.values)) { return message.values.map(Value.unwrap); } else { @@ -179,8 +186,8 @@ export function generateWrapShallow(ctx: Context, fullProtoTypeName: string, fie } if (ctx.options.useOptionals !== "all") defaultFields = ""; - chunks.push(code`wrap(object: {[key: string]: any} | undefined): Struct { - const struct = createBaseStruct(); + chunks.push(code`wrap(object: {[key: string]: any} | undefined): ${wrapTypeName(ctx.options, "Struct")} { + const struct = createBase${wrapTypeName(ctx.options, "Struct")}(); ${defaultFields} if (object !== undefined) { for (const key of Object.keys(object)) { @@ -193,10 +200,13 @@ export function generateWrapShallow(ctx: Context, fullProtoTypeName: string, fie if (isAnyValueTypeName(fullProtoTypeName)) { if (ctx.options.oneof === OneofOption.UNIONS) { - chunks.push(code`wrap(value: any): Value { - const result = createBaseValue()${maybeAsAny(ctx.options)}; + chunks.push(code`wrap(value: any): ${wrapTypeName(ctx.options, "Value")} { + const result = createBase${wrapTypeName(ctx.options, "Value")}()${maybeAsAny(ctx.options)}; if (value === null) { - result.kind = {$case: '${fieldNames.nullValue}', ${fieldNames.nullValue}: NullValue.NULL_VALUE}; + result.kind = {$case: '${fieldNames.nullValue}', ${fieldNames.nullValue}: ${wrapTypeName( + ctx.options, + "NullValue", + )}.NULL_VALUE}; } else if (typeof value === 'boolean') { result.kind = {$case: '${fieldNames.boolValue}', ${fieldNames.boolValue}: value}; } else if (typeof value === 'number') { @@ -213,8 +223,8 @@ export function generateWrapShallow(ctx: Context, fullProtoTypeName: string, fie return result; }`); } else if (ctx.options.oneof === OneofOption.UNIONS_VALUE) { - chunks.push(code`wrap(value: any): Value { - const result = createBaseValue()${maybeAsAny(ctx.options)}; + chunks.push(code`wrap(value: any): ${wrapTypeName(ctx.options, "Value")} { + const result = createBase${wrapTypeName(ctx.options, "Value")}()${maybeAsAny(ctx.options)}; if (value === null) { result.kind = {$case: '${fieldNames.nullValue}', value }; } else if (typeof value === 'boolean') { @@ -233,10 +243,10 @@ export function generateWrapShallow(ctx: Context, fullProtoTypeName: string, fie return result; }`); } else { - chunks.push(code`wrap(value: any): Value { - const result = createBaseValue()${maybeAsAny(ctx.options)}; + chunks.push(code`wrap(value: any): ${wrapTypeName(ctx.options, "Value")} { + const result = createBase${wrapTypeName(ctx.options, "Value")}()${maybeAsAny(ctx.options)}; if (value === null) { - result.${fieldNames.nullValue} = NullValue.NULL_VALUE; + result.${fieldNames.nullValue} = ${wrapTypeName(ctx.options, "NullValue")}.NULL_VALUE; } else if (typeof value === 'boolean') { result.${fieldNames.boolValue} = value; } else if (typeof value === 'number') { @@ -257,16 +267,16 @@ export function generateWrapShallow(ctx: Context, fullProtoTypeName: string, fie if (isListValueTypeName(fullProtoTypeName)) { const maybeReadyOnly = ctx.options.useReadonlyTypes ? "Readonly" : ""; - chunks.push(code`wrap(array: ${maybeReadyOnly}Array | undefined): ListValue { - const result = createBaseListValue()${maybeAsAny(ctx.options)}; + chunks.push(code`wrap(array: ${maybeReadyOnly}Array | undefined): ${wrapTypeName(ctx.options, "ListValue")} { + const result = createBase${wrapTypeName(ctx.options, "ListValue")}()${maybeAsAny(ctx.options)}; result.values = array ?? []; return result; }`); } if (isFieldMaskTypeName(fullProtoTypeName)) { - chunks.push(code`wrap(paths: ${maybeReadonly(ctx.options)} string[]): FieldMask { - const result = createBaseFieldMask()${maybeAsAny(ctx.options)}; + chunks.push(code`wrap(paths: ${maybeReadonly(ctx.options)} string[]): ${wrapTypeName(ctx.options, "FieldMask")} { + const result = createBase${wrapTypeName(ctx.options, "FieldMask")}()${maybeAsAny(ctx.options)}; result.paths = paths; return result; }`); @@ -284,7 +294,7 @@ export function generateUnwrapShallow(ctx: Context, fullProtoTypeName: string, f const chunks: Code[] = []; if (isStructTypeName(fullProtoTypeName)) { if (ctx.options.useMapType) { - chunks.push(code`unwrap(message: Struct): {[key: string]: any} { + chunks.push(code`unwrap(message: ${wrapTypeName(ctx.options, "Struct")}): {[key: string]: any} { const object: { [key: string]: any } = {}; if (message.fields) { for (const key of message.fields.keys()) { @@ -294,7 +304,7 @@ export function generateUnwrapShallow(ctx: Context, fullProtoTypeName: string, f return object; }`); } else { - chunks.push(code`unwrap(message: Struct): {[key: string]: any} { + chunks.push(code`unwrap(message: ${wrapTypeName(ctx.options, "Struct")}): {[key: string]: any} { const object: { [key: string]: any } = {}; if (message.fields) { for (const key of Object.keys(message.fields)) { @@ -308,7 +318,10 @@ export function generateUnwrapShallow(ctx: Context, fullProtoTypeName: string, f if (isAnyValueTypeName(fullProtoTypeName)) { if (ctx.options.oneof === OneofOption.UNIONS) { - chunks.push(code`unwrap(message: Value): string | number | boolean | Object | null | Array | undefined { + chunks.push(code`unwrap(message: ${wrapTypeName( + ctx.options, + "Value", + )}): string | number | boolean | Object | null | Array | undefined { if (message.kind?.$case === '${fieldNames.nullValue}') { return null; } else if (message.kind?.$case === '${fieldNames.numberValue}') { @@ -326,7 +339,10 @@ export function generateUnwrapShallow(ctx: Context, fullProtoTypeName: string, f } }`); } else if (ctx.options.oneof === OneofOption.UNIONS_VALUE) { - chunks.push(code`unwrap(message: Value): string | number | boolean | Object | null | Array | undefined { + chunks.push(code`unwrap(message: ${wrapTypeName( + ctx.options, + "Value", + )}): string | number | boolean | Object | null | Array | undefined { return message.kind?.value; }`); } else { @@ -350,7 +366,9 @@ export function generateUnwrapShallow(ctx: Context, fullProtoTypeName: string, f } if (isListValueTypeName(fullProtoTypeName)) { - chunks.push(code`unwrap(message: ${ctx.options.useReadonlyTypes ? "any" : "ListValue"}): Array { + chunks.push(code`unwrap(message: ${ + ctx.options.useReadonlyTypes ? "any" : wrapTypeName(ctx.options, "ListValue") + }): Array { if (message?.hasOwnProperty('values') && ${ctx.utils.globalThis}.Array.isArray(message.values)) { return message.values; } else { @@ -370,7 +388,9 @@ function generateFieldMaskUnwrap(ctx: Context): Code { const returnType = ctx.options.useOptionals === "all" ? "string[] | undefined" : "string[]"; const pathModifier = ctx.options.useOptionals === "all" ? "?" : ""; - return code`unwrap(message: ${ctx.options.useReadonlyTypes ? "any" : "FieldMask"}): ${returnType} { + return code`unwrap(message: ${ + ctx.options.useReadonlyTypes ? "any" : wrapTypeName(ctx.options, "FieldMask") + }): ${returnType} { return message${pathModifier}.paths; }`; } diff --git a/src/main.ts b/src/main.ts index 35b133d22..9b095cdf7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -109,6 +109,7 @@ import { withAndMaybeCheckIsNotNull, withOrMaybeCheckIsNotNull, withOrMaybeCheckIsNull, + wrapTypeName, } from "./utils"; import { visit, visitServices } from "./visit"; @@ -527,7 +528,7 @@ function makeProtobufTimestampWrapper() { function makeProtobufStructWrapper(options: Options) { const wrappers = imp("wrappers@protobufjs"); - const Struct = impProto(options, "google/protobuf/struct", "Struct"); + const Struct = impProto(options, "google/protobuf/struct", wrapTypeName(options, "Struct")); return code` ${wrappers}['.google.protobuf.Struct'] = { fromObject: ${Struct}.wrap, @@ -832,8 +833,8 @@ function makeMessageFns( "StructWrapperFns", code` ${maybeExport} interface StructWrapperFns { - wrap(object: {[key: string]: any} | undefined): Struct; - unwrap(message: Struct): {[key: string]: any}; + wrap(object: {[key: string]: any} | undefined): ${wrapTypeName(options, "Struct")}; + unwrap(message: ${wrapTypeName(options, "Struct")}): {[key: string]: any}; } `, ); @@ -842,7 +843,7 @@ function makeMessageFns( "AnyValueWrapperFns", code` ${maybeExport} interface AnyValueWrapperFns { - wrap(value: any): Value; + wrap(value: any): ${wrapTypeName(options, "Value")}; unwrap(message: any): string | number | boolean | Object | null | Array | undefined; } `, @@ -852,8 +853,11 @@ function makeMessageFns( "ListValueWrapperFns", code` ${maybeExport} interface ListValueWrapperFns { - wrap(array: ${options.useReadonlyTypes ? "Readonly" : ""}Array | undefined): ListValue; - unwrap(message: ${options.useReadonlyTypes ? "any" : "ListValue"}): Array; + wrap(array: ${options.useReadonlyTypes ? "Readonly" : ""}Array | undefined): ${wrapTypeName( + options, + "ListValue", + )}; + unwrap(message: ${options.useReadonlyTypes ? "any" : wrapTypeName(options, "ListValue")}): Array; } `, ); @@ -862,8 +866,8 @@ function makeMessageFns( "FieldMaskWrapperFns", code` ${maybeExport} interface FieldMaskWrapperFns { - wrap(paths: ${options.useReadonlyTypes ? "readonly" : ""} string[]): FieldMask; - unwrap(message: ${options.useReadonlyTypes ? "any" : "FieldMask"}): string[] ${ + wrap(paths: ${options.useReadonlyTypes ? "readonly" : ""} string[]): ${wrapTypeName(options, "FieldMask")}; + unwrap(message: ${options.useReadonlyTypes ? "any" : wrapTypeName(options, "FieldMask")}): string[] ${ options.useOptionals === "all" ? "| undefined" : "" }; } @@ -926,11 +930,7 @@ function makeTimestampMethods( longs: ReturnType, bytes: ReturnType, ) { - const Timestamp = impProto( - options, - "google/protobuf/timestamp", - `${options.typePrefix}Timestamp${options.typeSuffix}`, - ); + const Timestamp = impProto(options, "google/protobuf/timestamp", wrapTypeName(options, "Timestamp")); const NanoDate = imp("NanoDate=nano-date"); let seconds: string | Code = "Math.trunc(date.getTime() / 1_000)"; @@ -1033,12 +1033,12 @@ function makeTimestampMethods( } else if (typeof o === "string") { return new ${bytes.globalThis}.Date(o); } else { - return ${fromTimestamp}(${options.typePrefix}Timestamp${options.typeSuffix}.fromJSON(o)); + return ${fromTimestamp}(${wrapTypeName(options, "Timestamp")}.fromJSON(o)); } } ` : code` - function fromJsonTimestamp(o: any): ${options.typePrefix}Timestamp${options.typeSuffix} { + function fromJsonTimestamp(o: any): ${wrapTypeName(options, "Timestamp")} { if (o instanceof ${bytes.globalThis}.Date) { return ${toTimestamp}(o); } else if (typeof o === "string") { diff --git a/src/utils.ts b/src/utils.ts index 0c6a332a2..e722cf568 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -344,3 +344,7 @@ async function readPackageJson(): Promise { return await import("../package.json" as string); } } + +export function wrapTypeName(options: Options, name: string) { + return options.typePrefix + name + options.typeSuffix; +} diff --git a/src/visit.ts b/src/visit.ts index 36a0174ba..52a43864c 100644 --- a/src/visit.ts +++ b/src/visit.ts @@ -4,6 +4,7 @@ import { FileDescriptorProto, ServiceDescriptorProto, } from "ts-proto-descriptors"; +import { wrapTypeName } from "./utils"; import SourceInfo, { Fields } from "./sourceInfo"; import { Options } from "./options"; import { maybeSnakeToCamel } from "./case"; @@ -39,7 +40,7 @@ export function visit( const protoFullName = protoPrefix + enumDesc.name; // I.e. FooBar_ZazInner const tsFullName = tsPrefix + maybeSnakeToCamel(enumDesc.name, options); - const tsFullNameWithAffixes = messageName(`${options.typePrefix}${tsFullName}${options.typeSuffix}`); + const tsFullNameWithAffixes = messageName(wrapTypeName(options, tsFullName)); const nestedSourceInfo = sourceInfo.open(childEnumType, index); enumFn(tsFullNameWithAffixes, enumDesc, nestedSourceInfo, protoFullName); }); @@ -52,7 +53,7 @@ export function visit( const protoFullName = protoPrefix + message.name; // I.e. FooBar_ZazInner const tsFullName = tsPrefix + maybeSnakeToCamel(message.name, options); - const tsFullNameWithAffixes = messageName(`${options.typePrefix}${tsFullName}${options.typeSuffix}`); + const tsFullNameWithAffixes = messageName(wrapTypeName(options, tsFullName)); const nestedSourceInfo = sourceInfo.open(childType, index); messageFn(tsFullNameWithAffixes, message, nestedSourceInfo, protoFullName); const delim = options.useSnakeTypeName ? "_" : "";