From 64538091f7339f285ab6efbb0e18054970e00f33 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 11 Aug 2022 09:42:30 +0100 Subject: [PATCH] feat: define default types during decode (#62) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Proto3 language guide [states](https://developers.google.com/protocol-buffers/docs/proto3#default): > When a message is parsed, if the encoded message does not contain a particular singular element, the corresponding field in the parsed object is set to the default value for that field. When decoding objects, create an object with all non-optional fields set to the default values, then overwrite them during decoding. Given that we create arrays for repeated fields there's no need to check for their existence before returning the deserialized object. The only weird part in all this is message fields. The spec says that any non-optional field is required, but it also says that the default value for a sub-message is to be unset and that the actual value there is language-dependent and links to some language guides which don't include JavaScript - I've used `undefined` here. The upshot of this is that you can have a non-optional field with a default value of `undefined` which essentially makes it optional. Nice. At the moment we throw if the sub-message is not present in the protobuf if the field is required though in a future change we may wish to ignore the spec and initialise the sub-message to an instance of it's type with default values set 🤷. Refs #43 --- .../protons-benchmark/src/protons/bench.ts | 15 +- packages/protons-runtime/src/index.ts | 6 +- packages/protons/src/index.ts | 89 +++++++++- packages/protons/test/fixtures/basic.proto | 2 +- packages/protons/test/fixtures/basic.ts | 4 +- packages/protons/test/fixtures/circuit.ts | 12 +- packages/protons/test/fixtures/daemon.ts | 158 +++++++----------- packages/protons/test/fixtures/dht.ts | 29 +--- packages/protons/test/fixtures/noise.ts | 6 +- packages/protons/test/fixtures/peer.ts | 34 ++-- packages/protons/test/fixtures/test.ts | 15 +- packages/protons/test/index.spec.ts | 19 +++ 12 files changed, 213 insertions(+), 176 deletions(-) diff --git a/packages/protons-benchmark/src/protons/bench.ts b/packages/protons-benchmark/src/protons/bench.ts index efcdf84..877991e 100644 --- a/packages/protons-benchmark/src/protons/bench.ts +++ b/packages/protons-benchmark/src/protons/bench.ts @@ -159,7 +159,9 @@ export namespace Yo { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + lol: [] + } const end = length == null ? reader.len : reader.pos + length @@ -168,7 +170,6 @@ export namespace Yo { switch (tag >>> 3) { case 1: - obj.lol = obj.lol ?? [] obj.lol.push(FOO.codec().decode(reader)) break default: @@ -177,12 +178,6 @@ export namespace Yo { } } - obj.lol = obj.lol ?? [] - - if (obj.lol == null) { - throw new Error('Protocol error: value for required field "lol" was not found in protobuf') - } - return obj }) } @@ -230,7 +225,9 @@ export namespace Lol { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + b: undefined + } const end = length == null ? reader.len : reader.pos + length diff --git a/packages/protons-runtime/src/index.ts b/packages/protons-runtime/src/index.ts index 095bf20..54ac86b 100644 --- a/packages/protons-runtime/src/index.ts +++ b/packages/protons-runtime/src/index.ts @@ -180,12 +180,12 @@ export interface Reader { double: () => number /** - * Reads a sequence of bytes preceeded by its length as a varint + * Reads a sequence of bytes preceded by its length as a varint */ - bytes: () => number + bytes: () => Uint8Array /** - * Reads a string preceeded by its byte length as a varint + * Reads a string preceded by its byte length as a varint */ string: () => string diff --git a/packages/protons/src/index.ts b/packages/protons/src/index.ts index 2b03b25..385b91c 100644 --- a/packages/protons/src/index.ts +++ b/packages/protons/src/index.ts @@ -39,7 +39,6 @@ const encoderGenerators: Record string> = { bool: (val) => `writer.bool(${val})`, bytes: (val) => `writer.bytes(${val})`, double: (val) => `writer.double(${val})`, - // enumeration: (val) => `writer.double(${val})`, fixed32: (val) => `writer.fixed32(${val})`, fixed64: (val) => `writer.fixed64(${val})`, float: (val) => `writer.float(${val})`, @@ -58,7 +57,6 @@ const decoderGenerators: Record string> = { bool: () => 'reader.bool()', bytes: () => 'reader.bytes()', double: () => 'reader.double()', - // enumeration: () => `writer.double(${val})`, fixed32: () => 'reader.fixed32()', fixed64: () => 'reader.fixed64()', float: () => 'reader.float()', @@ -73,6 +71,24 @@ const decoderGenerators: Record string> = { uint64: () => 'reader.uint64()' } +const defaultValueGenerators: Record string> = { + bool: () => 'false', + bytes: () => 'new Uint8Array(0)', + double: () => '0', + fixed32: () => '0', + fixed64: () => '0n', + float: () => '0', + int32: () => '0', + int64: () => '0n', + sfixed32: () => '0', + sfixed64: () => '0n', + sint32: () => '0', + sint64: () => '0n', + string: () => "''", + uint32: () => '0', + uint64: () => '0n' +} + function findTypeName (typeName: string, classDef: MessageDef, moduleDef: ModuleDef): string { if (types[typeName] != null) { return types[typeName] @@ -117,6 +133,65 @@ function findDef (typeName: string, classDef: MessageDef, moduleDef: ModuleDef): throw new Error(`Could not resolve type name "${typeName}"`) } +function createDefaultObject (fields: Record, messageDef: MessageDef, moduleDef: ModuleDef): string { + const output = Object.entries(fields) + .map(([name, fieldDef]) => { + if (fieldDef.repeated) { + return `${name}: []` + } + + if (fieldDef.optional) { + return '' + } + + const type: string = fieldDef.type + let defaultValue + + if (defaultValueGenerators[type] != null) { + defaultValue = defaultValueGenerators[type]() + } else { + const def = findDef(fieldDef.type, messageDef, moduleDef) + + if (isEnumDef(def)) { + // select lowest-value enum - should be 0 but it's not guaranteed + const val = Object.entries(def.values) + .sort((a, b) => { + if (a[1] < b[1]) { + return 1 + } + + if (a[1] > b[1]) { + return -1 + } + + return 0 + }) + .pop() + + if (val == null) { + throw new Error(`Could not find default enum value for ${def.fullName}`) + } + + defaultValue = `${def.name}.${val[0]}` + } else { + defaultValue = 'undefined' + } + } + + return `${name}: ${defaultValue}` + }) + .filter(Boolean) + .join(',\n ') + + if (output !== '') { + return ` + ${output} + ` + } + + return '' +} + const encoders: Record = { bool: 'bool', bytes: 'bytes', @@ -259,7 +334,7 @@ export interface ${messageDef.name} { const ensureArrayProps = Object.entries(fields) .map(([name, fieldDef]) => { // make sure repeated fields have an array if not set - if (fieldDef.rule === 'repeated') { + if (fieldDef.optional && fieldDef.rule === 'repeated') { return ` obj.${name} = obj.${name} ?? []` } @@ -269,7 +344,7 @@ export interface ${messageDef.name} { const ensureRequiredFields = Object.entries(fields) .map(([name, fieldDef]) => { // make sure required fields are set - if (!fieldDef.optional) { + if (!fieldDef.optional && !fieldDef.repeated) { return ` if (obj.${name} == null) { throw new Error('Protocol error: value for required field "${name}" was not found in protobuf') @@ -331,7 +406,7 @@ ${Object.entries(fields) writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = {${createDefaultObject(fields, messageDef, moduleDef)}} const end = length == null ? reader.len : reader.pos + length @@ -360,8 +435,10 @@ ${Object.entries(fields) } return `case ${fieldDef.id}:${fieldDef.rule === 'repeated' +? `${fieldDef.optional ? ` - obj.${name} = obj.${name} ?? [] + obj.${name} = obj.${name} ?? []` +: ''} obj.${name}.push(${decoderGenerators[type] == null ? `${codec}.decode(reader${type === 'message' ? ', reader.uint32()' : ''})` : decoderGenerators[type]()})` : ` obj.${name} = ${decoderGenerators[type] == null ? `${codec}.decode(reader${type === 'message' ? ', reader.uint32()' : ''})` : decoderGenerators[type]()}`} diff --git a/packages/protons/test/fixtures/basic.proto b/packages/protons/test/fixtures/basic.proto index 55882d4..83fbc41 100644 --- a/packages/protons/test/fixtures/basic.proto +++ b/packages/protons/test/fixtures/basic.proto @@ -2,5 +2,5 @@ syntax = "proto3"; message Basic { optional string foo = 1; - required int32 num = 2; + int32 num = 2; } diff --git a/packages/protons/test/fixtures/basic.ts b/packages/protons/test/fixtures/basic.ts index 0950495..5e6a1a2 100644 --- a/packages/protons/test/fixtures/basic.ts +++ b/packages/protons/test/fixtures/basic.ts @@ -36,7 +36,9 @@ export namespace Basic { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + num: 0 + } const end = length == null ? reader.len : reader.pos + length diff --git a/packages/protons/test/fixtures/circuit.ts b/packages/protons/test/fixtures/circuit.ts index 44b0127..6567544 100644 --- a/packages/protons/test/fixtures/circuit.ts +++ b/packages/protons/test/fixtures/circuit.ts @@ -112,7 +112,10 @@ export namespace CircuitRelay { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + id: new Uint8Array(0), + addrs: [] + } const end = length == null ? reader.len : reader.pos + length @@ -124,7 +127,6 @@ export namespace CircuitRelay { obj.id = reader.bytes() break case 2: - obj.addrs = obj.addrs ?? [] obj.addrs.push(reader.bytes()) break default: @@ -133,16 +135,10 @@ export namespace CircuitRelay { } } - obj.addrs = obj.addrs ?? [] - if (obj.id == null) { throw new Error('Protocol error: value for required field "id" was not found in protobuf') } - if (obj.addrs == null) { - throw new Error('Protocol error: value for required field "addrs" was not found in protobuf') - } - return obj }) } diff --git a/packages/protons/test/fixtures/daemon.ts b/packages/protons/test/fixtures/daemon.ts index e2f0627..32080ca 100644 --- a/packages/protons/test/fixtures/daemon.ts +++ b/packages/protons/test/fixtures/daemon.ts @@ -110,7 +110,9 @@ export namespace Request { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + type: Type.IDENTIFY + } const end = length == null ? reader.len : reader.pos + length @@ -258,7 +260,10 @@ export namespace Response { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + type: Type.OK, + peers: [] + } const end = length == null ? reader.len : reader.pos + length @@ -282,7 +287,6 @@ export namespace Response { obj.dht = DHTResponse.codec().decode(reader, reader.uint32()) break case 6: - obj.peers = obj.peers ?? [] obj.peers.push(PeerInfo.codec().decode(reader, reader.uint32())) break case 7: @@ -297,16 +301,10 @@ export namespace Response { } } - obj.peers = obj.peers ?? [] - if (obj.type == null) { throw new Error('Protocol error: value for required field "type" was not found in protobuf') } - if (obj.peers == null) { - throw new Error('Protocol error: value for required field "peers" was not found in protobuf') - } - return obj }) } @@ -358,7 +356,10 @@ export namespace IdentifyResponse { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + id: new Uint8Array(0), + addrs: [] + } const end = length == null ? reader.len : reader.pos + length @@ -370,7 +371,6 @@ export namespace IdentifyResponse { obj.id = reader.bytes() break case 2: - obj.addrs = obj.addrs ?? [] obj.addrs.push(reader.bytes()) break default: @@ -379,16 +379,10 @@ export namespace IdentifyResponse { } } - obj.addrs = obj.addrs ?? [] - if (obj.id == null) { throw new Error('Protocol error: value for required field "id" was not found in protobuf') } - if (obj.addrs == null) { - throw new Error('Protocol error: value for required field "addrs" was not found in protobuf') - } - return obj }) } @@ -446,7 +440,10 @@ export namespace ConnectRequest { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + peer: new Uint8Array(0), + addrs: [] + } const end = length == null ? reader.len : reader.pos + length @@ -458,7 +455,6 @@ export namespace ConnectRequest { obj.peer = reader.bytes() break case 2: - obj.addrs = obj.addrs ?? [] obj.addrs.push(reader.bytes()) break case 3: @@ -470,16 +466,10 @@ export namespace ConnectRequest { } } - obj.addrs = obj.addrs ?? [] - if (obj.peer == null) { throw new Error('Protocol error: value for required field "peer" was not found in protobuf') } - if (obj.addrs == null) { - throw new Error('Protocol error: value for required field "addrs" was not found in protobuf') - } - return obj }) } @@ -537,7 +527,10 @@ export namespace StreamOpenRequest { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + peer: new Uint8Array(0), + proto: [] + } const end = length == null ? reader.len : reader.pos + length @@ -549,7 +542,6 @@ export namespace StreamOpenRequest { obj.peer = reader.bytes() break case 2: - obj.proto = obj.proto ?? [] obj.proto.push(reader.string()) break case 3: @@ -561,16 +553,10 @@ export namespace StreamOpenRequest { } } - obj.proto = obj.proto ?? [] - if (obj.peer == null) { throw new Error('Protocol error: value for required field "peer" was not found in protobuf') } - if (obj.proto == null) { - throw new Error('Protocol error: value for required field "proto" was not found in protobuf') - } - return obj }) } @@ -622,7 +608,10 @@ export namespace StreamHandlerRequest { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + addr: new Uint8Array(0), + proto: [] + } const end = length == null ? reader.len : reader.pos + length @@ -634,7 +623,6 @@ export namespace StreamHandlerRequest { obj.addr = reader.bytes() break case 2: - obj.proto = obj.proto ?? [] obj.proto.push(reader.string()) break default: @@ -643,16 +631,10 @@ export namespace StreamHandlerRequest { } } - obj.proto = obj.proto ?? [] - if (obj.addr == null) { throw new Error('Protocol error: value for required field "addr" was not found in protobuf') } - if (obj.proto == null) { - throw new Error('Protocol error: value for required field "proto" was not found in protobuf') - } - return obj }) } @@ -694,7 +676,9 @@ export namespace ErrorResponse { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + msg: '' + } const end = length == null ? reader.len : reader.pos + length @@ -772,7 +756,11 @@ export namespace StreamInfo { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + peer: new Uint8Array(0), + addr: new Uint8Array(0), + proto: '' + } const end = length == null ? reader.len : reader.pos + length @@ -914,7 +902,9 @@ export namespace DHTRequest { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + type: Type.FIND_PEER + } const end = length == null ? reader.len : reader.pos + length @@ -1024,7 +1014,9 @@ export namespace DHTResponse { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + type: Type.BEGIN + } const end = length == null ? reader.len : reader.pos + length @@ -1102,7 +1094,10 @@ export namespace PeerInfo { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + id: new Uint8Array(0), + addrs: [] + } const end = length == null ? reader.len : reader.pos + length @@ -1114,7 +1109,6 @@ export namespace PeerInfo { obj.id = reader.bytes() break case 2: - obj.addrs = obj.addrs ?? [] obj.addrs.push(reader.bytes()) break default: @@ -1123,16 +1117,10 @@ export namespace PeerInfo { } } - obj.addrs = obj.addrs ?? [] - if (obj.id == null) { throw new Error('Protocol error: value for required field "id" was not found in protobuf') } - if (obj.addrs == null) { - throw new Error('Protocol error: value for required field "addrs" was not found in protobuf') - } - return obj }) } @@ -1210,7 +1198,9 @@ export namespace ConnManagerRequest { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + type: Type.TAG_PEER + } const end = length == null ? reader.len : reader.pos + length @@ -1281,7 +1271,9 @@ export namespace DisconnectRequest { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + peer: new Uint8Array(0) + } const end = length == null ? reader.len : reader.pos + length @@ -1375,7 +1367,9 @@ export namespace PSRequest { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + type: Type.GET_TOPICS + } const end = length == null ? reader.len : reader.pos + length @@ -1475,7 +1469,9 @@ export namespace PSMessage { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + topicIDs: [] + } const end = length == null ? reader.len : reader.pos + length @@ -1493,7 +1489,6 @@ export namespace PSMessage { obj.seqno = reader.bytes() break case 4: - obj.topicIDs = obj.topicIDs ?? [] obj.topicIDs.push(reader.string()) break case 5: @@ -1508,12 +1503,6 @@ export namespace PSMessage { } } - obj.topicIDs = obj.topicIDs ?? [] - - if (obj.topicIDs == null) { - throw new Error('Protocol error: value for required field "topicIDs" was not found in protobuf') - } - return obj }) } @@ -1567,7 +1556,10 @@ export namespace PSResponse { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + topics: [], + peerIDs: [] + } const end = length == null ? reader.len : reader.pos + length @@ -1576,11 +1568,9 @@ export namespace PSResponse { switch (tag >>> 3) { case 1: - obj.topics = obj.topics ?? [] obj.topics.push(reader.string()) break case 2: - obj.peerIDs = obj.peerIDs ?? [] obj.peerIDs.push(reader.bytes()) break default: @@ -1589,17 +1579,6 @@ export namespace PSResponse { } } - obj.topics = obj.topics ?? [] - obj.peerIDs = obj.peerIDs ?? [] - - if (obj.topics == null) { - throw new Error('Protocol error: value for required field "topics" was not found in protobuf') - } - - if (obj.peerIDs == null) { - throw new Error('Protocol error: value for required field "peerIDs" was not found in protobuf') - } - return obj }) } @@ -1673,7 +1652,10 @@ export namespace PeerstoreRequest { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + type: Type.GET_PROTOCOLS, + protos: [] + } const end = length == null ? reader.len : reader.pos + length @@ -1688,7 +1670,6 @@ export namespace PeerstoreRequest { obj.id = reader.bytes() break case 3: - obj.protos = obj.protos ?? [] obj.protos.push(reader.string()) break default: @@ -1697,16 +1678,10 @@ export namespace PeerstoreRequest { } } - obj.protos = obj.protos ?? [] - if (obj.type == null) { throw new Error('Protocol error: value for required field "type" was not found in protobuf') } - if (obj.protos == null) { - throw new Error('Protocol error: value for required field "protos" was not found in protobuf') - } - return obj }) } @@ -1756,7 +1731,9 @@ export namespace PeerstoreResponse { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + protos: [] + } const end = length == null ? reader.len : reader.pos + length @@ -1768,7 +1745,6 @@ export namespace PeerstoreResponse { obj.peer = PeerInfo.codec().decode(reader, reader.uint32()) break case 2: - obj.protos = obj.protos ?? [] obj.protos.push(reader.string()) break default: @@ -1777,12 +1753,6 @@ export namespace PeerstoreResponse { } } - obj.protos = obj.protos ?? [] - - if (obj.protos == null) { - throw new Error('Protocol error: value for required field "protos" was not found in protobuf') - } - return obj }) } diff --git a/packages/protons/test/fixtures/dht.ts b/packages/protons/test/fixtures/dht.ts index fc278a9..afbe4e2 100644 --- a/packages/protons/test/fixtures/dht.ts +++ b/packages/protons/test/fixtures/dht.ts @@ -190,7 +190,9 @@ export namespace Message { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + addrs: [] + } const end = length == null ? reader.len : reader.pos + length @@ -202,7 +204,6 @@ export namespace Message { obj.id = reader.bytes() break case 2: - obj.addrs = obj.addrs ?? [] obj.addrs.push(reader.bytes()) break case 3: @@ -214,12 +215,6 @@ export namespace Message { } } - obj.addrs = obj.addrs ?? [] - - if (obj.addrs == null) { - throw new Error('Protocol error: value for required field "addrs" was not found in protobuf') - } - return obj }) } @@ -287,7 +282,10 @@ export namespace Message { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + closerPeers: [], + providerPeers: [] + } const end = length == null ? reader.len : reader.pos + length @@ -308,11 +306,9 @@ export namespace Message { obj.record = reader.bytes() break case 8: - obj.closerPeers = obj.closerPeers ?? [] obj.closerPeers.push(Message.Peer.codec().decode(reader, reader.uint32())) break case 9: - obj.providerPeers = obj.providerPeers ?? [] obj.providerPeers.push(Message.Peer.codec().decode(reader, reader.uint32())) break default: @@ -321,17 +317,6 @@ export namespace Message { } } - obj.closerPeers = obj.closerPeers ?? [] - obj.providerPeers = obj.providerPeers ?? [] - - if (obj.closerPeers == null) { - throw new Error('Protocol error: value for required field "closerPeers" was not found in protobuf') - } - - if (obj.providerPeers == null) { - throw new Error('Protocol error: value for required field "providerPeers" was not found in protobuf') - } - return obj }) } diff --git a/packages/protons/test/fixtures/noise.ts b/packages/protons/test/fixtures/noise.ts index 0bdada6..0d1af33 100644 --- a/packages/protons/test/fixtures/noise.ts +++ b/packages/protons/test/fixtures/noise.ts @@ -47,7 +47,11 @@ export namespace pb { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + identityKey: new Uint8Array(0), + identitySig: new Uint8Array(0), + data: new Uint8Array(0) + } const end = length == null ? reader.len : reader.pos + length diff --git a/packages/protons/test/fixtures/peer.ts b/packages/protons/test/fixtures/peer.ts index 68bd477..f9357a3 100644 --- a/packages/protons/test/fixtures/peer.ts +++ b/packages/protons/test/fixtures/peer.ts @@ -64,7 +64,11 @@ export namespace Peer { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + addresses: [], + protocols: [], + metadata: [] + } const end = length == null ? reader.len : reader.pos + length @@ -73,15 +77,12 @@ export namespace Peer { switch (tag >>> 3) { case 1: - obj.addresses = obj.addresses ?? [] obj.addresses.push(Address.codec().decode(reader, reader.uint32())) break case 2: - obj.protocols = obj.protocols ?? [] obj.protocols.push(reader.string()) break case 3: - obj.metadata = obj.metadata ?? [] obj.metadata.push(Metadata.codec().decode(reader, reader.uint32())) break case 4: @@ -96,22 +97,6 @@ export namespace Peer { } } - obj.addresses = obj.addresses ?? [] - obj.protocols = obj.protocols ?? [] - obj.metadata = obj.metadata ?? [] - - if (obj.addresses == null) { - throw new Error('Protocol error: value for required field "addresses" was not found in protobuf') - } - - if (obj.protocols == null) { - throw new Error('Protocol error: value for required field "protocols" was not found in protobuf') - } - - if (obj.metadata == null) { - throw new Error('Protocol error: value for required field "metadata" was not found in protobuf') - } - return obj }) } @@ -159,7 +144,9 @@ export namespace Address { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + multiaddr: new Uint8Array(0) + } const end = length == null ? reader.len : reader.pos + length @@ -232,7 +219,10 @@ export namespace Metadata { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + key: '', + value: new Uint8Array(0) + } const end = length == null ? reader.len : reader.pos + length diff --git a/packages/protons/test/fixtures/test.ts b/packages/protons/test/fixtures/test.ts index eea1650..3ca17c9 100644 --- a/packages/protons/test/fixtures/test.ts +++ b/packages/protons/test/fixtures/test.ts @@ -45,7 +45,9 @@ export namespace SubMessage { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + foo: '' + } const end = length == null ? reader.len : reader.pos + length @@ -211,7 +213,9 @@ export namespace AllTheTypes { writer.ldelim() } }, (reader, length) => { - const obj: any = {} + const obj: any = { + field14: [] + } const end = length == null ? reader.len : reader.pos + length @@ -259,7 +263,6 @@ export namespace AllTheTypes { obj.field13 = SubMessage.codec().decode(reader, reader.uint32()) break case 14: - obj.field14 = obj.field14 ?? [] obj.field14.push(reader.string()) break case 15: @@ -280,12 +283,6 @@ export namespace AllTheTypes { } } - obj.field14 = obj.field14 ?? [] - - if (obj.field14 == null) { - throw new Error('Protocol error: value for required field "field14" was not found in protobuf') - } - return obj }) } diff --git a/packages/protons/test/index.spec.ts b/packages/protons/test/index.spec.ts index 060c8d6..e6a8756 100644 --- a/packages/protons/test/index.spec.ts +++ b/packages/protons/test/index.spec.ts @@ -180,4 +180,23 @@ describe('encode', () => { expect(CircuitRelay.decode(encoded)).to.deep.equal(message) expect(CircuitRelay.decode(pbufJsBuf)).to.deep.equal(message) }) + + it('supports optional fields', () => { + const obj: Basic = { + num: 5 + } + + const encoded = Basic.encode(obj) + const decoded = Basic.decode(encoded) + + // foo is optional + expect(decoded).to.not.have.property('foo') + }) + + it('supports default fields', () => { + const decoded = Basic.decode(Uint8Array.from([])) + + // num is required + expect(decoded).to.have.property('num', 0) + }) })