Skip to content

Commit 406d775

Browse files
authored
fix!: adhere more closely to the language guide for proto3 default values (#66)
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. BREAKING CHANGE: ts definitions will need to be generated from `.proto` files - singular message fields have become optional as message fields are always optional in proto3 fixes #43
1 parent 6ad33c5 commit 406d775

22 files changed

+1582
-727
lines changed

packages/protons-runtime/src/codec.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export enum CODEC_TYPES {
1212

1313
export interface EncodeOptions {
1414
lengthDelimited?: boolean
15+
writeDefaults?: boolean
1516
}
1617

1718
export interface EncodeFunction<T> {

packages/protons-runtime/src/codecs/enum.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function enumeration <T> (v: any): Codec<T> {
2020
}
2121

2222
const decode: DecodeFunction<number | string> = function enumDecode (reader) {
23-
const val = reader.uint32()
23+
const val = reader.int32()
2424

2525
return findValue(val)
2626
}
+3-19
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,9 @@
11
import type { Uint8ArrayList } from 'uint8arraylist'
22
import type { Codec } from './codec.js'
3-
import pb from 'protobufjs'
4-
5-
const Reader = pb.Reader
6-
7-
// monkey patch the reader to add native bigint support
8-
const methods = [
9-
'uint64', 'int64', 'sint64', 'fixed64', 'sfixed64'
10-
]
11-
methods.forEach(method => {
12-
// @ts-expect-error
13-
const original = Reader.prototype[method]
14-
// @ts-expect-error
15-
Reader.prototype[method] = function (): bigint {
16-
return BigInt(original.call(this).toString())
17-
}
18-
})
3+
import { reader } from './reader.js'
194

205
export function decodeMessage <T> (buf: Uint8Array | Uint8ArrayList, codec: Codec<T>): T {
21-
const reader = Reader.create(buf instanceof Uint8Array ? buf : buf.subarray())
6+
const r = reader(buf instanceof Uint8Array ? buf : buf.subarray())
227

23-
// @ts-expect-error
24-
return codec.decode(reader)
8+
return codec.decode(r)
259
}

packages/protons-runtime/src/encode.ts

+2-18
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,9 @@
11
import type { Codec } from './codec.js'
2-
import pb from 'protobufjs'
3-
4-
const Writer = pb.Writer
5-
6-
// monkey patch the writer to add native bigint support
7-
const methods = [
8-
'uint64', 'int64', 'sint64', 'fixed64', 'sfixed64'
9-
]
10-
methods.forEach(method => {
11-
// @ts-expect-error
12-
const original = Writer.prototype[method]
13-
// @ts-expect-error
14-
Writer.prototype[method] = function (val: bigint): pb.Writer {
15-
return original.call(this, val.toString())
16-
}
17-
})
2+
import { writer } from './writer.js'
183

194
export function encodeMessage <T> (message: T, codec: Codec<T>): Uint8Array {
20-
const w = Writer.create()
5+
const w = writer()
216

22-
// @ts-expect-error
237
codec.encode(message, w, {
248
lengthDelimited: false
259
})

packages/protons-runtime/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export {
1818

1919
export { enumeration } from './codecs/enum.js'
2020
export { message } from './codecs/message.js'
21+
export { reader } from './reader.js'
22+
export { writer } from './writer.js'
2123
export type { Codec, EncodeOptions } from './codec.js'
2224

2325
export interface Writer {
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import pb from 'protobufjs/minimal.js'
2+
import type { Reader } from './index.js'
3+
4+
const PBReader = pb.Reader
5+
6+
// monkey patch the reader to add native bigint support
7+
const methods = [
8+
'uint64', 'int64', 'sint64', 'fixed64', 'sfixed64'
9+
]
10+
methods.forEach(method => {
11+
// @ts-expect-error
12+
const original = PBReader.prototype[method]
13+
// @ts-expect-error
14+
PBReader.prototype[method] = function (): bigint {
15+
return BigInt(original.call(this).toString())
16+
}
17+
})
18+
19+
export function reader (buf: Uint8Array): Reader {
20+
// @ts-expect-error class is monkey patched
21+
return PBReader.create(buf)
22+
}
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import pb from 'protobufjs/minimal.js'
2+
import type { Writer } from './index.js'
3+
4+
const PBWriter = pb.Writer
5+
6+
// monkey patch the writer to add native bigint support
7+
const methods = [
8+
'uint64', 'int64', 'sint64', 'fixed64', 'sfixed64'
9+
]
10+
methods.forEach(method => {
11+
// @ts-expect-error
12+
const original = PBWriter.prototype[method]
13+
// @ts-expect-error
14+
PBWriter.prototype[method] = function (val: bigint): pb.Writer {
15+
return original.call(this, val.toString())
16+
}
17+
})
18+
19+
export function writer (): Writer {
20+
// @ts-expect-error class is monkey patched
21+
return PBWriter.create()
22+
}

packages/protons/README.md

+16
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
- [Install](#install)
1414
- [Usage](#usage)
15+
- [Differences from protobuf.js](#differences-from-protobufjs)
1516
- [Contribute](#contribute)
1617
- [License](#license)
1718
- [Contribute](#contribute-1)
@@ -59,6 +60,21 @@ console.info(decoded.message)
5960
// 'hello world'
6061
```
6162

63+
## Differences from protobuf.js
64+
65+
This module uses the internal reader/writer from `protobuf.js` as it is highly optimised and there's no point reinventing the wheel.
66+
67+
It does have one or two differences:
68+
69+
1. Supports `proto3` semantics only
70+
2. All 64 bit values are represented as `BigInt`s and not `Long`s (e.g. `int64`, `uint64`, `sint64` etc)
71+
3. Unset `optional` fields are set on the deserialized object forms as `undefined` instead of the default values
72+
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
73+
74+
## Missing features
75+
76+
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`.
77+
6278
## Contribute
6379

6480
Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/protons/issues)!

0 commit comments

Comments
 (0)