From f67bc1b57002669f4eab0394db1255235a69e5f4 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Sat, 8 Oct 2022 11:10:23 +0100 Subject: [PATCH] fix: adhere more closely to the language guide for proto3 default values Only write singular fields when they are non-default values (unless in repeated fields). Always write optional values even when they are non-default values. Updates test suite to compare generated bytes to protobuf.js to ensure compatibility. Also adds a README section on differences between protons and protobuf.js fixes #43 --- packages/protons-runtime/src/codec.ts | 1 + packages/protons-runtime/src/codecs/enum.ts | 2 +- packages/protons-runtime/src/decode.ts | 22 +- packages/protons-runtime/src/encode.ts | 20 +- packages/protons-runtime/src/index.ts | 2 + packages/protons-runtime/src/reader.ts | 22 + packages/protons-runtime/src/writer.ts | 22 + packages/protons/README.md | 16 + packages/protons/src/index.ts | 160 ++-- packages/protons/test/fixtures/basic.ts | 24 +- packages/protons/test/fixtures/circuit.ts | 68 +- packages/protons/test/fixtures/daemon.proto | 35 +- packages/protons/test/fixtures/daemon.ts | 681 +++++++++--------- packages/protons/test/fixtures/dht.ts | 98 +-- packages/protons/test/fixtures/noise.ts | 44 +- packages/protons/test/fixtures/optional.proto | 32 + packages/protons/test/fixtures/optional.ts | 299 ++++++++ packages/protons/test/fixtures/peer.ts | 102 ++- packages/protons/test/fixtures/singular.proto | 32 + packages/protons/test/fixtures/singular.ts | 320 ++++++++ packages/protons/test/fixtures/test.ts | 111 +-- packages/protons/test/index.spec.ts | 216 +++++- 22 files changed, 1667 insertions(+), 662 deletions(-) create mode 100644 packages/protons-runtime/src/reader.ts create mode 100644 packages/protons-runtime/src/writer.ts create mode 100644 packages/protons/test/fixtures/optional.proto create mode 100644 packages/protons/test/fixtures/optional.ts create mode 100644 packages/protons/test/fixtures/singular.proto create mode 100644 packages/protons/test/fixtures/singular.ts diff --git a/packages/protons-runtime/src/codec.ts b/packages/protons-runtime/src/codec.ts index 5405013..367849a 100644 --- a/packages/protons-runtime/src/codec.ts +++ b/packages/protons-runtime/src/codec.ts @@ -12,6 +12,7 @@ export enum CODEC_TYPES { export interface EncodeOptions { lengthDelimited?: boolean + writeDefaults?: boolean } export interface EncodeFunction { diff --git a/packages/protons-runtime/src/codecs/enum.ts b/packages/protons-runtime/src/codecs/enum.ts index 6c3ec16..4c85300 100644 --- a/packages/protons-runtime/src/codecs/enum.ts +++ b/packages/protons-runtime/src/codecs/enum.ts @@ -20,7 +20,7 @@ export function enumeration (v: any): Codec { } const decode: DecodeFunction = function enumDecode (reader) { - const val = reader.uint32() + const val = reader.int32() return findValue(val) } diff --git a/packages/protons-runtime/src/decode.ts b/packages/protons-runtime/src/decode.ts index 1298363..f1bddfd 100644 --- a/packages/protons-runtime/src/decode.ts +++ b/packages/protons-runtime/src/decode.ts @@ -1,25 +1,9 @@ import type { Uint8ArrayList } from 'uint8arraylist' import type { Codec } from './codec.js' -import pb from 'protobufjs' - -const Reader = pb.Reader - -// monkey patch the reader to add native bigint support -const methods = [ - 'uint64', 'int64', 'sint64', 'fixed64', 'sfixed64' -] -methods.forEach(method => { - // @ts-expect-error - const original = Reader.prototype[method] - // @ts-expect-error - Reader.prototype[method] = function (): bigint { - return BigInt(original.call(this).toString()) - } -}) +import { reader } from './reader.js' export function decodeMessage (buf: Uint8Array | Uint8ArrayList, codec: Codec): T { - const reader = Reader.create(buf instanceof Uint8Array ? buf : buf.subarray()) + const r = reader(buf instanceof Uint8Array ? buf : buf.subarray()) - // @ts-expect-error - return codec.decode(reader) + return codec.decode(r) } diff --git a/packages/protons-runtime/src/encode.ts b/packages/protons-runtime/src/encode.ts index b9b742a..61c4166 100644 --- a/packages/protons-runtime/src/encode.ts +++ b/packages/protons-runtime/src/encode.ts @@ -1,25 +1,9 @@ import type { Codec } from './codec.js' -import pb from 'protobufjs' - -const Writer = pb.Writer - -// monkey patch the writer to add native bigint support -const methods = [ - 'uint64', 'int64', 'sint64', 'fixed64', 'sfixed64' -] -methods.forEach(method => { - // @ts-expect-error - const original = Writer.prototype[method] - // @ts-expect-error - Writer.prototype[method] = function (val: bigint): pb.Writer { - return original.call(this, val.toString()) - } -}) +import { writer } from './writer.js' export function encodeMessage (message: T, codec: Codec): Uint8Array { - const w = Writer.create() + const w = writer() - // @ts-expect-error codec.encode(message, w, { lengthDelimited: false }) diff --git a/packages/protons-runtime/src/index.ts b/packages/protons-runtime/src/index.ts index 54ac86b..44860e8 100644 --- a/packages/protons-runtime/src/index.ts +++ b/packages/protons-runtime/src/index.ts @@ -18,6 +18,8 @@ export { export { enumeration } from './codecs/enum.js' export { message } from './codecs/message.js' +export { reader } from './reader.js' +export { writer } from './writer.js' export type { Codec, EncodeOptions } from './codec.js' export interface Writer { diff --git a/packages/protons-runtime/src/reader.ts b/packages/protons-runtime/src/reader.ts new file mode 100644 index 0000000..ebf8857 --- /dev/null +++ b/packages/protons-runtime/src/reader.ts @@ -0,0 +1,22 @@ +import pb from 'protobufjs/minimal.js' +import type { Reader } from './index.js' + +const PBReader = pb.Reader + +// monkey patch the reader to add native bigint support +const methods = [ + 'uint64', 'int64', 'sint64', 'fixed64', 'sfixed64' +] +methods.forEach(method => { + // @ts-expect-error + const original = PBReader.prototype[method] + // @ts-expect-error + PBReader.prototype[method] = function (): bigint { + return BigInt(original.call(this).toString()) + } +}) + +export function reader (buf: Uint8Array): Reader { + // @ts-expect-error class is monkey patched + return PBReader.create(buf) +} diff --git a/packages/protons-runtime/src/writer.ts b/packages/protons-runtime/src/writer.ts new file mode 100644 index 0000000..d4ac8e9 --- /dev/null +++ b/packages/protons-runtime/src/writer.ts @@ -0,0 +1,22 @@ +import pb from 'protobufjs/minimal.js' +import type { Writer } from './index.js' + +const PBWriter = pb.Writer + +// monkey patch the writer to add native bigint support +const methods = [ + 'uint64', 'int64', 'sint64', 'fixed64', 'sfixed64' +] +methods.forEach(method => { + // @ts-expect-error + const original = PBWriter.prototype[method] + // @ts-expect-error + PBWriter.prototype[method] = function (val: bigint): pb.Writer { + return original.call(this, val.toString()) + } +}) + +export function writer (): Writer { + // @ts-expect-error class is monkey patched + return PBWriter.create() +} diff --git a/packages/protons/README.md b/packages/protons/README.md index 85c0c89..15c626d 100644 --- a/packages/protons/README.md +++ b/packages/protons/README.md @@ -12,6 +12,7 @@ - [Install](#install) - [Usage](#usage) +- [Differences from protobuf.js](#differences-from-protobufjs) - [Contribute](#contribute) - [License](#license) - [Contribute](#contribute-1) @@ -59,6 +60,21 @@ console.info(decoded.message) // 'hello world' ``` +## Differences from protobuf.js + +This module uses the internal reader/writer from `protobuf.js` as it is highly optimised and there's no point reinventing the wheel. + +It does have one or two differences: + +1. Supports `proto3` semantics only +2. All 64 bit values are represented as `BigInt`s and not `Long`s (e.g. `int64`, `uint64`, `sint64` etc) +3. Unset `optional` fields are set on the deserialized object forms as `undefined` instead of the default values +4. `singular` fields set to default values are not serialized and are set to default values when deserialized if not set - protobuf.js [diverges from the language guide](https://github.com/protobufjs/protobuf.js/issues/1468#issuecomment-745177012) around this feature + +## Missing features + +Some features are missing `OneOf`, `Map`s, etc due to them not being needed so far in ipfs/libp2p. If these features are important to you, please open PRs implementing them along with tests comparing the generated bytes to `protobuf.js` and `pbjs`. + ## Contribute Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/protons/issues)! diff --git a/packages/protons/src/index.ts b/packages/protons/src/index.ts index 385b91c..7f6c691 100644 --- a/packages/protons/src/index.ts +++ b/packages/protons/src/index.ts @@ -36,21 +36,21 @@ const types: Record = { } const encoderGenerators: Record string> = { - bool: (val) => `writer.bool(${val})`, - bytes: (val) => `writer.bytes(${val})`, - double: (val) => `writer.double(${val})`, - fixed32: (val) => `writer.fixed32(${val})`, - fixed64: (val) => `writer.fixed64(${val})`, - float: (val) => `writer.float(${val})`, - int32: (val) => `writer.int32(${val})`, - int64: (val) => `writer.int64(${val})`, - sfixed32: (val) => `writer.sfixed32(${val})`, - sfixed64: (val) => `writer.sfixed64(${val})`, - sint32: (val) => `writer.sint32(${val})`, - sint64: (val) => `writer.sint64(${val})`, - string: (val) => `writer.string(${val})`, - uint32: (val) => `writer.uint32(${val})`, - uint64: (val) => `writer.uint64(${val})` + bool: (val) => `w.bool(${val})`, + bytes: (val) => `w.bytes(${val})`, + double: (val) => `w.double(${val})`, + fixed32: (val) => `w.fixed32(${val})`, + fixed64: (val) => `w.fixed64(${val})`, + float: (val) => `w.float(${val})`, + int32: (val) => `w.int32(${val})`, + int64: (val) => `w.int64(${val})`, + sfixed32: (val) => `w.sfixed32(${val})`, + sfixed64: (val) => `w.sfixed64(${val})`, + sint32: (val) => `w.sint32(${val})`, + sint64: (val) => `w.sint64(${val})`, + string: (val) => `w.string(${val})`, + uint32: (val) => `w.uint32(${val})`, + uint64: (val) => `w.uint64(${val})` } const decoderGenerators: Record string> = { @@ -89,6 +89,24 @@ const defaultValueGenerators: Record string> = { uint64: () => '0n' } +const defaultValueTestGenerators: Record string> = { + bool: (field) => `${field} !== false`, + bytes: (field) => `(${field} != null && ${field}.byteLength > 0)`, + double: (field) => `${field} !== 0`, + fixed32: (field) => `${field} !== 0`, + fixed64: (field) => `${field} !== 0n`, + float: (field) => `${field} !== 0`, + int32: (field) => `${field} !== 0`, + int64: (field) => `${field} !== 0n`, + sfixed32: (field) => `${field} !== 0`, + sfixed64: (field) => `${field} !== 0n`, + sint32: (field) => `${field} !== 0`, + sint64: (field) => `${field} !== 0n`, + string: (field) => `${field} !== ''`, + uint32: (field) => `${field} !== 0`, + uint64: (field) => `${field} !== 0n` +} + function findTypeName (typeName: string, classDef: MessageDef, moduleDef: ModuleDef): string { if (types[typeName] != null) { return types[typeName] @@ -341,32 +359,20 @@ export interface ${messageDef.name} { return '' }).filter(Boolean).join('\n') - const ensureRequiredFields = Object.entries(fields) - .map(([name, fieldDef]) => { - // make sure required fields are set - 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') - }` - } - - return '' - }).filter(Boolean).join('\n') - interfaceCodecDef = ` let _codec: Codec<${messageDef.name}> export const codec = (): Codec<${messageDef.name}> => { if (_codec == null) { - _codec = message<${messageDef.name}>((obj, writer, opts = {}) => { + _codec = message<${messageDef.name}>((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } ${Object.entries(fields) .map(([name, fieldDef]) => { let codec: string = encoders[fieldDef.type] let type: string = fieldDef.type + let typeName: string = '' if (codec == null) { const def = findDef(fieldDef.type, messageDef, moduleDef) @@ -379,31 +385,84 @@ ${Object.entries(fields) type = 'message' } - const typeName = findTypeName(fieldDef.type, messageDef, moduleDef) + typeName = findTypeName(fieldDef.type, messageDef, moduleDef) codec = `${typeName}.codec()` } - return ` - if (obj.${name} != null) {${ - fieldDef.rule === 'repeated' -? ` - for (const value of obj.${name}) { - writer.uint32(${(fieldDef.id << 3) | codecTypes[type]}) - ${encoderGenerators[type] == null ? `${codec}.encode(value, writer)` : encoderGenerators[type]('value')} + let valueTest = `obj.${name} != null` + + // proto3 singular fields should only be written out if they are not the default value + if (!fieldDef.optional && !fieldDef.repeated) { + if (defaultValueTestGenerators[type] != null) { + valueTest = `opts.writeDefaults === true || ${defaultValueTestGenerators[type](`obj.${name}`)}` + } else if (type === 'enum') { + // handle enums + valueTest = `opts.writeDefaults === true || (obj.${name} != null && __${fieldDef.type}Values[obj.${name}] !== 0)` + } + } + + function createWriteField (valueVar: string) { + const id = (fieldDef.id << 3) | codecTypes[type] + + let writeField = `w.uint32(${id}) + ${encoderGenerators[type] == null ? `${codec}.encode(${valueVar}, w)` : encoderGenerators[type](valueVar)}` + + if (type === 'message') { + // message fields are only written if they have values + moduleDef.imports.add('writer') + + writeField = `const mw = writer() + ${typeName}.codec().encode(${valueVar}, mw, { + lengthDelimited: false, + writeDefaults: ${Boolean(fieldDef.repeated).toString()} + }) + const buf = mw.finish()` + + if (fieldDef.repeated) { + writeField += ` + + w.uint32(${id}) + w.bytes(buf)` + } else { + writeField += ` + + if (buf.byteLength > 0) { + w.uint32(${id}) + w.bytes(buf) }` -: ` - writer.uint32(${(fieldDef.id << 3) | codecTypes[type]}) - ${encoderGenerators[type] == null ? `${codec}.encode(obj.${name}, writer)` : encoderGenerators[type](`obj.${name}`)}` + } + } + + return writeField } - }${fieldDef.optional -? '' -: ` else { - throw new Error('Protocol error: required field "${name}" was not found in object') - }`}` + + let writeField = createWriteField(`obj.${name}`) + + if (fieldDef.repeated) { + writeField = ` + for (const value of obj.${name}) { + ${ + createWriteField('value') + .split('\n') + .map(s => { + const trimmed = s.trim() + + return trimmed === '' ? trimmed : ` ${s}` + }) + .join('\n') + } + } + `.trim() + } + + return ` + if (${valueTest}) { + ${writeField} + }` }).join('\n')} if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = {${createDefaultObject(fields, messageDef, moduleDef)}} @@ -448,7 +507,7 @@ ${Object.entries(fields) reader.skipType(tag & 7) break } - }${ensureArrayProps !== '' ? `\n\n${ensureArrayProps}` : ''}${ensureRequiredFields !== '' ? `\n${ensureRequiredFields}` : ''} + }${ensureArrayProps !== '' ? `\n\n${ensureArrayProps}` : ''} return obj }) @@ -551,7 +610,9 @@ export async function generate (source: string, flags: Flags) { let lines = [ '/* eslint-disable import/export */', + '/* eslint-disable complexity */', '/* eslint-disable @typescript-eslint/no-namespace */', + '/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */', '' ] @@ -574,6 +635,7 @@ export async function generate (source: string, flags: Flags) { ] const content = lines.join('\n').trim() + const outputPath = pathWithExtension(source, '.ts', flags.output) - await fs.writeFile(pathWithExtension(source, '.ts', flags.output), content + '\n') + await fs.writeFile(outputPath, content + '\n') } diff --git a/packages/protons/test/fixtures/basic.ts b/packages/protons/test/fixtures/basic.ts index 5e6a1a2..49f18b2 100644 --- a/packages/protons/test/fixtures/basic.ts +++ b/packages/protons/test/fixtures/basic.ts @@ -1,5 +1,7 @@ /* eslint-disable import/export */ +/* eslint-disable complexity */ /* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ import { encodeMessage, decodeMessage, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -15,25 +17,23 @@ export namespace Basic { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } if (obj.foo != null) { - writer.uint32(10) - writer.string(obj.foo) + w.uint32(10) + w.string(obj.foo) } - if (obj.num != null) { - writer.uint32(16) - writer.int32(obj.num) - } else { - throw new Error('Protocol error: required field "num" was not found in object') + if (opts.writeDefaults === true || obj.num !== 0) { + w.uint32(16) + w.int32(obj.num) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -58,10 +58,6 @@ export namespace Basic { } } - if (obj.num == null) { - throw new Error('Protocol error: value for required field "num" was not found in protobuf') - } - return obj }) } diff --git a/packages/protons/test/fixtures/circuit.ts b/packages/protons/test/fixtures/circuit.ts index 6567544..65ab343 100644 --- a/packages/protons/test/fixtures/circuit.ts +++ b/packages/protons/test/fixtures/circuit.ts @@ -1,7 +1,9 @@ /* eslint-disable import/export */ +/* eslint-disable complexity */ /* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ -import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime' +import { enumeration, encodeMessage, decodeMessage, message, writer } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' import type { Codec } from 'protons-runtime' @@ -87,29 +89,25 @@ export namespace CircuitRelay { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.id != null) { - writer.uint32(10) - writer.bytes(obj.id) - } else { - throw new Error('Protocol error: required field "id" was not found in object') + if (opts.writeDefaults === true || (obj.id != null && obj.id.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.id) } if (obj.addrs != null) { for (const value of obj.addrs) { - writer.uint32(18) - writer.bytes(value) + w.uint32(18) + w.bytes(value) } - } else { - throw new Error('Protocol error: required field "addrs" was not found in object') } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -135,10 +133,6 @@ export namespace CircuitRelay { } } - if (obj.id == null) { - throw new Error('Protocol error: value for required field "id" was not found in protobuf') - } - return obj }) } @@ -159,33 +153,51 @@ export namespace CircuitRelay { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } if (obj.type != null) { - writer.uint32(8) - CircuitRelay.Type.codec().encode(obj.type, writer) + w.uint32(8) + CircuitRelay.Type.codec().encode(obj.type, w) } if (obj.srcPeer != null) { - writer.uint32(18) - CircuitRelay.Peer.codec().encode(obj.srcPeer, writer) + const mw = writer() + CircuitRelay.Peer.codec().encode(obj.srcPeer, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(18) + w.bytes(buf) + } } if (obj.dstPeer != null) { - writer.uint32(26) - CircuitRelay.Peer.codec().encode(obj.dstPeer, writer) + const mw = writer() + CircuitRelay.Peer.codec().encode(obj.dstPeer, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(26) + w.bytes(buf) + } } if (obj.code != null) { - writer.uint32(32) - CircuitRelay.Status.codec().encode(obj.code, writer) + w.uint32(32) + CircuitRelay.Status.codec().encode(obj.code, w) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = {} diff --git a/packages/protons/test/fixtures/daemon.proto b/packages/protons/test/fixtures/daemon.proto index 5a79381..65d5bbc 100644 --- a/packages/protons/test/fixtures/daemon.proto +++ b/packages/protons/test/fixtures/daemon.proto @@ -14,7 +14,7 @@ message Request { PEERSTORE = 9; } - required Type type = 1; + Type type = 1; optional ConnectRequest connect = 2; optional StreamOpenRequest streamOpen = 3; @@ -32,7 +32,7 @@ message Response { ERROR = 1; } - required Type type = 1; + Type type = 1; optional ErrorResponse error = 2; optional StreamInfo streamInfo = 3; optional IdentifyResponse identify = 4; @@ -43,35 +43,35 @@ message Response { } message IdentifyResponse { - required bytes id = 1; + bytes id = 1; repeated bytes addrs = 2; } message ConnectRequest { - required bytes peer = 1; + bytes peer = 1; repeated bytes addrs = 2; optional int64 timeout = 3; } message StreamOpenRequest { - required bytes peer = 1; + bytes peer = 1; repeated string proto = 2; optional int64 timeout = 3; } message StreamHandlerRequest { - required bytes addr = 1; + bytes addr = 1; repeated string proto = 2; } message ErrorResponse { - required string msg = 1; + string msg = 1; } message StreamInfo { - required bytes peer = 1; - required bytes addr = 2; - required string proto = 3; + bytes peer = 1; + bytes addr = 2; + string proto = 3; } message DHTRequest { @@ -87,7 +87,7 @@ message DHTRequest { PROVIDE = 8; } - required Type type = 1; + Type type = 1; optional bytes peer = 2; optional bytes cid = 3; optional bytes key = 4; @@ -103,13 +103,13 @@ message DHTResponse { END = 2; } - required Type type = 1; + Type type = 1; optional PeerInfo peer = 2; optional bytes value = 3; } message PeerInfo { - required bytes id = 1; + bytes id = 1; repeated bytes addrs = 2; } @@ -120,7 +120,7 @@ message ConnManagerRequest { TRIM = 2; } - required Type type = 1; + Type type = 1; optional bytes peer = 2; optional string tag = 3; @@ -128,7 +128,7 @@ message ConnManagerRequest { } message DisconnectRequest { - required bytes peer = 1; + bytes peer = 1; } message PSRequest { @@ -139,7 +139,7 @@ message PSRequest { SUBSCRIBE = 3; } - required Type type = 1; + Type type = 1; optional string topic = 2; optional bytes data = 3; } @@ -160,11 +160,12 @@ message PSResponse { message PeerstoreRequest { enum Type { + INVALID = 0; GET_PROTOCOLS = 1; GET_PEER_INFO = 2; } - required Type type = 1; + Type type = 1; optional bytes id = 2; repeated string protos = 3; } diff --git a/packages/protons/test/fixtures/daemon.ts b/packages/protons/test/fixtures/daemon.ts index 32080ca..ac984b5 100644 --- a/packages/protons/test/fixtures/daemon.ts +++ b/packages/protons/test/fixtures/daemon.ts @@ -1,7 +1,9 @@ /* eslint-disable import/export */ +/* eslint-disable complexity */ /* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ -import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime' +import { enumeration, encodeMessage, decodeMessage, message, writer } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' import type { Codec } from 'protons-runtime' @@ -54,60 +56,130 @@ export namespace Request { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.type != null) { - writer.uint32(8) - Request.Type.codec().encode(obj.type, writer) - } else { - throw new Error('Protocol error: required field "type" was not found in object') + if (opts.writeDefaults === true || (obj.type != null && __TypeValues[obj.type] !== 0)) { + w.uint32(8) + Request.Type.codec().encode(obj.type, w) } if (obj.connect != null) { - writer.uint32(18) - ConnectRequest.codec().encode(obj.connect, writer) + const mw = writer() + ConnectRequest.codec().encode(obj.connect, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(18) + w.bytes(buf) + } } if (obj.streamOpen != null) { - writer.uint32(26) - StreamOpenRequest.codec().encode(obj.streamOpen, writer) + const mw = writer() + StreamOpenRequest.codec().encode(obj.streamOpen, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(26) + w.bytes(buf) + } } if (obj.streamHandler != null) { - writer.uint32(34) - StreamHandlerRequest.codec().encode(obj.streamHandler, writer) + const mw = writer() + StreamHandlerRequest.codec().encode(obj.streamHandler, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(34) + w.bytes(buf) + } } if (obj.dht != null) { - writer.uint32(42) - DHTRequest.codec().encode(obj.dht, writer) + const mw = writer() + DHTRequest.codec().encode(obj.dht, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(42) + w.bytes(buf) + } } if (obj.connManager != null) { - writer.uint32(50) - ConnManagerRequest.codec().encode(obj.connManager, writer) + const mw = writer() + ConnManagerRequest.codec().encode(obj.connManager, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(50) + w.bytes(buf) + } } if (obj.disconnect != null) { - writer.uint32(58) - DisconnectRequest.codec().encode(obj.disconnect, writer) + const mw = writer() + DisconnectRequest.codec().encode(obj.disconnect, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(58) + w.bytes(buf) + } } if (obj.pubsub != null) { - writer.uint32(66) - PSRequest.codec().encode(obj.pubsub, writer) + const mw = writer() + PSRequest.codec().encode(obj.pubsub, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(66) + w.bytes(buf) + } } if (obj.peerStore != null) { - writer.uint32(74) - PeerstoreRequest.codec().encode(obj.peerStore, writer) + const mw = writer() + PeerstoreRequest.codec().encode(obj.peerStore, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(74) + w.bytes(buf) + } } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -153,10 +225,6 @@ export namespace Request { } } - if (obj.type == null) { - throw new Error('Protocol error: value for required field "type" was not found in protobuf') - } - return obj }) } @@ -205,59 +273,116 @@ export namespace Response { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.type != null) { - writer.uint32(8) - Response.Type.codec().encode(obj.type, writer) - } else { - throw new Error('Protocol error: required field "type" was not found in object') + if (opts.writeDefaults === true || (obj.type != null && __TypeValues[obj.type] !== 0)) { + w.uint32(8) + Response.Type.codec().encode(obj.type, w) } if (obj.error != null) { - writer.uint32(18) - ErrorResponse.codec().encode(obj.error, writer) + const mw = writer() + ErrorResponse.codec().encode(obj.error, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(18) + w.bytes(buf) + } } if (obj.streamInfo != null) { - writer.uint32(26) - StreamInfo.codec().encode(obj.streamInfo, writer) + const mw = writer() + StreamInfo.codec().encode(obj.streamInfo, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(26) + w.bytes(buf) + } } if (obj.identify != null) { - writer.uint32(34) - IdentifyResponse.codec().encode(obj.identify, writer) + const mw = writer() + IdentifyResponse.codec().encode(obj.identify, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(34) + w.bytes(buf) + } } if (obj.dht != null) { - writer.uint32(42) - DHTResponse.codec().encode(obj.dht, writer) + const mw = writer() + DHTResponse.codec().encode(obj.dht, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(42) + w.bytes(buf) + } } if (obj.peers != null) { for (const value of obj.peers) { - writer.uint32(50) - PeerInfo.codec().encode(value, writer) + const mw = writer() + PeerInfo.codec().encode(value, mw, { + lengthDelimited: false, + writeDefaults: true + }) + const buf = mw.finish() + + w.uint32(50) + w.bytes(buf) } - } else { - throw new Error('Protocol error: required field "peers" was not found in object') } if (obj.pubsub != null) { - writer.uint32(58) - PSResponse.codec().encode(obj.pubsub, writer) + const mw = writer() + PSResponse.codec().encode(obj.pubsub, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(58) + w.bytes(buf) + } } if (obj.peerStore != null) { - writer.uint32(66) - PeerstoreResponse.codec().encode(obj.peerStore, writer) + const mw = writer() + PeerstoreResponse.codec().encode(obj.peerStore, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(66) + w.bytes(buf) + } } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -301,10 +426,6 @@ export namespace Response { } } - if (obj.type == null) { - throw new Error('Protocol error: value for required field "type" was not found in protobuf') - } - return obj }) } @@ -331,29 +452,25 @@ export namespace IdentifyResponse { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.id != null) { - writer.uint32(10) - writer.bytes(obj.id) - } else { - throw new Error('Protocol error: required field "id" was not found in object') + if (opts.writeDefaults === true || (obj.id != null && obj.id.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.id) } if (obj.addrs != null) { for (const value of obj.addrs) { - writer.uint32(18) - writer.bytes(value) + w.uint32(18) + w.bytes(value) } - } else { - throw new Error('Protocol error: required field "addrs" was not found in object') } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -379,10 +496,6 @@ export namespace IdentifyResponse { } } - if (obj.id == null) { - throw new Error('Protocol error: value for required field "id" was not found in protobuf') - } - return obj }) } @@ -410,34 +523,30 @@ export namespace ConnectRequest { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.peer != null) { - writer.uint32(10) - writer.bytes(obj.peer) - } else { - throw new Error('Protocol error: required field "peer" was not found in object') + if (opts.writeDefaults === true || (obj.peer != null && obj.peer.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.peer) } if (obj.addrs != null) { for (const value of obj.addrs) { - writer.uint32(18) - writer.bytes(value) + w.uint32(18) + w.bytes(value) } - } else { - throw new Error('Protocol error: required field "addrs" was not found in object') } if (obj.timeout != null) { - writer.uint32(24) - writer.int64(obj.timeout) + w.uint32(24) + w.int64(obj.timeout) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -466,10 +575,6 @@ export namespace ConnectRequest { } } - if (obj.peer == null) { - throw new Error('Protocol error: value for required field "peer" was not found in protobuf') - } - return obj }) } @@ -497,34 +602,30 @@ export namespace StreamOpenRequest { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.peer != null) { - writer.uint32(10) - writer.bytes(obj.peer) - } else { - throw new Error('Protocol error: required field "peer" was not found in object') + if (opts.writeDefaults === true || (obj.peer != null && obj.peer.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.peer) } if (obj.proto != null) { for (const value of obj.proto) { - writer.uint32(18) - writer.string(value) + w.uint32(18) + w.string(value) } - } else { - throw new Error('Protocol error: required field "proto" was not found in object') } if (obj.timeout != null) { - writer.uint32(24) - writer.int64(obj.timeout) + w.uint32(24) + w.int64(obj.timeout) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -553,10 +654,6 @@ export namespace StreamOpenRequest { } } - if (obj.peer == null) { - throw new Error('Protocol error: value for required field "peer" was not found in protobuf') - } - return obj }) } @@ -583,29 +680,25 @@ export namespace StreamHandlerRequest { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.addr != null) { - writer.uint32(10) - writer.bytes(obj.addr) - } else { - throw new Error('Protocol error: required field "addr" was not found in object') + if (opts.writeDefaults === true || (obj.addr != null && obj.addr.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.addr) } if (obj.proto != null) { for (const value of obj.proto) { - writer.uint32(18) - writer.string(value) + w.uint32(18) + w.string(value) } - } else { - throw new Error('Protocol error: required field "proto" was not found in object') } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -631,10 +724,6 @@ export namespace StreamHandlerRequest { } } - if (obj.addr == null) { - throw new Error('Protocol error: value for required field "addr" was not found in protobuf') - } - return obj }) } @@ -660,20 +749,18 @@ export namespace ErrorResponse { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.msg != null) { - writer.uint32(10) - writer.string(obj.msg) - } else { - throw new Error('Protocol error: required field "msg" was not found in object') + if (opts.writeDefaults === true || obj.msg !== '') { + w.uint32(10) + w.string(obj.msg) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -695,10 +782,6 @@ export namespace ErrorResponse { } } - if (obj.msg == null) { - throw new Error('Protocol error: value for required field "msg" was not found in protobuf') - } - return obj }) } @@ -726,34 +809,28 @@ export namespace StreamInfo { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.peer != null) { - writer.uint32(10) - writer.bytes(obj.peer) - } else { - throw new Error('Protocol error: required field "peer" was not found in object') + if (opts.writeDefaults === true || (obj.peer != null && obj.peer.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.peer) } - if (obj.addr != null) { - writer.uint32(18) - writer.bytes(obj.addr) - } else { - throw new Error('Protocol error: required field "addr" was not found in object') + if (opts.writeDefaults === true || (obj.addr != null && obj.addr.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.addr) } - if (obj.proto != null) { - writer.uint32(26) - writer.string(obj.proto) - } else { - throw new Error('Protocol error: required field "proto" was not found in object') + if (opts.writeDefaults === true || obj.proto !== '') { + w.uint32(26) + w.string(obj.proto) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -783,18 +860,6 @@ export namespace StreamInfo { } } - if (obj.peer == null) { - throw new Error('Protocol error: value for required field "peer" was not found in protobuf') - } - - 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 }) } @@ -856,50 +921,48 @@ export namespace DHTRequest { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.type != null) { - writer.uint32(8) - DHTRequest.Type.codec().encode(obj.type, writer) - } else { - throw new Error('Protocol error: required field "type" was not found in object') + if (opts.writeDefaults === true || (obj.type != null && __TypeValues[obj.type] !== 0)) { + w.uint32(8) + DHTRequest.Type.codec().encode(obj.type, w) } if (obj.peer != null) { - writer.uint32(18) - writer.bytes(obj.peer) + w.uint32(18) + w.bytes(obj.peer) } if (obj.cid != null) { - writer.uint32(26) - writer.bytes(obj.cid) + w.uint32(26) + w.bytes(obj.cid) } if (obj.key != null) { - writer.uint32(34) - writer.bytes(obj.key) + w.uint32(34) + w.bytes(obj.key) } if (obj.value != null) { - writer.uint32(42) - writer.bytes(obj.value) + w.uint32(42) + w.bytes(obj.value) } if (obj.count != null) { - writer.uint32(48) - writer.int32(obj.count) + w.uint32(48) + w.int32(obj.count) } if (obj.timeout != null) { - writer.uint32(56) - writer.int64(obj.timeout) + w.uint32(56) + w.int64(obj.timeout) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -939,10 +1002,6 @@ export namespace DHTRequest { } } - if (obj.type == null) { - throw new Error('Protocol error: value for required field "type" was not found in protobuf') - } - return obj }) } @@ -988,30 +1047,37 @@ export namespace DHTResponse { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.type != null) { - writer.uint32(8) - DHTResponse.Type.codec().encode(obj.type, writer) - } else { - throw new Error('Protocol error: required field "type" was not found in object') + if (opts.writeDefaults === true || (obj.type != null && __TypeValues[obj.type] !== 0)) { + w.uint32(8) + DHTResponse.Type.codec().encode(obj.type, w) } if (obj.peer != null) { - writer.uint32(18) - PeerInfo.codec().encode(obj.peer, writer) + const mw = writer() + PeerInfo.codec().encode(obj.peer, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(18) + w.bytes(buf) + } } if (obj.value != null) { - writer.uint32(26) - writer.bytes(obj.value) + w.uint32(26) + w.bytes(obj.value) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -1039,10 +1105,6 @@ export namespace DHTResponse { } } - if (obj.type == null) { - throw new Error('Protocol error: value for required field "type" was not found in protobuf') - } - return obj }) } @@ -1069,29 +1131,25 @@ export namespace PeerInfo { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.id != null) { - writer.uint32(10) - writer.bytes(obj.id) - } else { - throw new Error('Protocol error: required field "id" was not found in object') + if (opts.writeDefaults === true || (obj.id != null && obj.id.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.id) } if (obj.addrs != null) { for (const value of obj.addrs) { - writer.uint32(18) - writer.bytes(value) + w.uint32(18) + w.bytes(value) } - } else { - throw new Error('Protocol error: required field "addrs" was not found in object') } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -1117,10 +1175,6 @@ export namespace PeerInfo { } } - if (obj.id == null) { - throw new Error('Protocol error: value for required field "id" was not found in protobuf') - } - return obj }) } @@ -1167,35 +1221,33 @@ export namespace ConnManagerRequest { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.type != null) { - writer.uint32(8) - ConnManagerRequest.Type.codec().encode(obj.type, writer) - } else { - throw new Error('Protocol error: required field "type" was not found in object') + if (opts.writeDefaults === true || (obj.type != null && __TypeValues[obj.type] !== 0)) { + w.uint32(8) + ConnManagerRequest.Type.codec().encode(obj.type, w) } if (obj.peer != null) { - writer.uint32(18) - writer.bytes(obj.peer) + w.uint32(18) + w.bytes(obj.peer) } if (obj.tag != null) { - writer.uint32(26) - writer.string(obj.tag) + w.uint32(26) + w.string(obj.tag) } if (obj.weight != null) { - writer.uint32(32) - writer.int64(obj.weight) + w.uint32(32) + w.int64(obj.weight) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -1226,10 +1278,6 @@ export namespace ConnManagerRequest { } } - if (obj.type == null) { - throw new Error('Protocol error: value for required field "type" was not found in protobuf') - } - return obj }) } @@ -1255,20 +1303,18 @@ export namespace DisconnectRequest { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.peer != null) { - writer.uint32(10) - writer.bytes(obj.peer) - } else { - throw new Error('Protocol error: required field "peer" was not found in object') + if (opts.writeDefaults === true || (obj.peer != null && obj.peer.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.peer) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -1290,10 +1336,6 @@ export namespace DisconnectRequest { } } - if (obj.peer == null) { - throw new Error('Protocol error: value for required field "peer" was not found in protobuf') - } - return obj }) } @@ -1341,30 +1383,28 @@ export namespace PSRequest { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.type != null) { - writer.uint32(8) - PSRequest.Type.codec().encode(obj.type, writer) - } else { - throw new Error('Protocol error: required field "type" was not found in object') + if (opts.writeDefaults === true || (obj.type != null && __TypeValues[obj.type] !== 0)) { + w.uint32(8) + PSRequest.Type.codec().encode(obj.type, w) } if (obj.topic != null) { - writer.uint32(18) - writer.string(obj.topic) + w.uint32(18) + w.string(obj.topic) } if (obj.data != null) { - writer.uint32(26) - writer.bytes(obj.data) + w.uint32(26) + w.bytes(obj.data) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -1392,10 +1432,6 @@ export namespace PSRequest { } } - if (obj.type == null) { - throw new Error('Protocol error: value for required field "type" was not found in protobuf') - } - return obj }) } @@ -1426,47 +1462,45 @@ export namespace PSMessage { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } if (obj.from != null) { - writer.uint32(10) - writer.bytes(obj.from) + w.uint32(10) + w.bytes(obj.from) } if (obj.data != null) { - writer.uint32(18) - writer.bytes(obj.data) + w.uint32(18) + w.bytes(obj.data) } if (obj.seqno != null) { - writer.uint32(26) - writer.bytes(obj.seqno) + w.uint32(26) + w.bytes(obj.seqno) } if (obj.topicIDs != null) { for (const value of obj.topicIDs) { - writer.uint32(34) - writer.string(value) + w.uint32(34) + w.string(value) } - } else { - throw new Error('Protocol error: required field "topicIDs" was not found in object') } if (obj.signature != null) { - writer.uint32(42) - writer.bytes(obj.signature) + w.uint32(42) + w.bytes(obj.signature) } if (obj.key != null) { - writer.uint32(50) - writer.bytes(obj.key) + w.uint32(50) + w.bytes(obj.key) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -1529,31 +1563,27 @@ export namespace PSResponse { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } if (obj.topics != null) { for (const value of obj.topics) { - writer.uint32(10) - writer.string(value) + w.uint32(10) + w.string(value) } - } else { - throw new Error('Protocol error: required field "topics" was not found in object') } if (obj.peerIDs != null) { for (const value of obj.peerIDs) { - writer.uint32(18) - writer.bytes(value) + w.uint32(18) + w.bytes(value) } - } else { - throw new Error('Protocol error: required field "peerIDs" was not found in object') } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -1603,11 +1633,13 @@ export interface PeerstoreRequest { export namespace PeerstoreRequest { export enum Type { + INVALID = 'INVALID', GET_PROTOCOLS = 'GET_PROTOCOLS', GET_PEER_INFO = 'GET_PEER_INFO' } enum __TypeValues { + INVALID = 0, GET_PROTOCOLS = 1, GET_PEER_INFO = 2 } @@ -1622,38 +1654,34 @@ export namespace PeerstoreRequest { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.type != null) { - writer.uint32(8) - PeerstoreRequest.Type.codec().encode(obj.type, writer) - } else { - throw new Error('Protocol error: required field "type" was not found in object') + if (opts.writeDefaults === true || (obj.type != null && __TypeValues[obj.type] !== 0)) { + w.uint32(8) + PeerstoreRequest.Type.codec().encode(obj.type, w) } if (obj.id != null) { - writer.uint32(18) - writer.bytes(obj.id) + w.uint32(18) + w.bytes(obj.id) } if (obj.protos != null) { for (const value of obj.protos) { - writer.uint32(26) - writer.string(value) + w.uint32(26) + w.string(value) } - } else { - throw new Error('Protocol error: required field "protos" was not found in object') } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { - type: Type.GET_PROTOCOLS, + type: Type.INVALID, protos: [] } @@ -1678,10 +1706,6 @@ export namespace PeerstoreRequest { } } - if (obj.type == null) { - throw new Error('Protocol error: value for required field "type" was not found in protobuf') - } - return obj }) } @@ -1708,27 +1732,34 @@ export namespace PeerstoreResponse { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } if (obj.peer != null) { - writer.uint32(10) - PeerInfo.codec().encode(obj.peer, writer) + const mw = writer() + PeerInfo.codec().encode(obj.peer, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(10) + w.bytes(buf) + } } if (obj.protos != null) { for (const value of obj.protos) { - writer.uint32(18) - writer.string(value) + w.uint32(18) + w.string(value) } - } else { - throw new Error('Protocol error: required field "protos" was not found in object') } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { diff --git a/packages/protons/test/fixtures/dht.ts b/packages/protons/test/fixtures/dht.ts index afbe4e2..8bfd4b0 100644 --- a/packages/protons/test/fixtures/dht.ts +++ b/packages/protons/test/fixtures/dht.ts @@ -1,7 +1,9 @@ /* eslint-disable import/export */ +/* eslint-disable complexity */ /* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ -import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runtime' +import { encodeMessage, decodeMessage, message, enumeration, writer } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' import type { Codec } from 'protons-runtime' @@ -18,38 +20,38 @@ export namespace Record { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } if (obj.key != null) { - writer.uint32(10) - writer.bytes(obj.key) + w.uint32(10) + w.bytes(obj.key) } if (obj.value != null) { - writer.uint32(18) - writer.bytes(obj.value) + w.uint32(18) + w.bytes(obj.value) } if (obj.author != null) { - writer.uint32(26) - writer.bytes(obj.author) + w.uint32(26) + w.bytes(obj.author) } if (obj.signature != null) { - writer.uint32(34) - writer.bytes(obj.signature) + w.uint32(34) + w.bytes(obj.signature) } if (obj.timeReceived != null) { - writer.uint32(42) - writer.string(obj.timeReceived) + w.uint32(42) + w.string(obj.timeReceived) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = {} @@ -162,32 +164,30 @@ export namespace Message { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } if (obj.id != null) { - writer.uint32(10) - writer.bytes(obj.id) + w.uint32(10) + w.bytes(obj.id) } if (obj.addrs != null) { for (const value of obj.addrs) { - writer.uint32(18) - writer.bytes(value) + w.uint32(18) + w.bytes(value) } - } else { - throw new Error('Protocol error: required field "addrs" was not found in object') } if (obj.connection != null) { - writer.uint32(24) - Message.ConnectionType.codec().encode(obj.connection, writer) + w.uint32(24) + Message.ConnectionType.codec().encode(obj.connection, w) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -235,51 +235,61 @@ export namespace Message { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } if (obj.type != null) { - writer.uint32(8) - Message.MessageType.codec().encode(obj.type, writer) + w.uint32(8) + Message.MessageType.codec().encode(obj.type, w) } if (obj.clusterLevelRaw != null) { - writer.uint32(80) - writer.int32(obj.clusterLevelRaw) + w.uint32(80) + w.int32(obj.clusterLevelRaw) } if (obj.key != null) { - writer.uint32(18) - writer.bytes(obj.key) + w.uint32(18) + w.bytes(obj.key) } if (obj.record != null) { - writer.uint32(26) - writer.bytes(obj.record) + w.uint32(26) + w.bytes(obj.record) } if (obj.closerPeers != null) { for (const value of obj.closerPeers) { - writer.uint32(66) - Message.Peer.codec().encode(value, writer) + const mw = writer() + Message.Peer.codec().encode(value, mw, { + lengthDelimited: false, + writeDefaults: true + }) + const buf = mw.finish() + + w.uint32(66) + w.bytes(buf) } - } else { - throw new Error('Protocol error: required field "closerPeers" was not found in object') } if (obj.providerPeers != null) { for (const value of obj.providerPeers) { - writer.uint32(74) - Message.Peer.codec().encode(value, writer) + const mw = writer() + Message.Peer.codec().encode(value, mw, { + lengthDelimited: false, + writeDefaults: true + }) + const buf = mw.finish() + + w.uint32(74) + w.bytes(buf) } - } else { - throw new Error('Protocol error: required field "providerPeers" was not found in object') } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { diff --git a/packages/protons/test/fixtures/noise.ts b/packages/protons/test/fixtures/noise.ts index 0d1af33..fb0138c 100644 --- a/packages/protons/test/fixtures/noise.ts +++ b/packages/protons/test/fixtures/noise.ts @@ -1,5 +1,7 @@ /* eslint-disable import/export */ +/* eslint-disable complexity */ /* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ import { encodeMessage, decodeMessage, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -17,34 +19,28 @@ export namespace pb { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.identityKey != null) { - writer.uint32(10) - writer.bytes(obj.identityKey) - } else { - throw new Error('Protocol error: required field "identityKey" was not found in object') + if (opts.writeDefaults === true || (obj.identityKey != null && obj.identityKey.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.identityKey) } - if (obj.identitySig != null) { - writer.uint32(18) - writer.bytes(obj.identitySig) - } else { - throw new Error('Protocol error: required field "identitySig" was not found in object') + if (opts.writeDefaults === true || (obj.identitySig != null && obj.identitySig.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.identitySig) } - if (obj.data != null) { - writer.uint32(26) - writer.bytes(obj.data) - } else { - throw new Error('Protocol error: required field "data" was not found in object') + if (opts.writeDefaults === true || (obj.data != null && obj.data.byteLength > 0)) { + w.uint32(26) + w.bytes(obj.data) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -74,18 +70,6 @@ export namespace pb { } } - if (obj.identityKey == null) { - throw new Error('Protocol error: value for required field "identityKey" was not found in protobuf') - } - - if (obj.identitySig == null) { - throw new Error('Protocol error: value for required field "identitySig" was not found in protobuf') - } - - if (obj.data == null) { - throw new Error('Protocol error: value for required field "data" was not found in protobuf') - } - return obj }) } diff --git a/packages/protons/test/fixtures/optional.proto b/packages/protons/test/fixtures/optional.proto new file mode 100644 index 0000000..afb2695 --- /dev/null +++ b/packages/protons/test/fixtures/optional.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +enum OptionalEnum { + NO_VALUE = 0; + VALUE_1 = 1; + VALUE_2 = 2; +} + +message OptionalSubMessage { + optional string foo = 1; + optional int32 bar = 2; +} + +message Optional { + optional double double = 1; + optional float float = 2; + optional int32 int32 = 3; + optional int64 int64 = 4; + optional uint32 uint32 = 5; + optional uint64 uint64 = 6; + optional sint32 sint32 = 7; + optional sint64 sint64 = 8; + optional fixed32 fixed32 = 9; + optional fixed64 fixed64 = 10; + optional sfixed32 sfixed32 = 11; + optional sfixed64 sfixed64 = 12; + optional bool bool = 13; + optional string string = 14; + optional bytes bytes = 15; + optional OptionalEnum enum = 16; + optional OptionalSubMessage subMessage = 17; +} diff --git a/packages/protons/test/fixtures/optional.ts b/packages/protons/test/fixtures/optional.ts new file mode 100644 index 0000000..d7f6678 --- /dev/null +++ b/packages/protons/test/fixtures/optional.ts @@ -0,0 +1,299 @@ +/* eslint-disable import/export */ +/* eslint-disable complexity */ +/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ + +import { enumeration, encodeMessage, decodeMessage, message, writer } from 'protons-runtime' +import type { Uint8ArrayList } from 'uint8arraylist' +import type { Codec } from 'protons-runtime' + +export enum OptionalEnum { + NO_VALUE = 'NO_VALUE', + VALUE_1 = 'VALUE_1', + VALUE_2 = 'VALUE_2' +} + +enum __OptionalEnumValues { + NO_VALUE = 0, + VALUE_1 = 1, + VALUE_2 = 2 +} + +export namespace OptionalEnum { + export const codec = () => { + return enumeration(__OptionalEnumValues) + } +} +export interface OptionalSubMessage { + foo?: string + bar?: number +} + +export namespace OptionalSubMessage { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (obj.foo != null) { + w.uint32(10) + w.string(obj.foo) + } + + if (obj.bar != null) { + w.uint32(16) + w.int32(obj.bar) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.foo = reader.string() + break + case 2: + obj.bar = reader.int32() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: OptionalSubMessage): Uint8Array => { + return encodeMessage(obj, OptionalSubMessage.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): OptionalSubMessage => { + return decodeMessage(buf, OptionalSubMessage.codec()) + } +} + +export interface Optional { + double?: number + float?: number + int32?: number + int64?: bigint + uint32?: number + uint64?: bigint + sint32?: number + sint64?: bigint + fixed32?: number + fixed64?: bigint + sfixed32?: number + sfixed64?: bigint + bool?: boolean + string?: string + bytes?: Uint8Array + enum?: OptionalEnum + subMessage?: OptionalSubMessage +} + +export namespace Optional { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (obj.double != null) { + w.uint32(9) + w.double(obj.double) + } + + if (obj.float != null) { + w.uint32(21) + w.float(obj.float) + } + + if (obj.int32 != null) { + w.uint32(24) + w.int32(obj.int32) + } + + if (obj.int64 != null) { + w.uint32(32) + w.int64(obj.int64) + } + + if (obj.uint32 != null) { + w.uint32(40) + w.uint32(obj.uint32) + } + + if (obj.uint64 != null) { + w.uint32(48) + w.uint64(obj.uint64) + } + + if (obj.sint32 != null) { + w.uint32(56) + w.sint32(obj.sint32) + } + + if (obj.sint64 != null) { + w.uint32(64) + w.sint64(obj.sint64) + } + + if (obj.fixed32 != null) { + w.uint32(77) + w.fixed32(obj.fixed32) + } + + if (obj.fixed64 != null) { + w.uint32(81) + w.fixed64(obj.fixed64) + } + + if (obj.sfixed32 != null) { + w.uint32(93) + w.sfixed32(obj.sfixed32) + } + + if (obj.sfixed64 != null) { + w.uint32(97) + w.sfixed64(obj.sfixed64) + } + + if (obj.bool != null) { + w.uint32(104) + w.bool(obj.bool) + } + + if (obj.string != null) { + w.uint32(114) + w.string(obj.string) + } + + if (obj.bytes != null) { + w.uint32(122) + w.bytes(obj.bytes) + } + + if (obj.enum != null) { + w.uint32(128) + OptionalEnum.codec().encode(obj.enum, w) + } + + if (obj.subMessage != null) { + const mw = writer() + OptionalSubMessage.codec().encode(obj.subMessage, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(138) + w.bytes(buf) + } + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.double = reader.double() + break + case 2: + obj.float = reader.float() + break + case 3: + obj.int32 = reader.int32() + break + case 4: + obj.int64 = reader.int64() + break + case 5: + obj.uint32 = reader.uint32() + break + case 6: + obj.uint64 = reader.uint64() + break + case 7: + obj.sint32 = reader.sint32() + break + case 8: + obj.sint64 = reader.sint64() + break + case 9: + obj.fixed32 = reader.fixed32() + break + case 10: + obj.fixed64 = reader.fixed64() + break + case 11: + obj.sfixed32 = reader.sfixed32() + break + case 12: + obj.sfixed64 = reader.sfixed64() + break + case 13: + obj.bool = reader.bool() + break + case 14: + obj.string = reader.string() + break + case 15: + obj.bytes = reader.bytes() + break + case 16: + obj.enum = OptionalEnum.codec().decode(reader) + break + case 17: + obj.subMessage = OptionalSubMessage.codec().decode(reader, reader.uint32()) + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Optional): Uint8Array => { + return encodeMessage(obj, Optional.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): Optional => { + return decodeMessage(buf, Optional.codec()) + } +} diff --git a/packages/protons/test/fixtures/peer.ts b/packages/protons/test/fixtures/peer.ts index f9357a3..e6aca15 100644 --- a/packages/protons/test/fixtures/peer.ts +++ b/packages/protons/test/fixtures/peer.ts @@ -1,7 +1,9 @@ /* eslint-disable import/export */ +/* eslint-disable complexity */ /* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ -import { encodeMessage, decodeMessage, message } from 'protons-runtime' +import { encodeMessage, decodeMessage, message, writer } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' import type { Codec } from 'protons-runtime' @@ -18,50 +20,58 @@ export namespace Peer { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } if (obj.addresses != null) { for (const value of obj.addresses) { - writer.uint32(10) - Address.codec().encode(value, writer) + const mw = writer() + Address.codec().encode(value, mw, { + lengthDelimited: false, + writeDefaults: true + }) + const buf = mw.finish() + + w.uint32(10) + w.bytes(buf) } - } else { - throw new Error('Protocol error: required field "addresses" was not found in object') } if (obj.protocols != null) { for (const value of obj.protocols) { - writer.uint32(18) - writer.string(value) + w.uint32(18) + w.string(value) } - } else { - throw new Error('Protocol error: required field "protocols" was not found in object') } if (obj.metadata != null) { for (const value of obj.metadata) { - writer.uint32(26) - Metadata.codec().encode(value, writer) + const mw = writer() + Metadata.codec().encode(value, mw, { + lengthDelimited: false, + writeDefaults: true + }) + const buf = mw.finish() + + w.uint32(26) + w.bytes(buf) } - } else { - throw new Error('Protocol error: required field "metadata" was not found in object') } if (obj.pubKey != null) { - writer.uint32(34) - writer.bytes(obj.pubKey) + w.uint32(34) + w.bytes(obj.pubKey) } if (obj.peerRecordEnvelope != null) { - writer.uint32(42) - writer.bytes(obj.peerRecordEnvelope) + w.uint32(42) + w.bytes(obj.peerRecordEnvelope) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -123,25 +133,23 @@ export namespace Address { export const codec = (): Codec
=> { if (_codec == null) { - _codec = message
((obj, writer, opts = {}) => { + _codec = message
((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.multiaddr != null) { - writer.uint32(10) - writer.bytes(obj.multiaddr) - } else { - throw new Error('Protocol error: required field "multiaddr" was not found in object') + if (opts.writeDefaults === true || (obj.multiaddr != null && obj.multiaddr.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.multiaddr) } if (obj.isCertified != null) { - writer.uint32(16) - writer.bool(obj.isCertified) + w.uint32(16) + w.bool(obj.isCertified) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -166,10 +174,6 @@ export namespace Address { } } - if (obj.multiaddr == null) { - throw new Error('Protocol error: value for required field "multiaddr" was not found in protobuf') - } - return obj }) } @@ -196,27 +200,23 @@ export namespace Metadata { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.key != null) { - writer.uint32(10) - writer.string(obj.key) - } else { - throw new Error('Protocol error: required field "key" was not found in object') + if (opts.writeDefaults === true || obj.key !== '') { + w.uint32(10) + w.string(obj.key) } - if (obj.value != null) { - writer.uint32(18) - writer.bytes(obj.value) - } else { - throw new Error('Protocol error: required field "value" was not found in object') + if (opts.writeDefaults === true || (obj.value != null && obj.value.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.value) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -242,14 +242,6 @@ export namespace Metadata { } } - if (obj.key == null) { - throw new Error('Protocol error: value for required field "key" was not found in protobuf') - } - - if (obj.value == null) { - throw new Error('Protocol error: value for required field "value" was not found in protobuf') - } - return obj }) } diff --git a/packages/protons/test/fixtures/singular.proto b/packages/protons/test/fixtures/singular.proto new file mode 100644 index 0000000..bd7efff --- /dev/null +++ b/packages/protons/test/fixtures/singular.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +enum SingularEnum { + NO_VALUE = 0; + VALUE_1 = 1; + VALUE_2 = 2; +} + +message SingularSubMessage { + string foo = 1; + int32 bar = 2; +} + +message Singular { + double double = 1; + float float = 2; + int32 int32 = 3; + int64 int64 = 4; + uint32 uint32 = 5; + uint64 uint64 = 6; + sint32 sint32 = 7; + sint64 sint64 = 8; + fixed32 fixed32 = 9; + fixed64 fixed64 = 10; + sfixed32 sfixed32 = 11; + sfixed64 sfixed64 = 12; + bool bool = 13; + string string = 14; + bytes bytes = 15; + SingularEnum enum = 16; + SingularSubMessage subMessage = 17; +} diff --git a/packages/protons/test/fixtures/singular.ts b/packages/protons/test/fixtures/singular.ts new file mode 100644 index 0000000..83ccb39 --- /dev/null +++ b/packages/protons/test/fixtures/singular.ts @@ -0,0 +1,320 @@ +/* eslint-disable import/export */ +/* eslint-disable complexity */ +/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ + +import { enumeration, encodeMessage, decodeMessage, message, writer } from 'protons-runtime' +import type { Uint8ArrayList } from 'uint8arraylist' +import type { Codec } from 'protons-runtime' + +export enum SingularEnum { + NO_VALUE = 'NO_VALUE', + VALUE_1 = 'VALUE_1', + VALUE_2 = 'VALUE_2' +} + +enum __SingularEnumValues { + NO_VALUE = 0, + VALUE_1 = 1, + VALUE_2 = 2 +} + +export namespace SingularEnum { + export const codec = () => { + return enumeration(__SingularEnumValues) + } +} +export interface SingularSubMessage { + foo: string + bar: number +} + +export namespace SingularSubMessage { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || obj.foo !== '') { + w.uint32(10) + w.string(obj.foo) + } + + if (opts.writeDefaults === true || obj.bar !== 0) { + w.uint32(16) + w.int32(obj.bar) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + foo: '', + bar: 0 + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.foo = reader.string() + break + case 2: + obj.bar = reader.int32() + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: SingularSubMessage): Uint8Array => { + return encodeMessage(obj, SingularSubMessage.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): SingularSubMessage => { + return decodeMessage(buf, SingularSubMessage.codec()) + } +} + +export interface Singular { + double: number + float: number + int32: number + int64: bigint + uint32: number + uint64: bigint + sint32: number + sint64: bigint + fixed32: number + fixed64: bigint + sfixed32: number + sfixed64: bigint + bool: boolean + string: string + bytes: Uint8Array + enum: SingularEnum + subMessage: SingularSubMessage +} + +export namespace Singular { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (opts.writeDefaults === true || obj.double !== 0) { + w.uint32(9) + w.double(obj.double) + } + + if (opts.writeDefaults === true || obj.float !== 0) { + w.uint32(21) + w.float(obj.float) + } + + if (opts.writeDefaults === true || obj.int32 !== 0) { + w.uint32(24) + w.int32(obj.int32) + } + + if (opts.writeDefaults === true || obj.int64 !== 0n) { + w.uint32(32) + w.int64(obj.int64) + } + + if (opts.writeDefaults === true || obj.uint32 !== 0) { + w.uint32(40) + w.uint32(obj.uint32) + } + + if (opts.writeDefaults === true || obj.uint64 !== 0n) { + w.uint32(48) + w.uint64(obj.uint64) + } + + if (opts.writeDefaults === true || obj.sint32 !== 0) { + w.uint32(56) + w.sint32(obj.sint32) + } + + if (opts.writeDefaults === true || obj.sint64 !== 0n) { + w.uint32(64) + w.sint64(obj.sint64) + } + + if (opts.writeDefaults === true || obj.fixed32 !== 0) { + w.uint32(77) + w.fixed32(obj.fixed32) + } + + if (opts.writeDefaults === true || obj.fixed64 !== 0n) { + w.uint32(81) + w.fixed64(obj.fixed64) + } + + if (opts.writeDefaults === true || obj.sfixed32 !== 0) { + w.uint32(93) + w.sfixed32(obj.sfixed32) + } + + if (opts.writeDefaults === true || obj.sfixed64 !== 0n) { + w.uint32(97) + w.sfixed64(obj.sfixed64) + } + + if (opts.writeDefaults === true || obj.bool !== false) { + w.uint32(104) + w.bool(obj.bool) + } + + if (opts.writeDefaults === true || obj.string !== '') { + w.uint32(114) + w.string(obj.string) + } + + if (opts.writeDefaults === true || (obj.bytes != null && obj.bytes.byteLength > 0)) { + w.uint32(122) + w.bytes(obj.bytes) + } + + if (opts.writeDefaults === true || (obj.enum != null && __SingularEnumValues[obj.enum] !== 0)) { + w.uint32(128) + SingularEnum.codec().encode(obj.enum, w) + } + + if (obj.subMessage != null) { + const mw = writer() + SingularSubMessage.codec().encode(obj.subMessage, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(138) + w.bytes(buf) + } + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + double: 0, + float: 0, + int32: 0, + int64: 0n, + uint32: 0, + uint64: 0n, + sint32: 0, + sint64: 0n, + fixed32: 0, + fixed64: 0n, + sfixed32: 0, + sfixed64: 0n, + bool: false, + string: '', + bytes: new Uint8Array(0), + enum: SingularEnum.NO_VALUE, + subMessage: undefined + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: + obj.double = reader.double() + break + case 2: + obj.float = reader.float() + break + case 3: + obj.int32 = reader.int32() + break + case 4: + obj.int64 = reader.int64() + break + case 5: + obj.uint32 = reader.uint32() + break + case 6: + obj.uint64 = reader.uint64() + break + case 7: + obj.sint32 = reader.sint32() + break + case 8: + obj.sint64 = reader.sint64() + break + case 9: + obj.fixed32 = reader.fixed32() + break + case 10: + obj.fixed64 = reader.fixed64() + break + case 11: + obj.sfixed32 = reader.sfixed32() + break + case 12: + obj.sfixed64 = reader.sfixed64() + break + case 13: + obj.bool = reader.bool() + break + case 14: + obj.string = reader.string() + break + case 15: + obj.bytes = reader.bytes() + break + case 16: + obj.enum = SingularEnum.codec().decode(reader) + break + case 17: + obj.subMessage = SingularSubMessage.codec().decode(reader, reader.uint32()) + break + default: + reader.skipType(tag & 7) + break + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Singular): Uint8Array => { + return encodeMessage(obj, Singular.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): Singular => { + return decodeMessage(buf, Singular.codec()) + } +} diff --git a/packages/protons/test/fixtures/test.ts b/packages/protons/test/fixtures/test.ts index 3ca17c9..fa55cc9 100644 --- a/packages/protons/test/fixtures/test.ts +++ b/packages/protons/test/fixtures/test.ts @@ -1,7 +1,9 @@ /* eslint-disable import/export */ +/* eslint-disable complexity */ /* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ -import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime' +import { enumeration, encodeMessage, decodeMessage, message, writer } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' import type { Codec } from 'protons-runtime' @@ -29,20 +31,18 @@ export namespace SubMessage { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.foo != null) { - writer.uint32(10) - writer.string(obj.foo) - } else { - throw new Error('Protocol error: required field "foo" was not found in object') + if (opts.writeDefaults === true || obj.foo !== '') { + w.uint32(10) + w.string(obj.foo) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -64,10 +64,6 @@ export namespace SubMessage { } } - if (obj.foo == null) { - throw new Error('Protocol error: value for required field "foo" was not found in protobuf') - } - return obj }) } @@ -110,107 +106,114 @@ export namespace AllTheTypes { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } if (obj.field1 != null) { - writer.uint32(8) - writer.bool(obj.field1) + w.uint32(8) + w.bool(obj.field1) } if (obj.field2 != null) { - writer.uint32(16) - writer.int32(obj.field2) + w.uint32(16) + w.int32(obj.field2) } if (obj.field3 != null) { - writer.uint32(24) - writer.int64(obj.field3) + w.uint32(24) + w.int64(obj.field3) } if (obj.field4 != null) { - writer.uint32(32) - writer.uint32(obj.field4) + w.uint32(32) + w.uint32(obj.field4) } if (obj.field5 != null) { - writer.uint32(40) - writer.uint64(obj.field5) + w.uint32(40) + w.uint64(obj.field5) } if (obj.field6 != null) { - writer.uint32(48) - writer.sint32(obj.field6) + w.uint32(48) + w.sint32(obj.field6) } if (obj.field7 != null) { - writer.uint32(56) - writer.sint64(obj.field7) + w.uint32(56) + w.sint64(obj.field7) } if (obj.field8 != null) { - writer.uint32(65) - writer.double(obj.field8) + w.uint32(65) + w.double(obj.field8) } if (obj.field9 != null) { - writer.uint32(77) - writer.float(obj.field9) + w.uint32(77) + w.float(obj.field9) } if (obj.field10 != null) { - writer.uint32(82) - writer.string(obj.field10) + w.uint32(82) + w.string(obj.field10) } if (obj.field11 != null) { - writer.uint32(90) - writer.bytes(obj.field11) + w.uint32(90) + w.bytes(obj.field11) } if (obj.field12 != null) { - writer.uint32(96) - AnEnum.codec().encode(obj.field12, writer) + w.uint32(96) + AnEnum.codec().encode(obj.field12, w) } if (obj.field13 != null) { - writer.uint32(106) - SubMessage.codec().encode(obj.field13, writer) + const mw = writer() + SubMessage.codec().encode(obj.field13, mw, { + lengthDelimited: false, + writeDefaults: false + }) + const buf = mw.finish() + + if (buf.byteLength > 0) { + w.uint32(106) + w.bytes(buf) + } } if (obj.field14 != null) { for (const value of obj.field14) { - writer.uint32(114) - writer.string(value) + w.uint32(114) + w.string(value) } - } else { - throw new Error('Protocol error: required field "field14" was not found in object') } if (obj.field15 != null) { - writer.uint32(125) - writer.fixed32(obj.field15) + w.uint32(125) + w.fixed32(obj.field15) } if (obj.field16 != null) { - writer.uint32(129) - writer.fixed64(obj.field16) + w.uint32(129) + w.fixed64(obj.field16) } if (obj.field17 != null) { - writer.uint32(141) - writer.sfixed32(obj.field17) + w.uint32(141) + w.sfixed32(obj.field17) } if (obj.field18 != null) { - writer.uint32(145) - writer.sfixed64(obj.field18) + w.uint32(145) + w.sfixed64(obj.field18) } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { diff --git a/packages/protons/test/index.spec.ts b/packages/protons/test/index.spec.ts index e6a8756..23e99a7 100644 --- a/packages/protons/test/index.spec.ts +++ b/packages/protons/test/index.spec.ts @@ -10,7 +10,8 @@ import protobufjs, { Type as PBType } from 'protobufjs' import { Peer } from './fixtures/peer.js' import { CircuitRelay } from './fixtures/circuit.js' import long from 'long' -import { alloc } from 'uint8arrays/alloc' +import { Optional, OptionalEnum } from './fixtures/optional.js' +import { Singular, SingularEnum } from './fixtures/singular.js' function longifyBigInts (obj: any) { const output = { @@ -38,14 +39,19 @@ describe('encode', () => { const schema = pbjs.parseSchema(fs.readFileSync('./test/fixtures/basic.proto', 'utf-8')).compile() const pbjsBuf = schema.encodeBasic(basic) + const protobufJsSchema = protobufjs.loadSync('./test/fixtures/basic.proto').lookupType('Basic') + const protobufJsBuf = protobufJsSchema.encode(protobufJsSchema.fromObject(basic)).finish() + const encoded = Basic.encode(basic).subarray() expect(encoded).to.equalBytes(pbjsBuf) + expect(encoded).to.equalBytes(protobufJsBuf) const decoded = Basic.decode(encoded) expect(decoded).to.deep.equal(basic) expect(Basic.decode(encoded)).to.deep.equal(basic) expect(Basic.decode(pbjsBuf)).to.deep.equal(basic) + expect(Basic.decode(protobufJsBuf)).to.deep.equal(basic) }) it('should encode all the types', () => { @@ -60,7 +66,7 @@ describe('encode', () => { field8: 1, field9: 1, field10: 'hello', - field11: alloc(3).map((_, i) => i), + field11: Uint8Array.from([0, 1, 2, 3]), field12: AnEnum.DERP, field13: { foo: 'bar' @@ -75,11 +81,16 @@ describe('encode', () => { const schema = pbjs.parseSchema(fs.readFileSync('./test/fixtures/test.proto', 'utf-8')).compile() const pbjsBuf = schema.encodeAllTheTypes(longifyBigInts(allTheTypes)) - const encoded = AllTheTypes.encode(allTheTypes).subarray() + const protobufJsSchema = protobufjs.loadSync('./test/fixtures/test.proto').lookupType('AllTheTypes') + const protobufJsBuf = protobufJsSchema.encode(protobufJsSchema.fromObject(longifyBigInts(allTheTypes))).finish() + + const encoded = AllTheTypes.encode(allTheTypes) expect(encoded).to.equalBytes(pbjsBuf) + expect(encoded).to.equalBytes(protobufJsBuf) expect(AllTheTypes.decode(encoded)).to.deep.equal(allTheTypes) expect(AllTheTypes.decode(pbjsBuf)).to.deep.equal(allTheTypes) + expect(AllTheTypes.decode(protobufJsBuf)).to.deep.equal(allTheTypes) }) it('should encode all the types with max numeric values', () => { @@ -103,11 +114,16 @@ describe('encode', () => { const schema = pbjs.parseSchema(fs.readFileSync('./test/fixtures/test.proto', 'utf-8')).compile() const pbjsBuf = schema.encodeAllTheTypes(longifyBigInts(allTheTypes)) + const protobufJsSchema = protobufjs.loadSync('./test/fixtures/test.proto').lookupType('AllTheTypes') + const protobufJsBuf = protobufJsSchema.encode(protobufJsSchema.fromObject(longifyBigInts(allTheTypes))).finish() + const encoded = AllTheTypes.encode(allTheTypes).subarray() expect(encoded).to.equalBytes(pbjsBuf) + expect(encoded).to.equalBytes(protobufJsBuf) expect(AllTheTypes.decode(encoded)).to.deep.equal(allTheTypes) expect(AllTheTypes.decode(pbjsBuf)).to.deep.equal(allTheTypes) + expect(AllTheTypes.decode(protobufJsBuf)).to.deep.equal(allTheTypes) }) it('should encode all the types with min numeric values', () => { @@ -131,8 +147,12 @@ describe('encode', () => { const schema = pbjs.parseSchema(fs.readFileSync('./test/fixtures/test.proto', 'utf-8')).compile() const pbjsBuf = schema.encodeAllTheTypes(longifyBigInts(allTheTypes)) + const protobufJsSchema = protobufjs.loadSync('./test/fixtures/test.proto').lookupType('AllTheTypes') + const protobufJsBuf = protobufJsSchema.encode(protobufJsSchema.fromObject(longifyBigInts(allTheTypes))).finish() + const encoded = AllTheTypes.encode(allTheTypes).subarray() expect(encoded).to.equalBytes(pbjsBuf) + expect(encoded).to.equalBytes(protobufJsBuf) expect(AllTheTypes.decode(encoded)).to.deep.equal(allTheTypes) expect(AllTheTypes.decode(pbjsBuf)).to.deep.equal(allTheTypes) @@ -154,11 +174,43 @@ describe('encode', () => { const schema = pbjs.parseSchema(fs.readFileSync('./test/fixtures/peer.proto', 'utf-8')).compile() const pbjsBuf = schema.encodePeer(peer) + const protobufJsSchema = protobufjs.loadSync('./test/fixtures/peer.proto').lookupType('Peer') + const protobufJsBuf = protobufJsSchema.encode(protobufJsSchema.fromObject(longifyBigInts(peer))).finish() + + const encoded = Peer.encode(peer).subarray() + expect(encoded).to.equalBytes(pbjsBuf) + expect(encoded).to.equalBytes(protobufJsBuf) + + expect(Peer.decode(encoded)).to.deep.equal(peer) + expect(Peer.decode(pbjsBuf)).to.deep.equal(peer) + }) + + it('decodes multiple sub messages with singular fields set to default values', () => { + const peer: Peer = { + protocols: [], + metadata: [], + addresses: [{ + multiaddr: new Uint8Array(), + isCertified: false + }, { + multiaddr: new Uint8Array(), + isCertified: false + }] + } + + const pbjsSchema = pbjs.parseSchema(fs.readFileSync('./test/fixtures/peer.proto', 'utf-8')).compile() + const pbjsBuf = pbjsSchema.encodePeer(peer) + + const protobufJsSchema = protobufjs.loadSync('./test/fixtures/peer.proto').lookupType('Peer') + const protobufJsBuf = protobufJsSchema.encode(protobufJsSchema.fromObject(peer)).finish() + const encoded = Peer.encode(peer).subarray() expect(encoded).to.equalBytes(pbjsBuf) + expect(encoded).to.equalBytes(protobufJsBuf) expect(Peer.decode(encoded)).to.deep.equal(peer) expect(Peer.decode(pbjsBuf)).to.deep.equal(peer) + expect(Peer.decode(protobufJsBuf)).to.deep.equal(peer) }) it('decodes enums with values that are not 0-n', () => { @@ -171,14 +223,13 @@ describe('encode', () => { // @ts-expect-error const PbCircuitRelay = root.nested.CircuitRelay as PBType - const pbufJsBuf = PbCircuitRelay.encode(PbCircuitRelay.fromObject(message)).finish() + const protobufJsBuf = PbCircuitRelay.encode(PbCircuitRelay.fromObject(message)).finish() const encoded = CircuitRelay.encode(message).subarray() - - expect(encoded).to.equalBytes(pbufJsBuf) + expect(encoded).to.equalBytes(protobufJsBuf) expect(CircuitRelay.decode(encoded)).to.deep.equal(message) - expect(CircuitRelay.decode(pbufJsBuf)).to.deep.equal(message) + expect(CircuitRelay.decode(protobufJsBuf)).to.deep.equal(message) }) it('supports optional fields', () => { @@ -196,7 +247,156 @@ describe('encode', () => { it('supports default fields', () => { const decoded = Basic.decode(Uint8Array.from([])) - // num is required + // num is singular expect(decoded).to.have.property('num', 0) }) + + it('does not write unset optional fields', () => { + const obj: Optional = { + + } + + const encoded = Optional.encode(obj) + expect(encoded).to.have.lengthOf(0) + + const decoded = Optional.decode(encoded) + expect(Object.keys(decoded)).to.be.empty() + + const protobufJsSchema = protobufjs.loadSync('./test/fixtures/optional.proto').lookupType('Optional') + const protobufJsBuf = protobufJsSchema.encode(protobufJsSchema.fromObject(obj)).finish() + expect(encoded).to.equalBytes(protobufJsBuf) + }) + + it('writes set optional fields', () => { + const obj: Optional = { + double: 1.0, + float: 1.0, + int32: 1, + int64: 1n, + uint32: 1, + uint64: 1n, + sint32: 1, + sint64: 1n, + fixed32: 1, + fixed64: 1n, + sfixed32: 1, + sfixed64: 1n, + bool: true, + string: 'hello', + bytes: Uint8Array.from([0, 1, 2]), + enum: OptionalEnum.VALUE_1, + subMessage: { + foo: 'hello', + bar: 2 + } + } + + const encoded = Optional.encode(obj) + const decoded = Optional.decode(encoded) + expect(decoded).to.deep.equal(obj) + + const protobufJsSchema = protobufjs.loadSync('./test/fixtures/optional.proto').lookupType('Optional') + const protobufJsBuf = protobufJsSchema.encode(protobufJsSchema.fromObject(longifyBigInts(obj))).finish() + expect(encoded).to.equalBytes(protobufJsBuf) + }) + + it('writes set optional fields set to default values', () => { + const obj: Optional = { + double: 0, + float: 0, + int32: 0, + int64: 0n, + uint32: 0, + uint64: 0n, + sint32: 0, + sint64: 0n, + fixed32: 0, + fixed64: 0n, + sfixed32: 0, + sfixed64: 0n, + bool: false, + string: '', + bytes: new Uint8Array(), + enum: OptionalEnum.NO_VALUE, + subMessage: { + foo: '', + bar: 0 + } + } + + const encoded = Optional.encode(obj) + const decoded = Optional.decode(encoded) + expect(decoded).to.deep.equal(obj) + + const protobufJsSchema = protobufjs.loadSync('./test/fixtures/optional.proto').lookupType('Optional') + const protobufJsBuf = protobufJsSchema.encode(protobufJsSchema.fromObject(longifyBigInts(obj))).finish() + expect(encoded).to.equalBytes(protobufJsBuf) + }) + + it('does not write singular field values when set to defaults', () => { + const obj: Singular = { + double: 0, + float: 0, + int32: 0, + int64: 0n, + uint32: 0, + uint64: 0n, + sint32: 0, + sint64: 0n, + fixed32: 0, + fixed64: 0n, + sfixed32: 0, + sfixed64: 0n, + bool: false, + string: '', + bytes: new Uint8Array(), + enum: SingularEnum.NO_VALUE, + subMessage: { + foo: '', + bar: 0 + } + } + + const encoded = Singular.encode(obj) + expect(encoded).to.have.lengthOf(0) + + // Cannot compare bytes - protobuf.js does not implement singular properly - https://github.com/protobufjs/protobuf.js/issues/1468#issuecomment-745177012 + // const protobufJsSchema = protobufjs.loadSync('./test/fixtures/singular.proto').lookupType('Singular') + // const protobufJsBuf = protobufJsSchema.encode(protobufJsSchema.fromObject(longifyBigInts(obj))).finish() + // expect(encoded).to.equalBytes(protobufJsBuf) + }) + + it('writes singular field values when not set to defaults', () => { + const obj: Singular = { + double: 1.0, + float: 1.0, + int32: 1, + int64: 1n, + uint32: 1, + uint64: 1n, + sint32: 1, + sint64: 1n, + fixed32: 1, + fixed64: 1n, + sfixed32: 1, + sfixed64: 1n, + bool: true, + string: 'hello', + bytes: Uint8Array.from([0, 1, 2]), + enum: SingularEnum.VALUE_1, + subMessage: { + foo: 'hello', + bar: 2 + } + } + + const encoded = Singular.encode(obj) + const decoded = Singular.decode(encoded) + expect(decoded).to.deep.equal(obj) + + // Cannot compare bytes - protobuf.js does not implement singular properly - https://github.com/protobufjs/protobuf.js/issues/1468#issuecomment-745177012 + // const protobufJsSchema = protobufjs.loadSync('./test/fixtures/singular.proto').lookupType('Singular') + // const protobufJsBuf = protobufJsSchema.encode(protobufJsSchema.fromObject(longifyBigInts(obj))).finish() + // expect(encoded).to.equalBytes(protobufJsBuf) + }) })