From 7dd9c16ffdec4d9ea296fbdc30d390fe44192c42 Mon Sep 17 00:00:00 2001 From: Bouke Versteegh Date: Mon, 22 Nov 2021 09:57:13 +0100 Subject: [PATCH] feat: Support for Google.Protobuf.Value, ListValue and Struct (#396) --- README.markdown | 211 ++++++- integration/pbjs.sh | 8 + integration/struct/google/protobuf/struct.ts | 436 +++++++++++++ integration/struct/struct-test.ts | 63 ++ integration/struct/struct.bin | Bin 0 -> 4734 bytes integration/struct/struct.proto | 7 + integration/struct/struct.ts | 91 +++ integration/value/google/protobuf/struct.ts | 436 +++++++++++++ integration/value/google/protobuf/wrappers.ts | 578 ++++++++++++++++++ integration/value/value-test.ts | 64 ++ integration/value/value.bin | Bin 0 -> 9573 bytes integration/value/value.proto | 10 + integration/value/value.ts | 136 +++++ src/encode.ts | 2 + src/main.ts | 127 +++- src/types.ts | 23 + 16 files changed, 2165 insertions(+), 27 deletions(-) create mode 100644 integration/struct/google/protobuf/struct.ts create mode 100644 integration/struct/struct-test.ts create mode 100644 integration/struct/struct.bin create mode 100644 integration/struct/struct.proto create mode 100644 integration/struct/struct.ts create mode 100644 integration/value/google/protobuf/struct.ts create mode 100644 integration/value/google/protobuf/wrappers.ts create mode 100644 integration/value/value-test.ts create mode 100644 integration/value/value.bin create mode 100644 integration/value/value.proto create mode 100644 integration/value/value.ts diff --git a/README.markdown b/README.markdown index 05610fb7b..b3adc1ec4 100644 --- a/README.markdown +++ b/README.markdown @@ -22,10 +22,13 @@ - [Assumptions](#assumptions) - [Todo](#todo) - [OneOf Handling](#oneof-handling) -- [Primitive Types](#primitive-types) +- [Default Values and Unset Fields](#default-values-and-unset-fields) +- [Well-Known Types](#well-known-types) + - [Wrapper Types](#wrapper-types) + - [JSON Types (Struct Types)](#json-types-struct-types) + - [Timestamps](#timestamp) - [Wrapper Types](#wrapper-types) - [Number Types](#number-types) -- [Timestamps](#timestamps) - [Current Status of Optional Values](#current-status-of-optional-values) # Overview @@ -399,29 +402,201 @@ As this will automatically enforce only one of `field_a` or `field_b` "being set In ts-proto's currently-unscheduled 2.x release, `oneof=unions` will become the default behavior. -# Primitive Types +# Default values and unset fields -Protobuf has the somewhat annoying behavior that primitives types cannot differentiate between set-to-defalut-value and unset. +In core Protobuf, values that are _unset_ or equal to the default value are not sent over the wire. +The default value of a message is `undefined`. Primitive types take their natural default value, i.e. `string` is `''`, `number` is `0`, etc. +This behavior enables forward compatibility, as primitive fields will always have a value, even when omitted by outdated agents, but it also means _default_ and _unset_ values cannot be distinguished. -I.e. if you have a `string name = 1`, and set `object.name = ''`, Protobuf will skip sending the tagged `name` field over the wire, because its understood that readers on the other end will, when they see `name` is not included in the payload, return empty string. +If you need primitive fields where you can detect set/unset, see [Wrapper Types](#wrapper-types). -`ts-proto` models this behavior, of "unset" values being the primitive's default. (Technically by setting up an object prototype that knows the default values of the message's primitive fields.) +**Encode / Decode** -If you want fields where you can model set/unset, see Wrapper Types. +`ts-proto` follows the Protobuf rules, and always returns default values for unsets fields when decoding, while omitting them from the output when serialized in binary format. -# Wrapper Types +```protobuf +syntax = "proto3"; +message Foo { + string bar = 1; +} +``` + +```typescript +protobufBytes; // assume this is an empty Foo object, in protobuf binary format +Foo.decode(protobufBytes); // => { bar: '' } +``` + +```typescript +Foo.encode({ bar: '' }); // => { }, writes an empty Foo object, in protobuf binary format +``` + +**fromJSON / toJSON** + +Reading JSON will also initialize the default values. Since senders may either omit unset fields, or set them to the default value, use `fromJSON` to normalize the input. + +```typescript +Foo.fromJSON({ }); // => { bar: '' } +Foo.fromJSON({ bar: '' }); // => { bar: '' } +Foo.fromJSON({ bar: 'baz' }); // => { bar: 'baz' } +``` + +When writing JSON, `ts-proto` currently does **not** normalize message when converting to JSON, other than omitting unset fields, but it may do so in the future. + +```typescript +// Current ts-proto behavior +Foo.toJSON({ }); // => { } +Foo.toJSON({ bar: undefined }); // => { } +Foo.toJSON({ bar: '' }); // => { bar: '' } - note: this is the default value, but it's not omitted +Foo.toJSON({ bar: 'baz' }); // => { bar: 'baz' } +``` + +```typescript +// Possible future behavior, where ts-proto would normalize message +Foo.toJSON({ }); // => { } +Foo.toJSON({ bar: undefined }); // => { } +Foo.toJSON({ bar: '' }); // => { } - note: omitting the default value, as expected +Foo.toJSON({ bar: 'baz' }); // => { bar: 'baz' } +``` -In core Protobuf, unset primitive fields become their respective default values (so you loose ability to distinguish "unset" from "default"). +- Please open an issue if you need this behavior. -However, unset message fields stay `null`. +# Well-Known Types -This allows a cute hack where you can model a logical `string | unset` by creating a field that is technically a message (i.e. so it can stay `null` for the unset case), but the message only has a single string field (i.e for storing the value in the set case). +Protobuf comes with several predefined message definitions, called "[Well-Known Types](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf)". +Their interpretation is defined by the Protobuf specification, and libraries are expected to convert these messages to corresponding native types in the target language. -Protobuf has already "blessed" this pattern with several built-in types, i.e. `google.protobuf.StringValue`, `google.protobuf.Int32Value`, etc. +`ts-proto` currently automatically converts these messages to their corresponding native types. -`ts-proto` understands these wrapper types and "re-idiomizes" them by generating a `google.protobuf.StringValue name = 1` field as a `name: string | undefined`, and hides the `StringValue` implementation detail from your code (i.e. during `encode`/`decode` of the `name` field on the wire to external consumers, it's still read/written as a `StringValue` message field). +- Wrapper Types: + + * [google.protobuf.DoubleValue](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#DoubleValue) ⇆ `number | undefined` + * [google.protobuf.FloatValue](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#FloatValue) ⇆ `number | undefined` + * [google.protobuf.Int64Value](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#Int64Value) ⇆ `number | undefined` + * [google.protobuf.UInt64Value](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#UInt64Value) ⇆ `number | undefined` + * [google.protobuf.Int32Value](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#Int32Value) ⇆ `number | undefined` + * [google.protobuf.UInt32Value](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#UInt32Value) ⇆ `number | undefined` + * [google.protobuf.BoolValue](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#BoolValue) ⇆ `boolean | undefined` + * [google.protobuf.StringValue](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#StringValue) ⇆ `string | undefined` + * [google.protobuf.BytesValue](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.BytesValue) ⇆ `Uint8Array | undefined` + +- JSON Types (Struct Types): + + * [google.protobuf.Value](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#Value) ⇆ `any | undefined` (i.e. `number | string | boolean | null | array | object`) + * [google.protobuf.ListValue](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#ListValue) ⇆ `any[]` + * [google.protobuf.Struct](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#Struct) ⇆ `{ [key: string]: any } | undefined` + +## Wrapper Types + +Wrapper Types are messages containing a single primitive field, and can be imported in `.proto` files with `import "google/protobuf/wrappers.proto"`. + + +Since these are _messages_, their default value is `undefined`, allowing you to distinguish unset primitives from their default values, when using Wrapper Types. +`ts-proto` generates these fields as ` | undefined`. + +For example: + +```protobuf +// Protobuf +syntax = "proto3"; + +import "google/protobuf/wrappers.proto"; + +message ExampleMessage { + google.protobuf.StringValue name = 1; +} +``` + +```typescript +// TypeScript +interface ExampleMessage { + name: string | undefined; +} +``` + +When encoding a message the primitive value is converted back to its corresponding wrapper type: + +```typescript +ExampleMessage.encode({ name: 'foo' }) // => { name: { value: 'foo' } }, in binary +``` + +When calling toJSON, the value is not converted, because wrapper types are idiomatic in JSON. + +```typescript +ExampleMessage.toJSON({ name: 'foo' }) // => { name: 'foo' } +``` + +## JSON Types (Struct Types) + +Protobuf's language and types are not sufficient to represent all possible JSON values, since JSON may contain values whose type is unknown in advance. +For this reason, Protobuf offers several additional types to represent arbitrary JSON values. + +These are called Struct Types, and can be imported in `.proto` files with `import "google/protobuf/struct.proto"`. + +- [google.protobuf.Value](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#Value) ⇆ `any` + - This is the most general type, and can represent any JSON value (i.e. `number | string | boolean | null | array | object`). +- [google.protobuf.ListValue](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#ListValue) ⇆ `any[]` + - To represent a JSON array +- [google.protobuf.Struct](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#Struct) ⇆ `{ [key: string]: any }` + - To represent a JSON object + +`ts-proto` automatically converts back and forth between these Struct Types and their corresponding JSON types. + +Example: + +```protobuf +// Protobuf +syntax = "proto3"; + +import "google/protobuf/struct.proto"; + +message ExampleMessage { + google.protobuf.Value anything = 1; +} +``` + +```typescript +// TypeScript +interface ExampleMessage { + anything: any | undefined; +} +``` + +Encoding a JSON value embedded in a message, converts it to a Struct Type: +```typescript +ExampleMessage.encode({ anything: { "name": "hello" } }) +/* Outputs the following structure, encoded in protobuf binary format: +{ + anything: Value { + structValue = Struct { + fields = [ + MapEntry { + key = "name", + value = Value { + stringValue = "hello" + } + ] + } + } + } +}*/ + +ExampleMessage.encode({ anything: true }) +/* Outputs the following structure encoded in protobuf binary format: +{ + anything: Value { + boolValue = true + } +}*/ +``` + +## Timestamp + +The representation of `google.protobuf.Timestamp` is configurable by the `useDate` flag. + +| Protobuf well-known type | Default/`useDate=true` | `useDate=false` | `useDate=string` | +| --------------------------- | ---------------------- | ------------------------------------ | ---------------- | +| `google.protobuf.Timestamp` | `Date` | `{ seconds: number, nanos: number }` | `string` | -This makes dealing with `string | unset` in your code much nicer, albeit it's unfortunate that, in Protobuf core, this is not as simple as marking a `string name = 1` field as `optional`, i.e. you have to "dirty" your proto files a bit by knowing to use the `StringValue` convention. # Number Types @@ -452,14 +627,6 @@ The protobuf number types map to JavaScript types based on the `forceLong` confi Where (\*) indicates they might throw an error at runtime. -# Timestamps - -The representation of `google.protobuf.Timestamp` is configurable by the `useDate` flag. - -| Protobuf well-known type | Default/`useDate=true` | `useDate=false` | `useDate=string` | -| --------------------------- | ---------------------- | ------------------------------------ | ---------------- | -| `google.protobuf.Timestamp` | `Date` | `{ seconds: number, nanos: number }` | `string` | - # Current Status of Optional Values - Required primitives: use as-is, i.e. `string name = 1`. diff --git a/integration/pbjs.sh b/integration/pbjs.sh index 8ed0a2036..5f93eb0dd 100755 --- a/integration/pbjs.sh +++ b/integration/pbjs.sh @@ -52,3 +52,11 @@ yarn pbts --no-comments -o integration/oneof-properties/pbjs.d.ts integration/on # oneof-unions/ yarn pbjs --force-message --force-number -t static-module -o integration/oneof-unions/pbjs.js integration/oneof-unions/oneof.proto yarn pbts --no-comments -o integration/oneof-unions/pbjs.d.ts integration/oneof-unions/pbjs.js + +# struct/ +yarn pbjs --force-message --force-number -t static-module -o integration/struct/pbjs.js integration/struct/struct.proto +yarn pbts --no-comments -o integration/struct/pbjs.d.ts integration/struct/pbjs.js + +# value/ +yarn pbjs --force-message --force-number -t static-module -o integration/value/pbjs.js integration/value/value.proto +yarn pbts --no-comments -o integration/value/pbjs.d.ts integration/value/pbjs.js diff --git a/integration/struct/google/protobuf/struct.ts b/integration/struct/google/protobuf/struct.ts new file mode 100644 index 000000000..d54b6ca9b --- /dev/null +++ b/integration/struct/google/protobuf/struct.ts @@ -0,0 +1,436 @@ +/* eslint-disable */ +import { util, configure, Writer, Reader } from 'protobufjs/minimal'; +import * as Long from 'long'; + +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'; + default: + return 'UNKNOWN'; + } +} + +/** + * `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: { [key: string]: any | undefined }; +} + +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 that + * 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[]; +} + +const baseStruct: object = {}; + +export const Struct = { + encode(message: Struct, writer: Writer = Writer.create()): 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: Reader | Uint8Array, length?: number): Struct { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseStruct } as Struct; + message.fields = {}; + 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 { + const message = { ...baseStruct } as Struct; + message.fields = {}; + if (object.fields !== undefined && object.fields !== null) { + Object.entries(object.fields).forEach(([key, value]) => { + message.fields[key] = value as any | undefined; + }); + } + return message; + }, + + 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(object: DeepPartial): Struct { + const message = { ...baseStruct } as Struct; + message.fields = {}; + if (object.fields !== undefined && object.fields !== null) { + Object.entries(object.fields).forEach(([key, value]) => { + if (value !== undefined) { + message.fields[key] = value; + } + }); + } + return message; + }, +}; + +const baseStruct_FieldsEntry: object = { key: '' }; + +export const Struct_FieldsEntry = { + encode(message: Struct_FieldsEntry, writer: Writer = Writer.create()): Writer { + if (message.key !== '') { + writer.uint32(10).string(message.key); + } + if (message.value !== undefined) { + Value.encode(wrapAnyValue(message.value), writer.uint32(18).fork()).ldelim(); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): Struct_FieldsEntry { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseStruct_FieldsEntry } as Struct_FieldsEntry; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.key = reader.string(); + break; + case 2: + message.value = unwrapAnyValue(Value.decode(reader, reader.uint32())); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Struct_FieldsEntry { + const message = { ...baseStruct_FieldsEntry } as Struct_FieldsEntry; + message.key = object.key !== undefined && object.key !== null ? String(object.key) : ''; + message.value = object.value; + return message; + }, + + 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(object: DeepPartial): Struct_FieldsEntry { + const message = { ...baseStruct_FieldsEntry } as Struct_FieldsEntry; + message.key = object.key ?? ''; + message.value = object.value ?? undefined; + return message; + }, +}; + +const baseValue: object = {}; + +export const Value = { + encode(message: Value, writer: Writer = Writer.create()): 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(wrapStruct(message.structValue), writer.uint32(42).fork()).ldelim(); + } + if (message.listValue !== undefined) { + ListValue.encode({ values: message.listValue }, writer.uint32(50).fork()).ldelim(); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): Value { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseValue } as Value; + 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 = unwrapStruct(Struct.decode(reader, reader.uint32())); + break; + case 6: + message.listValue = ListValue.decode(reader, reader.uint32()).values; + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Value { + const message = { ...baseValue } as Value; + message.nullValue = + object.nullValue !== undefined && object.nullValue !== null ? nullValueFromJSON(object.nullValue) : undefined; + message.numberValue = + object.numberValue !== undefined && object.numberValue !== null ? Number(object.numberValue) : undefined; + message.stringValue = + object.stringValue !== undefined && object.stringValue !== null ? String(object.stringValue) : undefined; + message.boolValue = + object.boolValue !== undefined && object.boolValue !== null ? Boolean(object.boolValue) : undefined; + message.structValue = typeof object.structValue === 'object' ? object.structValue : undefined; + message.listValue = Array.isArray(object?.listValue) ? [...object.listValue] : undefined; + return message; + }, + + 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(object: DeepPartial): Value { + const message = { ...baseValue } as Value; + 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; + }, +}; + +const baseListValue: object = {}; + +export const ListValue = { + encode(message: ListValue, writer: Writer = Writer.create()): Writer { + for (const v of message.values) { + Value.encode(wrapAnyValue(v!), writer.uint32(10).fork()).ldelim(); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): ListValue { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseListValue } as ListValue; + message.values = []; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.values.push(unwrapAnyValue(Value.decode(reader, reader.uint32()))); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): ListValue { + const message = { ...baseListValue } as ListValue; + message.values = Array.isArray(object?.values) ? [...object.values] : []; + return message; + }, + + toJSON(message: ListValue): unknown { + const obj: any = {}; + if (message.values) { + obj.values = message.values.map((e) => e); + } else { + obj.values = []; + } + return obj; + }, + + fromPartial(object: DeepPartial): ListValue { + const message = { ...baseListValue } as ListValue; + message.values = (object.values ?? []).map((e) => e); + 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; + +// If you get a compile-error about 'Constructor and ... have no overlap', +// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'. +if (util.Long !== Long) { + util.Long = Long as any; + configure(); +} + +function wrapAnyValue(value: any): Value { + if (value === null) { + return { nullValue: 0 } as Value; + } else if (typeof value === 'boolean') { + return { boolValue: value } as Value; + } else if (typeof value === 'number') { + return { numberValue: value } as Value; + } else if (typeof value === 'string') { + return { stringValue: value } as Value; + } else if (Array.isArray(value)) { + return { listValue: value } as Value; + } else if (typeof value === 'object') { + return { structValue: value } as Value; + } else if (typeof value === 'undefined') { + return {} as Value; + } else { + throw new Error('Unsupported any value type: ' + typeof value); + } +} + +function unwrapAnyValue(value: Value): string | number | boolean | Object | null | Array | undefined { + if (value.stringValue !== undefined) { + return value.stringValue; + } else if (value.numberValue !== undefined) { + return value.numberValue; + } else if (value.boolValue !== undefined) { + return value.boolValue; + } else if (value.structValue !== undefined) { + return value.structValue; + } else if (value.listValue !== undefined) { + return value.listValue; + } else if (value.nullValue !== undefined) { + return null; + } +} + +function wrapStruct(object: { [key: string]: any }): Struct { + const struct = Struct.fromPartial({}); + Object.keys(object).forEach((key) => { + struct.fields[key] = object[key]; + }); + return struct; +} + +function unwrapStruct(struct: Struct): { [key: string]: any } { + const object: { [key: string]: any } = {}; + Object.keys(struct.fields).forEach((key) => { + object[key] = struct.fields[key]; + }); + return object; +} diff --git a/integration/struct/struct-test.ts b/integration/struct/struct-test.ts new file mode 100644 index 000000000..27b701754 --- /dev/null +++ b/integration/struct/struct-test.ts @@ -0,0 +1,63 @@ +import { Reader } from 'protobufjs'; +import { StructMessage } from './struct'; + +import { StructMessage as PbStructMessage } from './pbjs'; + +let data = { + value: { + name: 'john', + pet: null, + posts: [{ id: 1, title: 'hello world', public: true }], + }, +}; + +describe('struct', () => { + it('can encode objects', () => { + const s1 = StructMessage.fromJSON(data); + const s2 = PbStructMessage.decode(Reader.create(StructMessage.encode(s1).finish())); + + expect(s2).toMatchInlineSnapshot(` + Object { + "value": Object { + "fields": Object { + "name": Object { + "stringValue": "john", + }, + "pet": Object { + "nullValue": "NULL_VALUE", + }, + "posts": Object { + "listValue": Object { + "values": Array [ + Object { + "structValue": Object { + "fields": Object { + "id": Object { + "numberValue": 1, + }, + "public": Object { + "boolValue": true, + }, + "title": Object { + "stringValue": "hello world", + }, + }, + }, + }, + ], + }, + }, + }, + }, + } + `); + }); + + it('can decode objects', () => { + let message = StructMessage.fromJSON(data); + let encodedValue = StructMessage.encode(message).finish(); + const decodedValue = StructMessage.decode(Reader.create(encodedValue)); + + expect(StructMessage.toJSON(decodedValue)).toEqual(data); + }); +}); diff --git a/integration/struct/struct.bin b/integration/struct/struct.bin new file mode 100644 index 0000000000000000000000000000000000000000..af5a5b0a396832343032246911f0234c0605e685 GIT binary patch literal 4734 zcmbtYTW=f372XRe&?8x~nTu8WHldUF0-6fc6iDr+!IE52Yf~h%OG;G(H(HV-X=`$q z*j-9dUJCSAv_PNwP@sKmfxZ`g?XT#s=u?sJ%wBYh-G}l)nw>f4n{)Z*oY`e7Q5-JD zaeW>JaZo9h^c$;dN>%ypzg2eUFbEE(VqGqc76Z5Bm4a+rFJa(rG zv8G{edEGYcffIwF_Dpvry5Rvj*ihYgb6XgeN z#b3-uB23k3e-$Fj$!so!IC=hIDx{O)B$Ui1LiZUP1wm#;0U{;Zisll5pRqJCDM}F{ z%Nw_@SenE!g-0F}ZFo8LqBzxElDb#D>_9}y)7B&}9~w)n@Y?ey)#vP5E>W(trDKX} zmeDr_J(YTLn_bI;SYBnUJM476*)cjp^M=y+4SRPS%r5)VSWcYj6BGODFV|Df90pU@ zKdcAg;TaR-)47P&U;Dv{pR#g3`j4vqQPbP~#$U91ZOPoagaS9~yJ9+hNzwySeDV9c zY^hwhr~FD~|5{<{N?E_BlsEpd!llAEnDWNr;6Q{Cf65c6hY_E+vCBO_7UB2^1_^!; zgflm0ycx_-L+|h?<{O`W`gy9rEq`3++?YoNuBYdIG#t(*4HQESWJUC zHp+s5xFlY}h^Mfej96!k+hT$Qh2Cfpdx6hgf5I1$fH^!07U5XPnUUwa;iL&iJu6!5}_zWBeiSrqkdAJm%xTpLnDc zkrHr3{0t`1@5v>@A|4!M7L9|6;Il;(!}HjMrG)B^f@1;1G-8bVLF|o14U1mHiG+l3 zen)tE(HsO~JaxU92ocF2HUgWSi$!Jxe4H#0s@x8CX2}0#2ctX?>LeI1X2OqUh$$uO zh<|{Ukb@~A^xSE57HXLwEHZvRx4BbYfdoR6fPHr+C@={b6K?sy&emkyy*MI|P)vy? z0oXfpPx(j?QxL>}3x9%H0EJ}4EQrAX8Iu_7hIJq(KfnS5F0v5?2k{BfgbW3Pcr+Jd zVi2@EB34M;@e>wB5eQNTEe2bb!=2vdVArrsj&a}a?N}|d#T$EAGb7ef2)x@HFy67YtpVf)y_($isvhrc@@>;@ZUM$SRw2Xlv zw+05ng>^X47&?{=oYftewms|*tX}sqvb>APz;y$ zQxF9k6C+_4Qbgl()J2eyPi}2;qqPH&RwfMwI95s*C^^ICRvL5Azr<9ntaD|JMy0Y| zRnSJ6R{A&@CX)}8`*QMrS*Kw#`H`|ICzs1Q4U@@_m71JX%Q_8{$xqTrz|%09e5h2V zBrEGQOeP;GAIiy($~q1IrZcUelnWcmPt?kvbiSW7+W;#9P)S)LlxijE^P@h5xqfWkX z4+Yv&wmJHV?477n(PBOiLTX2&QyAi-jG|fO#>uK$=bI=!;#+q%pQ3GeKI73i^ybpA z93GwIwcszD+#Qa3mIGk}7oI{j82wt{^2-NQys1l}KEpfN1atAUnxha-1no6~ejFSM zs?LZ2wOOjeD5Z!&>H{@lIh&azOCzLSI3WKeXp+ssL5511)HH^lUQj@QjnXRnDJv8d zO)2Z2X}nV7L-YU?ZrDQ^fZ=b<0AnkxNUBSu`(E}0*w5a-ojvJzx_6)bQv*%Za^VYQ zTdn+E+Afq*<8+|5uf3Z+5zq&>*i(sGDC-tX zFV6~P)u*-f@pp^?>93Y= zWNX;v)y?EYK@v%SRk=M{l6~aI-~C(7J^`fPX5RWaruuVs-I0xZTSO5$Z24*Sm4rTW zy!q2X`kCa#l-`IHWl5#0173&~rA$``9nNdYC#Yuf=1?GktRWRj@3Un@NT#}|;p<4= YVv1T)eUeNUe{?TfgOkM%tC^GE0}&J%YybcN literal 0 HcmV?d00001 diff --git a/integration/struct/struct.proto b/integration/struct/struct.proto new file mode 100644 index 000000000..6aa650d40 --- /dev/null +++ b/integration/struct/struct.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +import "google/protobuf/struct.proto"; + +message StructMessage { + google.protobuf.Struct value = 1; +} diff --git a/integration/struct/struct.ts b/integration/struct/struct.ts new file mode 100644 index 000000000..5edbeb70a --- /dev/null +++ b/integration/struct/struct.ts @@ -0,0 +1,91 @@ +/* eslint-disable */ +import { util, configure, Writer, Reader } from 'protobufjs/minimal'; +import * as Long from 'long'; +import { Struct } from './google/protobuf/struct'; + +export const protobufPackage = ''; + +export interface StructMessage { + value: { [key: string]: any } | undefined; +} + +const baseStructMessage: object = {}; + +export const StructMessage = { + encode(message: StructMessage, writer: Writer = Writer.create()): Writer { + if (message.value !== undefined) { + Struct.encode(wrapStruct(message.value), writer.uint32(10).fork()).ldelim(); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): StructMessage { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseStructMessage } as StructMessage; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.value = unwrapStruct(Struct.decode(reader, reader.uint32())); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): StructMessage { + const message = { ...baseStructMessage } as StructMessage; + message.value = typeof object.value === 'object' ? object.value : undefined; + return message; + }, + + toJSON(message: StructMessage): unknown { + const obj: any = {}; + message.value !== undefined && (obj.value = message.value); + return obj; + }, + + fromPartial(object: DeepPartial): StructMessage { + const message = { ...baseStructMessage } as StructMessage; + message.value = object.value ?? undefined; + 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; + +// If you get a compile-error about 'Constructor and ... have no overlap', +// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'. +if (util.Long !== Long) { + util.Long = Long as any; + configure(); +} + +function wrapStruct(object: { [key: string]: any }): Struct { + const struct = Struct.fromPartial({}); + Object.keys(object).forEach((key) => { + struct.fields[key] = object[key]; + }); + return struct; +} + +function unwrapStruct(struct: Struct): { [key: string]: any } { + const object: { [key: string]: any } = {}; + Object.keys(struct.fields).forEach((key) => { + object[key] = struct.fields[key]; + }); + return object; +} diff --git a/integration/value/google/protobuf/struct.ts b/integration/value/google/protobuf/struct.ts new file mode 100644 index 000000000..d54b6ca9b --- /dev/null +++ b/integration/value/google/protobuf/struct.ts @@ -0,0 +1,436 @@ +/* eslint-disable */ +import { util, configure, Writer, Reader } from 'protobufjs/minimal'; +import * as Long from 'long'; + +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'; + default: + return 'UNKNOWN'; + } +} + +/** + * `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: { [key: string]: any | undefined }; +} + +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 that + * 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[]; +} + +const baseStruct: object = {}; + +export const Struct = { + encode(message: Struct, writer: Writer = Writer.create()): 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: Reader | Uint8Array, length?: number): Struct { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseStruct } as Struct; + message.fields = {}; + 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 { + const message = { ...baseStruct } as Struct; + message.fields = {}; + if (object.fields !== undefined && object.fields !== null) { + Object.entries(object.fields).forEach(([key, value]) => { + message.fields[key] = value as any | undefined; + }); + } + return message; + }, + + 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(object: DeepPartial): Struct { + const message = { ...baseStruct } as Struct; + message.fields = {}; + if (object.fields !== undefined && object.fields !== null) { + Object.entries(object.fields).forEach(([key, value]) => { + if (value !== undefined) { + message.fields[key] = value; + } + }); + } + return message; + }, +}; + +const baseStruct_FieldsEntry: object = { key: '' }; + +export const Struct_FieldsEntry = { + encode(message: Struct_FieldsEntry, writer: Writer = Writer.create()): Writer { + if (message.key !== '') { + writer.uint32(10).string(message.key); + } + if (message.value !== undefined) { + Value.encode(wrapAnyValue(message.value), writer.uint32(18).fork()).ldelim(); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): Struct_FieldsEntry { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseStruct_FieldsEntry } as Struct_FieldsEntry; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.key = reader.string(); + break; + case 2: + message.value = unwrapAnyValue(Value.decode(reader, reader.uint32())); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Struct_FieldsEntry { + const message = { ...baseStruct_FieldsEntry } as Struct_FieldsEntry; + message.key = object.key !== undefined && object.key !== null ? String(object.key) : ''; + message.value = object.value; + return message; + }, + + 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(object: DeepPartial): Struct_FieldsEntry { + const message = { ...baseStruct_FieldsEntry } as Struct_FieldsEntry; + message.key = object.key ?? ''; + message.value = object.value ?? undefined; + return message; + }, +}; + +const baseValue: object = {}; + +export const Value = { + encode(message: Value, writer: Writer = Writer.create()): 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(wrapStruct(message.structValue), writer.uint32(42).fork()).ldelim(); + } + if (message.listValue !== undefined) { + ListValue.encode({ values: message.listValue }, writer.uint32(50).fork()).ldelim(); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): Value { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseValue } as Value; + 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 = unwrapStruct(Struct.decode(reader, reader.uint32())); + break; + case 6: + message.listValue = ListValue.decode(reader, reader.uint32()).values; + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Value { + const message = { ...baseValue } as Value; + message.nullValue = + object.nullValue !== undefined && object.nullValue !== null ? nullValueFromJSON(object.nullValue) : undefined; + message.numberValue = + object.numberValue !== undefined && object.numberValue !== null ? Number(object.numberValue) : undefined; + message.stringValue = + object.stringValue !== undefined && object.stringValue !== null ? String(object.stringValue) : undefined; + message.boolValue = + object.boolValue !== undefined && object.boolValue !== null ? Boolean(object.boolValue) : undefined; + message.structValue = typeof object.structValue === 'object' ? object.structValue : undefined; + message.listValue = Array.isArray(object?.listValue) ? [...object.listValue] : undefined; + return message; + }, + + 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(object: DeepPartial): Value { + const message = { ...baseValue } as Value; + 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; + }, +}; + +const baseListValue: object = {}; + +export const ListValue = { + encode(message: ListValue, writer: Writer = Writer.create()): Writer { + for (const v of message.values) { + Value.encode(wrapAnyValue(v!), writer.uint32(10).fork()).ldelim(); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): ListValue { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseListValue } as ListValue; + message.values = []; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.values.push(unwrapAnyValue(Value.decode(reader, reader.uint32()))); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): ListValue { + const message = { ...baseListValue } as ListValue; + message.values = Array.isArray(object?.values) ? [...object.values] : []; + return message; + }, + + toJSON(message: ListValue): unknown { + const obj: any = {}; + if (message.values) { + obj.values = message.values.map((e) => e); + } else { + obj.values = []; + } + return obj; + }, + + fromPartial(object: DeepPartial): ListValue { + const message = { ...baseListValue } as ListValue; + message.values = (object.values ?? []).map((e) => e); + 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; + +// If you get a compile-error about 'Constructor and ... have no overlap', +// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'. +if (util.Long !== Long) { + util.Long = Long as any; + configure(); +} + +function wrapAnyValue(value: any): Value { + if (value === null) { + return { nullValue: 0 } as Value; + } else if (typeof value === 'boolean') { + return { boolValue: value } as Value; + } else if (typeof value === 'number') { + return { numberValue: value } as Value; + } else if (typeof value === 'string') { + return { stringValue: value } as Value; + } else if (Array.isArray(value)) { + return { listValue: value } as Value; + } else if (typeof value === 'object') { + return { structValue: value } as Value; + } else if (typeof value === 'undefined') { + return {} as Value; + } else { + throw new Error('Unsupported any value type: ' + typeof value); + } +} + +function unwrapAnyValue(value: Value): string | number | boolean | Object | null | Array | undefined { + if (value.stringValue !== undefined) { + return value.stringValue; + } else if (value.numberValue !== undefined) { + return value.numberValue; + } else if (value.boolValue !== undefined) { + return value.boolValue; + } else if (value.structValue !== undefined) { + return value.structValue; + } else if (value.listValue !== undefined) { + return value.listValue; + } else if (value.nullValue !== undefined) { + return null; + } +} + +function wrapStruct(object: { [key: string]: any }): Struct { + const struct = Struct.fromPartial({}); + Object.keys(object).forEach((key) => { + struct.fields[key] = object[key]; + }); + return struct; +} + +function unwrapStruct(struct: Struct): { [key: string]: any } { + const object: { [key: string]: any } = {}; + Object.keys(struct.fields).forEach((key) => { + object[key] = struct.fields[key]; + }); + return object; +} diff --git a/integration/value/google/protobuf/wrappers.ts b/integration/value/google/protobuf/wrappers.ts new file mode 100644 index 000000000..216285744 --- /dev/null +++ b/integration/value/google/protobuf/wrappers.ts @@ -0,0 +1,578 @@ +/* eslint-disable */ +import { util, configure, Writer, Reader } from 'protobufjs/minimal'; +import * as Long from 'long'; + +export const protobufPackage = 'google.protobuf'; + +/** + * Wrapper message for `double`. + * + * The JSON representation for `DoubleValue` is JSON number. + */ +export interface DoubleValue { + /** The double value. */ + value: number; +} + +/** + * Wrapper message for `float`. + * + * The JSON representation for `FloatValue` is JSON number. + */ +export interface FloatValue { + /** The float value. */ + value: number; +} + +/** + * Wrapper message for `int64`. + * + * The JSON representation for `Int64Value` is JSON string. + */ +export interface Int64Value { + /** The int64 value. */ + value: number; +} + +/** + * Wrapper message for `uint64`. + * + * The JSON representation for `UInt64Value` is JSON string. + */ +export interface UInt64Value { + /** The uint64 value. */ + value: number; +} + +/** + * Wrapper message for `int32`. + * + * The JSON representation for `Int32Value` is JSON number. + */ +export interface Int32Value { + /** The int32 value. */ + value: number; +} + +/** + * Wrapper message for `uint32`. + * + * The JSON representation for `UInt32Value` is JSON number. + */ +export interface UInt32Value { + /** The uint32 value. */ + value: number; +} + +/** + * Wrapper message for `bool`. + * + * The JSON representation for `BoolValue` is JSON `true` and `false`. + */ +export interface BoolValue { + /** The bool value. */ + value: boolean; +} + +/** + * Wrapper message for `string`. + * + * The JSON representation for `StringValue` is JSON string. + */ +export interface StringValue { + /** The string value. */ + value: string; +} + +/** + * Wrapper message for `bytes`. + * + * The JSON representation for `BytesValue` is JSON string. + */ +export interface BytesValue { + /** The bytes value. */ + value: Uint8Array; +} + +const baseDoubleValue: object = { value: 0 }; + +export const DoubleValue = { + encode(message: DoubleValue, writer: Writer = Writer.create()): Writer { + if (message.value !== 0) { + writer.uint32(9).double(message.value); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): DoubleValue { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseDoubleValue } as DoubleValue; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.value = reader.double(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): DoubleValue { + const message = { ...baseDoubleValue } as DoubleValue; + message.value = object.value !== undefined && object.value !== null ? Number(object.value) : 0; + return message; + }, + + toJSON(message: DoubleValue): unknown { + const obj: any = {}; + message.value !== undefined && (obj.value = message.value); + return obj; + }, + + fromPartial(object: DeepPartial): DoubleValue { + const message = { ...baseDoubleValue } as DoubleValue; + message.value = object.value ?? 0; + return message; + }, +}; + +const baseFloatValue: object = { value: 0 }; + +export const FloatValue = { + encode(message: FloatValue, writer: Writer = Writer.create()): Writer { + if (message.value !== 0) { + writer.uint32(13).float(message.value); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): FloatValue { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseFloatValue } as FloatValue; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.value = reader.float(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): FloatValue { + const message = { ...baseFloatValue } as FloatValue; + message.value = object.value !== undefined && object.value !== null ? Number(object.value) : 0; + return message; + }, + + toJSON(message: FloatValue): unknown { + const obj: any = {}; + message.value !== undefined && (obj.value = message.value); + return obj; + }, + + fromPartial(object: DeepPartial): FloatValue { + const message = { ...baseFloatValue } as FloatValue; + message.value = object.value ?? 0; + return message; + }, +}; + +const baseInt64Value: object = { value: 0 }; + +export const Int64Value = { + encode(message: Int64Value, writer: Writer = Writer.create()): Writer { + if (message.value !== 0) { + writer.uint32(8).int64(message.value); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): Int64Value { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseInt64Value } as Int64Value; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.value = longToNumber(reader.int64() as Long); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Int64Value { + const message = { ...baseInt64Value } as Int64Value; + message.value = object.value !== undefined && object.value !== null ? Number(object.value) : 0; + return message; + }, + + toJSON(message: Int64Value): unknown { + const obj: any = {}; + message.value !== undefined && (obj.value = message.value); + return obj; + }, + + fromPartial(object: DeepPartial): Int64Value { + const message = { ...baseInt64Value } as Int64Value; + message.value = object.value ?? 0; + return message; + }, +}; + +const baseUInt64Value: object = { value: 0 }; + +export const UInt64Value = { + encode(message: UInt64Value, writer: Writer = Writer.create()): Writer { + if (message.value !== 0) { + writer.uint32(8).uint64(message.value); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): UInt64Value { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseUInt64Value } as UInt64Value; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.value = longToNumber(reader.uint64() as Long); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): UInt64Value { + const message = { ...baseUInt64Value } as UInt64Value; + message.value = object.value !== undefined && object.value !== null ? Number(object.value) : 0; + return message; + }, + + toJSON(message: UInt64Value): unknown { + const obj: any = {}; + message.value !== undefined && (obj.value = message.value); + return obj; + }, + + fromPartial(object: DeepPartial): UInt64Value { + const message = { ...baseUInt64Value } as UInt64Value; + message.value = object.value ?? 0; + return message; + }, +}; + +const baseInt32Value: object = { value: 0 }; + +export const Int32Value = { + encode(message: Int32Value, writer: Writer = Writer.create()): Writer { + if (message.value !== 0) { + writer.uint32(8).int32(message.value); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): Int32Value { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseInt32Value } as Int32Value; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.value = reader.int32(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Int32Value { + const message = { ...baseInt32Value } as Int32Value; + message.value = object.value !== undefined && object.value !== null ? Number(object.value) : 0; + return message; + }, + + toJSON(message: Int32Value): unknown { + const obj: any = {}; + message.value !== undefined && (obj.value = message.value); + return obj; + }, + + fromPartial(object: DeepPartial): Int32Value { + const message = { ...baseInt32Value } as Int32Value; + message.value = object.value ?? 0; + return message; + }, +}; + +const baseUInt32Value: object = { value: 0 }; + +export const UInt32Value = { + encode(message: UInt32Value, writer: Writer = Writer.create()): Writer { + if (message.value !== 0) { + writer.uint32(8).uint32(message.value); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): UInt32Value { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseUInt32Value } as UInt32Value; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.value = reader.uint32(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): UInt32Value { + const message = { ...baseUInt32Value } as UInt32Value; + message.value = object.value !== undefined && object.value !== null ? Number(object.value) : 0; + return message; + }, + + toJSON(message: UInt32Value): unknown { + const obj: any = {}; + message.value !== undefined && (obj.value = message.value); + return obj; + }, + + fromPartial(object: DeepPartial): UInt32Value { + const message = { ...baseUInt32Value } as UInt32Value; + message.value = object.value ?? 0; + return message; + }, +}; + +const baseBoolValue: object = { value: false }; + +export const BoolValue = { + encode(message: BoolValue, writer: Writer = Writer.create()): Writer { + if (message.value === true) { + writer.uint32(8).bool(message.value); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): BoolValue { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseBoolValue } as BoolValue; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.value = reader.bool(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): BoolValue { + const message = { ...baseBoolValue } as BoolValue; + message.value = object.value !== undefined && object.value !== null ? Boolean(object.value) : false; + return message; + }, + + toJSON(message: BoolValue): unknown { + const obj: any = {}; + message.value !== undefined && (obj.value = message.value); + return obj; + }, + + fromPartial(object: DeepPartial): BoolValue { + const message = { ...baseBoolValue } as BoolValue; + message.value = object.value ?? false; + return message; + }, +}; + +const baseStringValue: object = { value: '' }; + +export const StringValue = { + encode(message: StringValue, writer: Writer = Writer.create()): Writer { + if (message.value !== '') { + writer.uint32(10).string(message.value); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): StringValue { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseStringValue } as StringValue; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.value = reader.string(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): StringValue { + const message = { ...baseStringValue } as StringValue; + message.value = object.value !== undefined && object.value !== null ? String(object.value) : ''; + return message; + }, + + toJSON(message: StringValue): unknown { + const obj: any = {}; + message.value !== undefined && (obj.value = message.value); + return obj; + }, + + fromPartial(object: DeepPartial): StringValue { + const message = { ...baseStringValue } as StringValue; + message.value = object.value ?? ''; + return message; + }, +}; + +const baseBytesValue: object = {}; + +export const BytesValue = { + encode(message: BytesValue, writer: Writer = Writer.create()): Writer { + if (message.value.length !== 0) { + writer.uint32(10).bytes(message.value); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): BytesValue { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseBytesValue } as BytesValue; + message.value = new Uint8Array(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.value = reader.bytes(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): BytesValue { + const message = { ...baseBytesValue } as BytesValue; + message.value = + object.value !== undefined && object.value !== null ? bytesFromBase64(object.value) : new Uint8Array(); + return message; + }, + + toJSON(message: BytesValue): unknown { + const obj: any = {}; + message.value !== undefined && + (obj.value = base64FromBytes(message.value !== undefined ? message.value : new Uint8Array())); + return obj; + }, + + fromPartial(object: DeepPartial): BytesValue { + const message = { ...baseBytesValue } as BytesValue; + message.value = object.value ?? new Uint8Array(); + return message; + }, +}; + +declare var self: any | undefined; +declare var window: any | undefined; +declare var global: any | undefined; +var globalThis: any = (() => { + if (typeof globalThis !== 'undefined') return globalThis; + if (typeof self !== 'undefined') return self; + if (typeof window !== 'undefined') return window; + if (typeof global !== 'undefined') return global; + throw 'Unable to locate global object'; +})(); + +const atob: (b64: string) => string = + globalThis.atob || ((b64) => globalThis.Buffer.from(b64, 'base64').toString('binary')); +function bytesFromBase64(b64: string): Uint8Array { + const bin = atob(b64); + const arr = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; ++i) { + arr[i] = bin.charCodeAt(i); + } + return arr; +} + +const btoa: (bin: string) => string = + globalThis.btoa || ((bin) => globalThis.Buffer.from(bin, 'binary').toString('base64')); +function base64FromBytes(arr: Uint8Array): string { + const bin: string[] = []; + for (const byte of arr) { + bin.push(String.fromCharCode(byte)); + } + return btoa(bin.join('')); +} + +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; + +function longToNumber(long: Long): number { + if (long.gt(Number.MAX_SAFE_INTEGER)) { + throw new globalThis.Error('Value is larger than Number.MAX_SAFE_INTEGER'); + } + return long.toNumber(); +} + +// If you get a compile-error about 'Constructor and ... have no overlap', +// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'. +if (util.Long !== Long) { + util.Long = Long as any; + configure(); +} diff --git a/integration/value/value-test.ts b/integration/value/value-test.ts new file mode 100644 index 000000000..deb4a73f3 --- /dev/null +++ b/integration/value/value-test.ts @@ -0,0 +1,64 @@ +import { Reader } from 'protobufjs'; +import { ValueMessage } from './value'; + +import { ValueMessage as PbValueMessage } from './pbjs'; + +describe('values', () => { + it('json value', () => { + const s1 = ValueMessage.fromJSON({ + value: 'Hello', + anyList: [1, 'foo', true], + repeatedAny: [2, 'bar', false], + }); + + const s2 = PbValueMessage.decode(Reader.create(ValueMessage.encode(s1).finish())); + + expect(s2).toMatchInlineSnapshot(` + Object { + "anyList": Object { + "values": Array [ + Object { + "numberValue": 1, + }, + Object { + "stringValue": "foo", + }, + Object { + "boolValue": true, + }, + ], + }, + "repeatedAny": Array [ + Object { + "numberValue": 2, + }, + Object { + "stringValue": "bar", + }, + Object { + "boolValue": false, + }, + ], + "value": Object { + "stringValue": "Hello", + }, + } + `); + }); + + it('decodes lists of any types correctly', () => { + const s1 = ValueMessage.fromJSON({ + anyList: [1, 'foo', true], + repeatedAny: [2, 'bar', false], + }); + expect(s1).toEqual({ anyList: [1, 'foo', true], repeatedAny: [2, 'bar', false] }); + }); + + it('toJson', () => { + const s1 = ValueMessage.fromPartial({ + anyList: [1], + repeatedAny: [2], + }); + expect(s1).toEqual({ anyList: [1], repeatedAny: [2] }); + }); +}); diff --git a/integration/value/value.bin b/integration/value/value.bin new file mode 100644 index 0000000000000000000000000000000000000000..4f4868c81ba4bc4633509427088832fae5610f2f GIT binary patch literal 9573 zcmeHNO>Y~=8D1_)i5kg{y>=6U{+h^1EE}}s$ZirlEsQ0#lGdh3nIBS>z_nK7N@7gy z%DYP|%%wnk?GI>yo_Z+IqNf(0$B|&9*QP8icE%|3`a%kDOz|t?> zJ{YBDCu%e_3M?xyPwQBlTzK~l=IbO;O%grQKTFO0NoQHH2xQFbb-OPl17do9A^!%0 z3P=R~idLH-db#-~oAP@5+isw?)A=coOv=@S2zu&xyNW1Hz==q%N}?N#ZTo%KV=VKs z|A3NR()tXROjJmtU-#<)(E(EB=gKYWe#|WhqWd zVb;hK*HT~1E1%i*yNnE z?Y8F_-;1Jy@X`Ey+kNeJ{R12nD+79wQUb(XASV#>jPaV=hJk|4b}#Dq9(TMp?}aYl z@X+rCEmy2;cRVNP3tVA=A9kWW?g!%keh)bJ{dQ-k({hMIfjfcAaq@mAirhBG9e>?v zV-W2*5&lEcb-Vr{{Ft|Vuic?dA#DO5H+lp}^zY6QU?KN+Vni*!?ehIz7(w#L0aD`X zZ2PZWa8w#&-1DPO%PrtzC*g~H;Bvec$nTP&;dv6C2k z&b~{Ek$R~8Mgf7?_ak_K&`AXE z20Az=-@yk4zOY8CIl27#KQx(MIMA*9dfLIe8-bs6ozn+Ik-h*#jf*ub*Zso z)+~cy)=dTD8&&*qBATY&SN=flGFyLWeC?YmA$G&8#)-QnPH<_*%2JR;^o{BwMuW zrLt+ST8ncyA7^rF-KsQreZ?%7M+IfPx>2!egnn2|UbKMHES4=g3P*2(Y0WA%NT9)} z1myu>xxjdR%_`X#K_3>>VAeJZifP??)&vzk@Scjo2Qd=e|qz>mOHHOJ> zAbbUe9>~cn)^gciwkjoy@KlM-hF!OA!(cYBEfY^t^M;8-;V3wmJQBEIA{r05E|d&= za(juJi|b&r7*oRo>b7zhY^pa)D@x~Z{~Sx{Mw)BWG|Cz284Y2S>A4T(P%hroZivMj zMw*6l@dIs1EKV9}8p_2FwSrho8EG2I#gEh?*wauh-qL0SB{R}Al#8?4`(p6}BTd6U zrC1By-_-T{Xr%pj5I1$YcZ$P;nDKo3iZ=a~Wm9wM3E zF@Ct$Y3<>iIiUBY-N!qH0MqUEIYsC;mpKD22*oKpVHC-G&aR8_6d#UwB4Q`rsjzo& z;0F|rw)+6$;Tc7+NWh6#b&fCL>2bey_7A!U8y%1FuoZL;1h9lXBFRgaKdC3;aJ**; zBOKsBQf&3NzjBe$@&-NL6s7P!Lpl)z6LR&M;~{Ljbk;WZNB*u$uQN12VV2%uJf+Zq zl7WJ-SdAfxrx8?7>Tv!etcf;O2LTl{3L3+|bVh>%Eac9!hb)uPbj?UV(0RVVn}`8; zxPgZ(0N^KDfUzl-rLAMM_i{V~+Xp|m63^0@UBAKpro&BAM&`%bYAXL1oo}sE=6A$P zPB=+9M9V=0cDamT2=Yjwk#XSvls(gkav5)zMwIiq4kaW|ORE=zWOM0*(tQdPuN;;n z2eD6?DtIA8LUOT6%5%R)lp-G@W4K5sJP|p`qzqJu!Eke&whezrGJvg)*N;EIpW2im zx#S+)4FVrY2HvUD45zdc+f4w$CNf(J`u%b49GijJQaRkb$9g%PzbyDsj-3DpJ=q5| z*zq_w#ct6KGPIewmd_*08oHpQO(}1@O_?`?P8BTQ3SqE*OrlBG2Ho?@B;(^CG5Vu~lvdXuTN@e!svm|6)_9Zao+sSc(V znBEacFQdiOncS8&Z3X&X@aDA%DkFSehs zhn~TGu@kts6=^$xn*n1Ra3y0PcnVt`VT_yZ!kyajB}y!o1>>iQ#j;@hG_hC~jGw|{ zzfUne3$Lkb>#6*2QhX~ZskX?|af@(=x^9Gmz{S;a1NltpiJv7F;}P#P z?|CYDJKfVqj~B1h7V+Ma?w%#Tt7p%%Jj-OoE7jC5=Ff53Hf%5Y2Fj*BMHZqN{HOWO z1@?2uo6(KTmnh8gulZI|ekySf1LpuG3#t&mhs#3`hSIJGN6Bppf#-BdMmY;foFH)e zQjiR}<(IjO>=9f>r(5`3znZ^GE+dVZ9CyMiNC3!xuowRJTu){&`t!*P@i(0E{Gz;3 zpoGz%=dWy!i8ylqn}6P9*JTAfD0`Ix!joDsH`(N(xXI#8GA_rB66$lLR6}HDhOwot z@2FP62#r2OV|&pj502rJj%jdM^CY57OoYQgbARC&M%kD~F*=s$Tug*SrkHd`MnLCc z67Z>uuyV{{6EP7075jMKkD-}TG{s+}j&D$$+mH?zuBjHq$I?J3-YLwV+@gr>_Pv@E zf4@OZidpTO6#Lf&(WIDt+oX8gr1)Q(C2yM)`<=HUqey6| z3vYV;Etzmq|C*}agRawZ(X~88DUCiO8lA4J+^8c8uDwpUhe9$sbkS#|B73-O8yty- zrD!+Eyo{Km7m8|q$LV#WB=w|}5Q4G6@EVmjx6hu@KP0v zN)`Ej3+-YmQe)QC?M1apWo%3>doyxL?xS+t=?1RT?jMDUOclK%B;a?Fo>gJcv!bDF zCEXqLtY|0~=d_i$XGKG~I6vrF(NHel)8+&vdR8=)iyseqRy3503xl2&4R>YB>Qn7e zD*t8dwlJA+QQ<>dvUsH;;X)IZv93#%GE1$U{a100jhirkn>e?0%-42Q#SunR1?tk=f9`I8FWc z7rvAFRe?2tKjQn;+t^U*&k+30VfEi%7(ovLpx;#L#|)b{mHIIQ`c0+&tdV)4ZJ(#3@p}bJ)&k}xTMEyqu00jhal=?BlZbzvfGYH^7{XYB$HQ289RVv>){_avg z>TK;NrRzZXTWGVRsY=0WYX^T9bcqW!4aWdw7tM$3xW!{6NC#b%i{cj3VIy|HcZku^ z$HcY840}7{qOU@GAw~y%6}krFM#k5I6YU^x;@N@>71#q&My~NVAo$7-;{^XzVh2e# zA^<2Lz$;}3%&_~FvIAxi;FY=tIU{q3TKJ?lbeoF$v+)mV{}bTn0N_wvgB-!{C)Yq6 zF@hchK;Ku_05fdvt80K6(DyG~OgcK>rrF4kZBG4tP5zAL&&(fFY)UjSR%I+4{49pL zJ!kza#(2QS(f6i4nEDybDZdY?#iYh(Y%63rs*Fi=`(1**fsDF&VueO2rxIU{srLb*WkN@sLVRQT{e%vGeLPDMG@sDn)ZAgrA`p4>%j%Hp? cHS;uG2b0_+kH3LQ?)^eM1C!kRz1Z~s1>^ZEQvd(} literal 0 HcmV?d00001 diff --git a/integration/value/value.proto b/integration/value/value.proto new file mode 100644 index 000000000..0b4710624 --- /dev/null +++ b/integration/value/value.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +import "google/protobuf/struct.proto"; +import "google/protobuf/wrappers.proto"; + +message ValueMessage { + google.protobuf.Value value = 1; + google.protobuf.ListValue anyList = 2; + repeated google.protobuf.Value repeatedAny = 3; +} diff --git a/integration/value/value.ts b/integration/value/value.ts new file mode 100644 index 000000000..7e15efe5b --- /dev/null +++ b/integration/value/value.ts @@ -0,0 +1,136 @@ +/* eslint-disable */ +import { util, configure, Writer, Reader } from 'protobufjs/minimal'; +import * as Long from 'long'; +import { Value, ListValue } from './google/protobuf/struct'; + +export const protobufPackage = ''; + +export interface ValueMessage { + value: any | undefined; + anyList: Array | undefined; + repeatedAny: any[]; +} + +const baseValueMessage: object = {}; + +export const ValueMessage = { + encode(message: ValueMessage, writer: Writer = Writer.create()): Writer { + if (message.value !== undefined) { + Value.encode(wrapAnyValue(message.value), writer.uint32(10).fork()).ldelim(); + } + if (message.anyList !== undefined) { + ListValue.encode({ values: message.anyList }, writer.uint32(18).fork()).ldelim(); + } + for (const v of message.repeatedAny) { + Value.encode(wrapAnyValue(v!), writer.uint32(26).fork()).ldelim(); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): ValueMessage { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseValueMessage } as ValueMessage; + message.repeatedAny = []; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.value = unwrapAnyValue(Value.decode(reader, reader.uint32())); + break; + case 2: + message.anyList = ListValue.decode(reader, reader.uint32()).values; + break; + case 3: + message.repeatedAny.push(unwrapAnyValue(Value.decode(reader, reader.uint32()))); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): ValueMessage { + const message = { ...baseValueMessage } as ValueMessage; + message.value = object.value; + message.anyList = Array.isArray(object?.anyList) ? [...object.anyList] : undefined; + message.repeatedAny = Array.isArray(object?.repeatedAny) ? [...object.repeatedAny] : []; + return message; + }, + + toJSON(message: ValueMessage): unknown { + const obj: any = {}; + message.value !== undefined && (obj.value = message.value); + message.anyList !== undefined && (obj.anyList = message.anyList); + if (message.repeatedAny) { + obj.repeatedAny = message.repeatedAny.map((e) => e); + } else { + obj.repeatedAny = []; + } + return obj; + }, + + fromPartial(object: DeepPartial): ValueMessage { + const message = { ...baseValueMessage } as ValueMessage; + message.value = object.value ?? undefined; + message.anyList = object.anyList ?? undefined; + message.repeatedAny = (object.repeatedAny ?? []).map((e) => e); + 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; + +// If you get a compile-error about 'Constructor and ... have no overlap', +// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'. +if (util.Long !== Long) { + util.Long = Long as any; + configure(); +} + +function wrapAnyValue(value: any): Value { + if (value === null) { + return { nullValue: 0 } as Value; + } else if (typeof value === 'boolean') { + return { boolValue: value } as Value; + } else if (typeof value === 'number') { + return { numberValue: value } as Value; + } else if (typeof value === 'string') { + return { stringValue: value } as Value; + } else if (Array.isArray(value)) { + return { listValue: value } as Value; + } else if (typeof value === 'object') { + return { structValue: value } as Value; + } else if (typeof value === 'undefined') { + return {} as Value; + } else { + throw new Error('Unsupported any value type: ' + typeof value); + } +} + +function unwrapAnyValue(value: Value): string | number | boolean | Object | null | Array | undefined { + if (value.stringValue !== undefined) { + return value.stringValue; + } else if (value.numberValue !== undefined) { + return value.numberValue; + } else if (value.boolValue !== undefined) { + return value.boolValue; + } else if (value.structValue !== undefined) { + return value.structValue; + } else if (value.listValue !== undefined) { + return value.listValue; + } else if (value.nullValue !== undefined) { + return null; + } +} diff --git a/src/encode.ts b/src/encode.ts index d2c412058..022033d01 100644 --- a/src/encode.ts +++ b/src/encode.ts @@ -36,6 +36,8 @@ export function generateEncoder(ctx: Context, typeName: string): Code { return code`${TypeValue}.encode({value: value ?? false}).finish()`; case 'BytesValue': return code`${TypeValue}.encode({value: value ?? new Uint8Array()}).finish()`; + case 'ListValue': + return code`${TypeValue}.encode({value: value ?? []]}).finish()`; } throw new Error(`unknown wrapper type: ${name}`); diff --git a/src/main.ts b/src/main.ts index 276217339..3f2436aae 100644 --- a/src/main.ts +++ b/src/main.ts @@ -14,9 +14,11 @@ import { defaultValue, detectMapType, getEnumMethod, + isAnyValueType, isBytes, isBytesValueType, isEnum, + isListValueType, isLong, isLongValueType, isMapType, @@ -32,6 +34,8 @@ import { toReaderCall, toTypeName, valueTypeName, + messageToTypeName, + isStructType, } from './types'; import SourceInfo, { Fields } from './sourceInfo'; import { assertInstanceOf, FormattedMethodDescriptor, maybeAddComment, maybePrefixPackage } from './utils'; @@ -258,7 +262,8 @@ export function generateFile(ctx: Context, fileDesc: FileDescriptorProto): [stri export type Utils = ReturnType & ReturnType & ReturnType & - ReturnType; + ReturnType & + ReturnType; /** These are runtime utility methods used by the generated code. */ export function makeUtils(options: Options): Utils { @@ -269,6 +274,7 @@ export function makeUtils(options: Options): Utils { ...makeDeepPartial(options, longs), ...makeTimestampMethods(options, longs), ...longs, + ...makeWrappingUtils(), }; } @@ -342,6 +348,73 @@ function makeLongUtils(options: Options, bytes: ReturnType return { numberToLong, longToNumber, longToString, longInit, Long }; } +function makeWrappingUtils() { + const wrapAnyValue = conditionalOutput( + 'wrapAnyValue', + code`function wrapAnyValue(value: any): Value { + if (value === null) { + return {nullValue: 0} as Value; + } else if (typeof value === 'boolean') { + return {boolValue: value} as Value; + } else if (typeof value === 'number') { + return {numberValue: value} as Value; + } else if (typeof value === 'string') { + return {stringValue: value} as Value; + } else if (Array.isArray(value)) { + return {listValue: value} as Value; + } else if (typeof value === 'object') { + return {structValue: value} as Value; + } else if (typeof value === 'undefined') { + return {} as Value; + } else { + throw new Error('Unsupported any value type: ' + typeof value); + } + }` + ); + + const unwrapAnyValue = conditionalOutput( + 'unwrapAnyValue', + code`function unwrapAnyValue(value: Value): string | number | boolean | Object | null | Array | undefined { + if (value.stringValue !== undefined) { + return value.stringValue; + } else if (value.numberValue !== undefined) { + return value.numberValue; + } else if (value.boolValue !== undefined) { + return value.boolValue; + } else if (value.structValue !== undefined) { + return value.structValue; + } else if (value.listValue !== undefined) { + return value.listValue; + } else if (value.nullValue !== undefined) { + return null; + } + }` + ); + + const wrapStruct = conditionalOutput( + 'wrapStruct', + code`function wrapStruct(object: {[key: string]: any}): Struct { + const struct = Struct.fromPartial({}); + Object.keys(object).forEach(key => { + struct.fields[key] = object[key]; + }); + return struct; + }` + ); + + const unwrapStruct = conditionalOutput( + 'unwrapStruct', + code`function unwrapStruct(struct: Struct): {[key: string]: any} { + const object: { [key: string]: any } = {}; + Object.keys(struct.fields).forEach(key => { + object[key] = struct.fields[key]; + }); + return object; + }` + ); + return { wrapAnyValue, unwrapAnyValue, wrapStruct, unwrapStruct }; +} + function makeByteUtils() { const globalThis = conditionalOutput( 'globalThis', @@ -696,8 +769,15 @@ function generateDecode(ctx: Context, fullName: string, messageDesc: DescriptorP } } } else if (isValueType(ctx, field)) { + const unwrap = (decodedValue: any): Code => { + if (isAnyValueType(field)) return code`${ctx.utils.unwrapAnyValue}(${decodedValue})`; + if (isStructType(field)) return code`${ctx.utils.unwrapStruct}(${decodedValue})`; + if (isListValueType(field)) return code`${decodedValue}.values`; + return code`${decodedValue}.value`; + }; const type = basicTypeName(ctx, field, { keepValueType: true }); - readSnippet = code`${type}.decode(reader, reader.uint32()).value`; + const decoder = code`${type}.decode(reader, reader.uint32())`; + readSnippet = code`${unwrap(decoder)}`; } else if (isTimestamp(field) && (options.useDate === DateOption.DATE || options.useDate === DateOption.STRING)) { const type = basicTypeName(ctx, field, { keepValueType: true }); readSnippet = code`${utils.fromTimestamp}(${type}.decode(reader, reader.uint32()))`; @@ -792,11 +872,18 @@ function generateEncode(ctx: Context, fullName: string, messageDesc: DescriptorP writeSnippet = (place) => code`${type}.encode(${utils.toTimestamp}(${place}), writer.uint32(${tag}).fork()).ldelim()`; } else if (isValueType(ctx, field)) { + const maybeTypeField = options.outputTypeRegistry ? `$type: '${field.typeName.slice(1)}',` : ''; + + const wrappedValue = (place: string): Code => { + if (isAnyValueType(field)) return code`${ctx.utils.wrapAnyValue}(${place})`; + if (isListValueType(field)) return code`{values: ${place}}`; + if (isStructType(field)) return code`${ctx.utils.wrapStruct}(${place})`; + return code`{${maybeTypeField} value: ${place}!}`; + }; + const tag = ((field.number << 3) | 2) >>> 0; const type = basicTypeName(ctx, field, { keepValueType: true }); - const maybeTypeField = options.outputTypeRegistry ? `$type: '${field.typeName.slice(1)}',` : ''; - writeSnippet = (place) => - code`${type}.encode({ ${maybeTypeField} value: ${place}! }, writer.uint32(${tag}).fork()).ldelim()`; + writeSnippet = (place) => code`${type}.encode(${wrappedValue(place)}, writer.uint32(${tag}).fork()).ldelim()`; } else if (isMessage(field)) { const tag = ((field.number << 3) | 2) >>> 0; const type = basicTypeName(ctx, field); @@ -935,12 +1022,16 @@ function generateFromJson(ctx: Context, fullName: string, messageDesc: Descripto (options.useDate === DateOption.DATE || options.useDate === DateOption.TIMESTAMP) ) { return code`${utils.fromJsonTimestamp}(${from})`; + } else if (isAnyValueType(field) || isStructType(field)) { + return code`${from}`; } else if (isValueType(ctx, field)) { const valueType = valueTypeName(ctx, field.typeName)!; if (isLongValueType(field) && options.forceLong === LongOption.LONG) { return code`${capitalize(valueType.toCodeString())}.fromValue(${from})`; } else if (isBytesValueType(field)) { return code`new ${capitalize(valueType.toCodeString())}(${from})`; + } else if (isListValueType(field)) { + return code`[...${from}]`; } else { return code`${capitalize(valueType.toCodeString())}(${from})`; } @@ -973,6 +1064,8 @@ function generateFromJson(ctx: Context, fullName: string, messageDesc: Descripto } else if (isValueType(ctx, valueType)) { const type = basicTypeName(ctx, valueType); return code`${from} as ${type}`; + } else if (isAnyValueType(valueType)) { + return code`${from}`; } else { const type = basicTypeName(ctx, valueType); return code`${type}.fromJSON(${from})`; @@ -998,6 +1091,10 @@ function generateFromJson(ctx: Context, fullName: string, messageDesc: Descripto }); `); chunks.push(code`}`); + } else if (isAnyValueType(field)) { + chunks.push(code` + message.${fieldName} = Array.isArray(object?.${fieldName}) ? [...object.${fieldName}] : []; + `); } else { // Explicit `any` type required to make TS with noImplicitAny happy. `object` is also `any` here. chunks.push(code` @@ -1011,6 +1108,18 @@ function generateFromJson(ctx: Context, fullName: string, messageDesc: Descripto message.${oneofName} = { $case: '${fieldName}', ${fieldName}: ${readSnippet(`object.${fieldName}`)} } `); chunks.push(code`}`); + } else if (isAnyValueType(field)) { + chunks.push(code`message.${fieldName} = object.${fieldName};`); + } else if (isStructType(field)) { + chunks.push( + code`message.${fieldName} = typeof(object.${fieldName}) === 'object' ? object.${fieldName} : undefined;` + ); + } else if (isListValueType(field)) { + chunks.push(code` + message.${fieldName} = Array.isArray(object?.${fieldName}) + ? ${readSnippet(`object.${fieldName}`)} + : ${'undefined'}; + `); } else { const fallback = isWithinOneOf(field) ? 'undefined' : defaultValue(ctx, field); chunks.push(code` @@ -1070,10 +1179,14 @@ function generateToJson(ctx: Context, fullName: string, messageDesc: DescriptorP return code`${from}.toString()`; } else if (isScalar(valueType) || isValueType(ctx, valueType)) { return code`${from}`; + } else if (isAnyValueType(valueType)) { + return code`${from}`; } else { const type = basicTypeName(ctx, valueType); return code`${type}.toJSON(${from})`; } + } else if (isAnyValueType(field)) { + return code`${from}`; } else if (isMessage(field) && !isValueType(ctx, field) && !isMapType(ctx, messageDesc, field)) { const type = basicTypeName(ctx, field, { keepValueType: true }); return code`${from} ? ${type}.toJSON(${from}) : ${defaultValue(ctx, field)}`; @@ -1163,6 +1276,8 @@ function generateFromPartial(ctx: Context, fullName: string, messageDesc: Descri const cstr = capitalize(basicTypeName(ctx, valueType).toCodeString()); return code`${cstr}(${from})`; } + } else if (isAnyValueType(valueType)) { + return code`${from}`; } else if ( isTimestamp(valueType) && (options.useDate === DateOption.DATE || options.useDate === DateOption.STRING) @@ -1174,6 +1289,8 @@ function generateFromPartial(ctx: Context, fullName: string, messageDesc: Descri const type = basicTypeName(ctx, valueType); return code`${type}.fromPartial(${from})`; } + } else if (isAnyValueType(field)) { + return code`${from}`; } else { const type = basicTypeName(ctx, field); return code`${type}.fromPartial(${from})`; diff --git a/src/types.ts b/src/types.ts index 6e162b074..450c0f804 100644 --- a/src/types.ts +++ b/src/types.ts @@ -370,10 +370,26 @@ export function isValueType(ctx: Context, field: FieldDescriptorProto): boolean return valueTypeName(ctx, field.typeName) !== undefined; } +export function isAnyValueType(field: FieldDescriptorProto): boolean { + return field.typeName === '.google.protobuf.Value'; +} + export function isBytesValueType(field: FieldDescriptorProto): boolean { return field.typeName === '.google.protobuf.BytesValue'; } +export function isListValueType(field: FieldDescriptorProto): boolean { + return isListValueTypeName(field.typeName); +} + +export function isListValueTypeName(typeName: string): boolean { + return typeName === 'google.protobuf.ListValue' || typeName === '.google.protobuf.ListValue'; +} + +export function isStructType(field: FieldDescriptorProto): boolean { + return field.typeName === '.google.protobuf.Struct'; +} + export function isLongValueType(field: FieldDescriptorProto): boolean { return field.typeName === '.google.protobuf.Int64Value' || field.typeName === '.google.protobuf.UInt64Value'; } @@ -399,6 +415,12 @@ export function valueTypeName(ctx: Context, typeName: string): Code | undefined return code`boolean`; case '.google.protobuf.BytesValue': return code`Uint8Array`; + case '.google.protobuf.ListValue': + return code`Array`; + case '.google.protobuf.Value': + return code`any`; + case '.google.protobuf.Struct': + return code`{[key: string]: any}`; default: return undefined; } @@ -415,6 +437,7 @@ export function wrapperTypeName(typeName: string): string | undefined { case '.google.protobuf.UInt64Value': case '.google.protobuf.BoolValue': case '.google.protobuf.BytesValue': + case '.google.protobuf.ListValue': case '.google.protobuf.Timestamp': return typeName.split('.')[3]; default: