From 3264b0f4a98f3e00b262ce8af7927b0bc0375160 Mon Sep 17 00:00:00 2001 From: Stephen Haberman Date: Thu, 29 Dec 2022 10:59:34 -0600 Subject: [PATCH] fix: Additional fix for structs with useMapType. (#743) --- .../use-map-type/google/protobuf/struct.ts | 469 ++++++++++++++++++ integration/use-map-type/use-map-type-test.ts | 2 + integration/use-map-type/use-map-type.bin | Bin 7634 -> 12261 bytes integration/use-map-type/use-map-type.proto | 4 + integration/use-map-type/use-map-type.ts | 12 + src/main.ts | 57 ++- 6 files changed, 518 insertions(+), 26 deletions(-) create mode 100644 integration/use-map-type/google/protobuf/struct.ts diff --git a/integration/use-map-type/google/protobuf/struct.ts b/integration/use-map-type/google/protobuf/struct.ts new file mode 100644 index 000000000..8ff1c706f --- /dev/null +++ b/integration/use-map-type/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. */ + fields: Map; +} + +export interface Struct_FieldsEntry { + 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 Value { + /** Represents a null value. */ + nullValue?: + | NullValue + | 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 ListValue { + /** Repeated field of dynamically typed values. */ + values: any[]; +} + +function createBaseStruct(): Struct { + return { fields: new Map() }; +} + +export const Struct = { + encode(message: Struct, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + message.fields.forEach((value, key) => { + 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(); + 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.set(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>((acc, [key, value]) => { + acc.set(key, value as any | undefined); + return acc; + }, new Map()) + : new Map(), + }; + }, + + toJSON(message: Struct): unknown { + const obj: any = {}; + obj.fields = {}; + if (message.fields) { + message.fields.forEach((v, k) => { + obj.fields[k] = v; + }); + } + return obj; + }, + + fromPartial, I>>(object: I): Struct { + const message = createBaseStruct(); + message.fields = (() => { + const m = new Map(); + (object.fields as Map ?? new Map()).forEach((value, key) => { + if (value !== undefined) { + m.set(key, value); + } + }); + return m; + })(); + return message; + }, + + wrap(object: { [key: string]: any } | undefined): Struct { + const struct = createBaseStruct(); + if (object !== undefined) { + Object.keys(object).forEach((key) => { + struct.fields.set(key, object[key]); + }); + } + return struct; + }, + + unwrap(message: Struct): { [key: string]: any } { + const object: { [key: string]: any } = {}; + [...message.fields.keys()].forEach((key) => { + object[key] = message.fields.get(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(); + 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(); + 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(); + 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(); + 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(); + + 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(); + 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(); + message.values = object.values?.map((e) => e) || []; + return message; + }, + + wrap(value: Array | undefined): ListValue { + const result = createBaseListValue(); + + result.values = value ?? []; + + return result; + }, + + unwrap(message: ListValue): 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-map-type/use-map-type-test.ts b/integration/use-map-type/use-map-type-test.ts index 1e3d839e3..77cd2ea18 100644 --- a/integration/use-map-type/use-map-type-test.ts +++ b/integration/use-map-type/use-map-type-test.ts @@ -10,6 +10,7 @@ describe("use-map-type", () => { stringToBytes: new Map([["bar", new TextEncoder().encode("buz")]]), int64ToInt64: new Map([[3, 4]]), mapOfTimestamps: new Map([["qux", now]]), + struct: { foo: 1 }, }; const jsonFromObject = Maps.toJSON(m); @@ -24,6 +25,7 @@ describe("use-map-type", () => { stringToBytes: new Map([["bar", Buffer.from(new TextEncoder().encode("buz"))]]), int64ToInt64: m.int64ToInt64, mapOfTimestamps: m.mapOfTimestamps, + struct: { foo: 1 }, }); const jsonFromDecoded = Maps.toJSON(decoded); expect(jsonFromDecoded).toEqual(jsonFromObject); diff --git a/integration/use-map-type/use-map-type.bin b/integration/use-map-type/use-map-type.bin index 777daacec49ac349d0ec9c03373078ff604c8bc6..ad1f2909c35625b3b6c654819141c299a6ac32cf 100644 GIT binary patch delta 3304 zcmai0OK%%h6rQ;=cI|7&Nv1D%lD1cANt=>7A`}Vwph~NxqzR=>nl7l)W^CU$L!B9G zW}Ff>Be6%Kbb}CUBzA}{*sww32O$0e0tty9z^;7f&Ld4iz$>47&-u>lJ7?~%SAKu| z-Jf;)4FD@&|y26X=uI#qMx-N|KmFY8$ z#H^8-H4??foYI>0qF{R|HL>sUN^<=CC%Ya`^1_zW?aMk7I*^X-LwZ2BONr8D_O?Fu zJ>vy+h%mUK?ZOBz=XJDd zoZnelTzvdsW^rY9NV^g9!!3VvZ{crL5)#}cD&5sToJu*;@w<-KY4|}0EPM9L;bKyn zXMbHRe|3@c|cpkx{2+T`;;4!s_~FgPA8$%Q$@HC$3@3B%Tx`0XjYu@52>ZRB zA4D*0ZX*)UiQFx@6CFg~Ylt~N5b|SZv)7fA!u7ZaTY=kCk>&8B>pqd<_Hv$&aKm^9 z2_78e3R}PVk!(c`aeqUKwu~IN8&ZDJh7;|mE62hKNQIm=VITP&8Ewb_HE^R17)YV! zIZHyUBTPEYVrs;kD>t{7Ke)3Oo0Ri*xFI5kP8u7>&oDLJGNwy4{th=yjcJx~i3xjB ztayIVmeee20wO=K2|VUiULe~d86U|c;9fd9l1T=nQ>XdQ3{=rA^P2X)ZvVir8!sR;Hu$^6i zRc-J%lEe`qgTN0O|JS^3|&^x_^3B1l04f}{<1Ff4pb{J5P zpaYZZr7Ay17S!z-^Q3KyrB?@)wsTU5%uKa*#vDjM7=g^x>X{Uyw=*N7d_rO3?xs0v zkM727`~7BD?m^WHq??AuD4Uy=<0+~R(3|#13d$^Fc0Xv+XAx&LQ7JM8)Y%-B0d+P< zWk8(;>ID@lgj;6aKDj&8lrwu`HGq1{ETz#IK)qEyoJcV<2zhc(%T;#ojy zX^Xx`TNZTx?`5^yzuH%8guMn+kiJ0VTQ1bN&CP<^S1U0awg&{+z1lOngz5^bQkO^q ziB<-O_@~I&WS03*d!*Z2VlDq3rwKm|oL&!e?*x7yBSf1ct*`nx1XcmfQ3jAAh+ve4AQN#WRxJG@vD^~ zMNdd@vpSp%79=!&)gEaU)mP=sPyXhgA9?<=K5@l9;MJMDi^l4_h!>*p>6H$%8cSD(T6VP~W4MdSj<#Lxwei#rIS#Uk)Q-V!F#W3Pj3_gB^lWwqv%z!c zQ!@xdxjO@9L%BQJS3}Ajd82hTlzHnhP-mE3Pa$a=4d=PiHX5F|oZZ20_}aUvJCxO8 bStPV-ES7x|Xw_IJPN!zHYOJv{8RPx|p>P^> delta 473 zcmX|+K}y6x5JkJHyE928=|E3LvJoWeBpJkwJ4Ic20WTnkn_1|H#D%LMUO<==cmrW> z;tgC3;w`L>>Fnz9>;0m(H(zI;zXkq8@A>+5(d?E51y>L4vVF0JaCuk29@P4pr}A5G zwu^h%yd6D5^dTv*N{DO?$I}i(Lm>fLDMdy^sDxEoOn_&p1X!s%gi=WdC)*TTz(wb>Ql#!@75v0!Ey9;!WDw_r!fPuLh5!7^@)y z)?%D`a#>z_Vl6(L4;sIiU^7I>T7o%s?ioO2FPRDaERk3E8Q string_to_bytes = 3; map int64_to_int64 = 4; map map_of_timestamps = 5; + google.protobuf.Struct struct = 6; } diff --git a/integration/use-map-type/use-map-type.ts b/integration/use-map-type/use-map-type.ts index 158713474..a0aa400a6 100644 --- a/integration/use-map-type/use-map-type.ts +++ b/integration/use-map-type/use-map-type.ts @@ -1,6 +1,7 @@ /* eslint-disable */ import * as Long from "long"; import * as _m0 from "protobufjs/minimal"; +import { Struct } from "./google/protobuf/struct"; import { Timestamp } from "./google/protobuf/timestamp"; export const protobufPackage = "simple"; @@ -15,6 +16,7 @@ export interface Maps { stringToBytes: Map; int64ToInt64: Map; mapOfTimestamps: Map; + struct: { [key: string]: any } | undefined; } export interface Maps_StrToEntityEntry { @@ -96,6 +98,7 @@ function createBaseMaps(): Maps { stringToBytes: new Map(), int64ToInt64: new Map(), mapOfTimestamps: new Map(), + struct: undefined, }; } @@ -116,6 +119,9 @@ export const Maps = { message.mapOfTimestamps.forEach((value, key) => { Maps_MapOfTimestampsEntry.encode({ key: key as any, value }, writer.uint32(42).fork()).ldelim(); }); + if (message.struct !== undefined) { + Struct.encode(Struct.wrap(message.struct), writer.uint32(50).fork()).ldelim(); + } return writer; }, @@ -156,6 +162,9 @@ export const Maps = { message.mapOfTimestamps.set(entry5.key, entry5.value); } break; + case 6: + message.struct = Struct.unwrap(Struct.decode(reader, reader.uint32())); + break; default: reader.skipType(tag & 7); break; @@ -196,6 +205,7 @@ export const Maps = { return acc; }, new Map()) : new Map(), + struct: isObject(object.struct) ? object.struct : undefined, }; }, @@ -231,6 +241,7 @@ export const Maps = { obj.mapOfTimestamps[k] = v.toISOString(); }); } + message.struct !== undefined && (obj.struct = message.struct); return obj; }, @@ -281,6 +292,7 @@ export const Maps = { }); return m; })(); + message.struct = object.struct ?? undefined; return message; }, }; diff --git a/src/main.ts b/src/main.ts index 6d954efe6..deb77a22c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1845,18 +1845,23 @@ function generateWrap(ctx: Context, fullProtoTypeName: string, fieldNames: Struc function generateUnwrap(ctx: Context, fullProtoTypeName: string, fieldNames: StructFieldNames): Code[] { const chunks: Code[] = []; if (isStructTypeName(fullProtoTypeName)) { - let setStatement = "object[key] = message.fields[key];"; if (ctx.options.useMapType) { - setStatement = "object[key] = message.fields.get(key);"; + chunks.push(code`unwrap(message: Struct): {[key: string]: any} { + const object: { [key: string]: any } = {}; + [...message.fields.keys()].forEach((key) => { + object[key] = message.fields.get(key); + }); + return object; + }`); + } else { + chunks.push(code`unwrap(message: Struct): {[key: string]: any} { + const object: { [key: string]: any } = {}; + Object.keys(message.fields).forEach(key => { + object[key] = message.fields[key]; + }); + return object; + }`); } - - chunks.push(code`unwrap(message: Struct): {[key: string]: any} { - const object: { [key: string]: any } = {}; - Object.keys(message.fields).forEach(key => { - ${setStatement} - }); - return object; - }`); } if (isAnyValueTypeName(fullProtoTypeName)) { @@ -1877,24 +1882,24 @@ function generateUnwrap(ctx: Context, fullProtoTypeName: string, fieldNames: Str } else { return undefined; } - }`); + }`); } else { chunks.push(code`unwrap(message: Value): string | number | boolean | Object | null | Array | undefined { - if (message?.${fieldNames.stringValue} !== undefined) { - return message.${fieldNames.stringValue}; - } else if (message?.${fieldNames.numberValue} !== undefined) { - return message.${fieldNames.numberValue}; - } else if (message?.${fieldNames.boolValue} !== undefined) { - return message.${fieldNames.boolValue}; - } else if (message?.${fieldNames.structValue} !== undefined) { - return message.${fieldNames.structValue}; - } else if (message?.${fieldNames.listValue} !== undefined) { - return message.${fieldNames.listValue}; - } else if (message?.${fieldNames.nullValue} !== undefined) { - return null; - } - return undefined; - }`); + if (message?.${fieldNames.stringValue} !== undefined) { + return message.${fieldNames.stringValue}; + } else if (message?.${fieldNames.numberValue} !== undefined) { + return message.${fieldNames.numberValue}; + } else if (message?.${fieldNames.boolValue} !== undefined) { + return message.${fieldNames.boolValue}; + } else if (message?.${fieldNames.structValue} !== undefined) { + return message.${fieldNames.structValue}; + } else if (message?.${fieldNames.listValue} !== undefined) { + return message.${fieldNames.listValue}; + } else if (message?.${fieldNames.nullValue} !== undefined) { + return null; + } + return undefined; + }`); } }