@pbwire/pbwire 0.1.0 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -26
- package/build.sh +1 -1
- package/lib/pbwire-compat.js +525 -0
- package/lib/pbwire.js +62 -2
- package/package.json +1 -1
- package/runtime/addon.c +286 -0
package/README.md
CHANGED
|
@@ -1,9 +1,36 @@
|
|
|
1
1
|
# pbwire Node.js binding
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
The runtime is `runtime/addon.c`, an N-API addon in C over `libpbwire.a`;
|
|
4
|
+
the `Schema`/`Message` surface is `lib/pbwire.js`. No npm dependencies.
|
|
5
|
+
|
|
6
|
+
Most code never touches that surface: generate a protobufjs-static-shaped
|
|
7
|
+
facade once (`make -C codegen gen-node`) and use it like the
|
|
8
|
+
library it mirrors — same `create`/`encode`/`decode`/`verify` calls,
|
|
9
|
+
same observable behavior, but the wire format stays in the C core:
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
const { Person } = require('./tests/gen/addressbook.js');
|
|
13
|
+
|
|
14
|
+
const msg = Person.create({name: 'alice', id: 7});
|
|
15
|
+
Person.verify(msg); // null: valid
|
|
16
|
+
const wire = Person.encode(msg).finish(); // Buffer
|
|
17
|
+
const back = Person.decode(wire);
|
|
18
|
+
back.name; // 'alice'
|
|
19
|
+
back.toJSON(); // {name: 'alice', id: 7, ...}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The generated file embeds its own `FileDescriptorSet`, so there is no
|
|
23
|
+
`.desc` to ship: `require` it and go. int64 reads as `BigInt`; `create`/
|
|
24
|
+
`fromObject` also take `number|string|Long-like`. `toObject()` keys are
|
|
25
|
+
camelCase (protobufjs convention); pass `{keepCase: true}` for proto
|
|
26
|
+
names. `fromObject` rejects bad shapes with `TypeError`; `verify`
|
|
27
|
+
returns the same reason strings as protobufjs. The one deliberate
|
|
28
|
+
difference: direct post-hoc writes of an implicit zero (`m.i32 = 0`)
|
|
29
|
+
are not marked present — set values through `create`/`fromObject` to
|
|
30
|
+
write explicit zeros.
|
|
31
|
+
|
|
32
|
+
The dynamic API is still there for gateways and schemas known only at
|
|
33
|
+
runtime:
|
|
7
34
|
|
|
8
35
|
```js
|
|
9
36
|
const { Schema } = require('./lib/pbwire');
|
|
@@ -11,18 +38,12 @@ const { Schema } = require('./lib/pbwire');
|
|
|
11
38
|
const schema = Schema.fromFile('tests/data/demo.desc');
|
|
12
39
|
const person = schema.message('demo.Person');
|
|
13
40
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const back = person.decode(wire);
|
|
22
|
-
back.get('name'); // 'alice'
|
|
23
|
-
back.get('inner').x; // 42
|
|
24
|
-
back.encode().equals(wire); // true: byte-exact re-encode
|
|
25
|
-
back.free();
|
|
41
|
+
const wire2 = person.encode({name: 'alice', id: 7, inner: {x: 42}});
|
|
42
|
+
const back2 = person.decode(wire2);
|
|
43
|
+
back2.get('name'); // 'alice'
|
|
44
|
+
back2.get('inner').x; // 42
|
|
45
|
+
back2.encode().equals(wire2); // true: byte-exact re-encode
|
|
46
|
+
back2.free();
|
|
26
47
|
```
|
|
27
48
|
|
|
28
49
|
## How it works
|
|
@@ -187,19 +208,37 @@ per-message finalizer.
|
|
|
187
208
|
- **No protoc plugin / code generation.** The schema is always loaded at run
|
|
188
209
|
time, like the Python binding: a `FileDescriptorSet` is the only schema
|
|
189
210
|
source, and there is no generated layout to keep in sync.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
211
|
+
Also exported: JSON/text format (`messageToJson`/`jsonToMessage` — the latter
|
|
212
|
+
with an optional ignore-unknown flag, `messageToText`/`textToMessage` — the
|
|
213
|
+
former with an optional print-unknown flag), field presence (`has`), and
|
|
214
|
+
delimited framing (`encodeDelimited`/`decodeDelimited`, returning
|
|
215
|
+
`{message, read}` so concatenated frames decode in a loop).
|
|
216
|
+
See `test/test_pbwire.js`.
|
|
217
|
+
|
|
218
|
+
## Conformance
|
|
219
|
+
|
|
220
|
+
`conformance/testee.js` drives the official `conformance_test_runner`
|
|
221
|
+
through this binding (`make conformance-node`):
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
CONFORMANCE SUITE PASSED: 2742 successes, 0 skipped, 2 expected failures,
|
|
225
|
+
0 unexpected failures.
|
|
226
|
+
CONFORMANCE SUITE PASSED: 445 successes, 0 skipped, 0 expected failures,
|
|
227
|
+
0 unexpected failures.
|
|
228
|
+
```
|
|
193
229
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
230
|
+
Binary input decodes to a tree (unknown fields survive, oneof collapses to
|
|
231
|
+
the last member) and every output renders that same tree; JSON input with
|
|
232
|
+
unknown fields only parses when the test category asks to ignore them, and
|
|
233
|
+
text output honors the runner's `print_unknown_fields` flag. The 2 expected
|
|
234
|
+
failures are the shared `conformance/failure_list_pbwire.txt` (tag varints
|
|
235
|
+
the reference C++ testee truncates identically); only JSPB answers `skipped`.
|
|
197
236
|
|
|
198
|
-
##
|
|
237
|
+
## Generated models
|
|
199
238
|
|
|
200
|
-
`protoc-gen-pbwire
|
|
239
|
+
`protoc-gen-pbwire` (in `../../codegen`) generates `matrix3.d.ts` interfaces
|
|
201
240
|
(for TypeScript) and a JSDoc/`@ts-check` `.js` with the same shapes plus the enum
|
|
202
241
|
constants, describing exactly the plain objects `MessageType.encode()` accepts
|
|
203
242
|
and `Message.get()` returns. The committed output is `tests/gen/` and
|
|
204
|
-
`examples/gen/`; the
|
|
205
|
-
See
|
|
243
|
+
`examples/gen/`; the generated test type-checks with `tsc --strict --checkJs`.
|
|
244
|
+
See `../../codegen/README.md`.
|
package/build.sh
CHANGED
|
@@ -46,4 +46,4 @@ if [ "${1:-all}" = build ]; then
|
|
|
46
46
|
fi
|
|
47
47
|
|
|
48
48
|
# --expose-gc so the finalizer test can force the collections it checks.
|
|
49
|
-
exec node --expose-gc --test "$here/test/test_pbwire.js" "$here/tests/
|
|
49
|
+
exec node --expose-gc --test "$here/test/test_pbwire.js" "$here/tests/generated_test.js" "$here/tests/compat_test.js"
|
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* pbwire compat runtime: the verify/fromObject/toObject engine every generated
|
|
4
|
+
* facade delegates to. Schemas are cached per embedded descriptor set, so any
|
|
5
|
+
* number of generated files share this module with no global-state surprises:
|
|
6
|
+
* one entry per distinct FileDescriptorSet, keyed by its own bytes.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { Schema } = require('./pbwire.js');
|
|
10
|
+
|
|
11
|
+
const _schemas = new Map();
|
|
12
|
+
function _type(descB64, full) {
|
|
13
|
+
let byName = _schemas.get(descB64);
|
|
14
|
+
if (byName === undefined) {
|
|
15
|
+
byName = new Map();
|
|
16
|
+
byName.set('', Schema.fromBytes(Buffer.from(descB64, 'base64')));
|
|
17
|
+
_schemas.set(descB64, byName);
|
|
18
|
+
}
|
|
19
|
+
let t = byName.get(full);
|
|
20
|
+
if (t === undefined) {
|
|
21
|
+
t = byName.get('').message(full);
|
|
22
|
+
byName.set(full, t);
|
|
23
|
+
}
|
|
24
|
+
return t;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function _isLongLike(v) {
|
|
28
|
+
return typeof v === 'object' && v !== null &&
|
|
29
|
+
Number.isInteger(v.low) && Number.isInteger(v.high);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function _longToBigInt(v) {
|
|
33
|
+
return (BigInt(v.high >>> 0) << 32n) | BigInt(v.low >>> 0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// _toBigInt normalizes an int64-ish value for the encoder. BigInt passes
|
|
37
|
+
// through, numbers pass through (the addon casts them, same as plain
|
|
38
|
+
// objects), numeric strings and Long-like {low, high} become BigInt.
|
|
39
|
+
function _toBigInt(v, path, unsigned) {
|
|
40
|
+
if (typeof v === 'bigint') {
|
|
41
|
+
// fall through to the range check below
|
|
42
|
+
} else if (typeof v === 'number') {
|
|
43
|
+
if (!Number.isInteger(v)) {
|
|
44
|
+
throw new TypeError('pbwire: ' + path + ' is not an integer');
|
|
45
|
+
}
|
|
46
|
+
return v;
|
|
47
|
+
} else if (typeof v === 'string') {
|
|
48
|
+
if (!/^-?\d+$/.test(v)) {
|
|
49
|
+
throw new TypeError('pbwire: ' + path + ' is not an integer string');
|
|
50
|
+
}
|
|
51
|
+
v = BigInt(v);
|
|
52
|
+
} else if (_isLongLike(v)) {
|
|
53
|
+
v = _longToBigInt(v);
|
|
54
|
+
} else {
|
|
55
|
+
throw new TypeError('pbwire: ' + path + ' is not an int64 value');
|
|
56
|
+
}
|
|
57
|
+
if (unsigned) {
|
|
58
|
+
if (v < 0n || v > 18446744073709551615n) {
|
|
59
|
+
throw new TypeError('pbwire: ' + path + ' out of uint64 range');
|
|
60
|
+
}
|
|
61
|
+
} else if (v < -9223372036854775808n || v > 9223372036854775807n) {
|
|
62
|
+
throw new TypeError('pbwire: ' + path + ' out of int64 range');
|
|
63
|
+
}
|
|
64
|
+
return v;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Shape check only (any BigInt passes); range is enforced when the value
|
|
68
|
+
// is normalized for the encoder in _toBigInt.
|
|
69
|
+
function _checkInt64(v) {
|
|
70
|
+
if (typeof v === 'bigint') {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (typeof v === 'number') {
|
|
74
|
+
return Number.isInteger(v);
|
|
75
|
+
}
|
|
76
|
+
if (typeof v === 'string') {
|
|
77
|
+
return /^-?\d+$/.test(v);
|
|
78
|
+
}
|
|
79
|
+
return _isLongLike(v);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// _verifyField returns null when v is valid, else a reason string.
|
|
83
|
+
function _verifyField(fd, v, path, enums, classes) {
|
|
84
|
+
if (fd.rep) {
|
|
85
|
+
if (!Array.isArray(v)) {
|
|
86
|
+
return path + ': array expected';
|
|
87
|
+
}
|
|
88
|
+
for (let i = 0; i < v.length; i++) {
|
|
89
|
+
const bad = _verifyOne(fd, v[i], path + '[' + i + ']', enums, classes);
|
|
90
|
+
if (bad !== null) {
|
|
91
|
+
return bad;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return _verifyOne(fd, v, path, enums, classes);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function _verifyOne(fd, v, path, enums, classes) {
|
|
100
|
+
switch (fd.k) {
|
|
101
|
+
// Reason strings mirror protobufjs verify() verbatim so mixed code reads
|
|
102
|
+
// the same errors from either library. Like protobufjs, 32-bit checks are
|
|
103
|
+
// integer-only (range truncation happens in the encoder, identically).
|
|
104
|
+
case 'i32':
|
|
105
|
+
case 's32':
|
|
106
|
+
case 'sfx32':
|
|
107
|
+
case 'u32':
|
|
108
|
+
case 'fx32':
|
|
109
|
+
return Number.isInteger(v) ? null : path + ': integer expected';
|
|
110
|
+
case 'i64':
|
|
111
|
+
case 's64':
|
|
112
|
+
case 'sfx64':
|
|
113
|
+
case 'u64':
|
|
114
|
+
case 'fx64':
|
|
115
|
+
return _checkInt64(v) ? null : path + ': integer|Long expected';
|
|
116
|
+
case 'f32':
|
|
117
|
+
case 'f64':
|
|
118
|
+
return (typeof v === 'number') ? null : path + ': number expected';
|
|
119
|
+
case 'bool':
|
|
120
|
+
return (typeof v === 'boolean') ? null : path + ': boolean expected';
|
|
121
|
+
case 'str':
|
|
122
|
+
return (typeof v === 'string') ? null : path + ': string expected';
|
|
123
|
+
case 'bytes':
|
|
124
|
+
return (typeof v === 'object' && v !== null &&
|
|
125
|
+
(Buffer.isBuffer(v) || v instanceof Uint8Array))
|
|
126
|
+
? null : path + ': buffer expected';
|
|
127
|
+
case 'enum': {
|
|
128
|
+
const names = enums[fd.en];
|
|
129
|
+
if (typeof v === 'number' && Number.isInteger(v)) {
|
|
130
|
+
// Open enums (proto3) carry unknown values through the round trip.
|
|
131
|
+
if (!fd.closed || names === undefined) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
for (const key in names) {
|
|
135
|
+
if (names[key] === v) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return path + ': enum value expected';
|
|
140
|
+
}
|
|
141
|
+
return path + ': enum value expected';
|
|
142
|
+
}
|
|
143
|
+
case 'msg':
|
|
144
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v)) {
|
|
145
|
+
return path + ': object expected';
|
|
146
|
+
}
|
|
147
|
+
return classes[fd.msg].verify(v);
|
|
148
|
+
default:
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function _fromOne(fd, v, path, enums, classes) {
|
|
154
|
+
switch (fd.k) {
|
|
155
|
+
case 'i64':
|
|
156
|
+
case 's64':
|
|
157
|
+
case 'sfx64':
|
|
158
|
+
return _toBigInt(v, path, false);
|
|
159
|
+
case 'u64':
|
|
160
|
+
case 'fx64':
|
|
161
|
+
return _toBigInt(v, path, true);
|
|
162
|
+
case 'bytes':
|
|
163
|
+
if (Buffer.isBuffer(v)) {
|
|
164
|
+
return v;
|
|
165
|
+
}
|
|
166
|
+
if (v instanceof Uint8Array) {
|
|
167
|
+
return Buffer.from(v);
|
|
168
|
+
}
|
|
169
|
+
throw new TypeError('pbwire: ' + path + ' is not bytes');
|
|
170
|
+
case 'enum': {
|
|
171
|
+
if (typeof v === 'string') {
|
|
172
|
+
const names = enums[fd.en];
|
|
173
|
+
if (names !== undefined && names[v] !== undefined) {
|
|
174
|
+
return names[v];
|
|
175
|
+
}
|
|
176
|
+
throw new TypeError('pbwire: ' + path + ' unknown enum name ' + v);
|
|
177
|
+
}
|
|
178
|
+
if (typeof v === 'number' && Number.isInteger(v)) {
|
|
179
|
+
return v;
|
|
180
|
+
}
|
|
181
|
+
throw new TypeError('pbwire: ' + path + ' is not an enum value');
|
|
182
|
+
}
|
|
183
|
+
case 'msg':
|
|
184
|
+
if (v !== null && typeof v === 'object' && !(v instanceof classes[fd.msg])) {
|
|
185
|
+
return classes[fd.msg].fromObject(v);
|
|
186
|
+
}
|
|
187
|
+
return v;
|
|
188
|
+
default:
|
|
189
|
+
return v;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function _fromValue(fd, v, path, enums, classes) {
|
|
194
|
+
if (v === null || v === undefined) {
|
|
195
|
+
return fd.rep ? [] : null;
|
|
196
|
+
}
|
|
197
|
+
if (fd.rep) {
|
|
198
|
+
if (!Array.isArray(v)) {
|
|
199
|
+
throw new TypeError('pbwire: ' + path + ' is not an array');
|
|
200
|
+
}
|
|
201
|
+
return v.map((item, i) => _fromOne(fd, item, path + '[' + i + ']', enums, classes));
|
|
202
|
+
}
|
|
203
|
+
return _fromOne(fd, v, path, enums, classes);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function _toInt64(v, mode) {
|
|
207
|
+
const big = typeof v === 'bigint' ? v
|
|
208
|
+
: typeof v === 'number' ? BigInt(Math.trunc(v))
|
|
209
|
+
: _longToBigInt(v);
|
|
210
|
+
if (mode === 'String') {
|
|
211
|
+
return big.toString();
|
|
212
|
+
}
|
|
213
|
+
if (mode === 'Number') {
|
|
214
|
+
return Number(big);
|
|
215
|
+
}
|
|
216
|
+
return big;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function _toOne(fd, v, opts, enums, revEnums) {
|
|
220
|
+
switch (fd.k) {
|
|
221
|
+
case 'i64':
|
|
222
|
+
case 's64':
|
|
223
|
+
case 'sfx64':
|
|
224
|
+
case 'u64':
|
|
225
|
+
case 'fx64':
|
|
226
|
+
return _toInt64(v, opts.longs || 'BigInt');
|
|
227
|
+
case 'enum':
|
|
228
|
+
if ((opts.enums || 'Number') === 'String') {
|
|
229
|
+
const rev = revEnums[fd.en];
|
|
230
|
+
if (rev !== undefined && rev[v] !== undefined) {
|
|
231
|
+
return rev[v];
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return v;
|
|
235
|
+
case 'bytes':
|
|
236
|
+
return (opts.bytes || 'Buffer') === 'base64' ? Buffer.from(v).toString('base64') : v;
|
|
237
|
+
case 'msg':
|
|
238
|
+
return v.toObject(opts);
|
|
239
|
+
default:
|
|
240
|
+
return v;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Output keys are camelCase like protobufjs toObject; keepCase:true keeps
|
|
245
|
+
// the proto names (the dynamic API's convention).
|
|
246
|
+
function _toPlain(cls, msg, opts, enums, revEnums) {
|
|
247
|
+
const out = {};
|
|
248
|
+
const keyOf = (fd) => (opts.keepCase ? fd.n : fd.js);
|
|
249
|
+
const marked = msg._present instanceof Set ? msg._present : null;
|
|
250
|
+
for (const fd of cls._FIELDS) {
|
|
251
|
+
const v = msg[fd.js];
|
|
252
|
+
if (v === null || v === undefined) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (fd.rep && v.length === 0) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
// Unmarked fields are constructor defaults or direct post-hoc writes:
|
|
259
|
+
// include non-default values (direct post-hoc writes work), drop default
|
|
260
|
+
// ones (so create({}) encodes empty). Marked fields always go on the wire.
|
|
261
|
+
if (!opts.defaults && !fd.req && marked !== null &&
|
|
262
|
+
!marked.has(fd.js) && _isDefault(fd, v)) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
out[keyOf(fd)] = fd.rep
|
|
266
|
+
? v.map((item) => _toOne(fd, item, opts, enums, revEnums))
|
|
267
|
+
: _toOne(fd, v, opts, enums, revEnums);
|
|
268
|
+
}
|
|
269
|
+
if (opts.defaults) {
|
|
270
|
+
for (const fd of cls._FIELDS) {
|
|
271
|
+
if (out[keyOf(fd)] !== undefined) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (fd.rep) {
|
|
275
|
+
out[keyOf(fd)] = [];
|
|
276
|
+
} else if (fd.k === 'msg') {
|
|
277
|
+
out[keyOf(fd)] = null;
|
|
278
|
+
} else {
|
|
279
|
+
out[keyOf(fd)] = _toOne(fd, _defaultFor(fd), opts, enums, revEnums);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return out;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function _isDefault(fd, v) {
|
|
287
|
+
if (fd.rep) {
|
|
288
|
+
return v.length === 0;
|
|
289
|
+
}
|
|
290
|
+
switch (fd.k) {
|
|
291
|
+
case 'i64':
|
|
292
|
+
case 's64':
|
|
293
|
+
case 'sfx64':
|
|
294
|
+
case 'u64':
|
|
295
|
+
case 'fx64':
|
|
296
|
+
return v === 0 || v === 0n;
|
|
297
|
+
case 'bool':
|
|
298
|
+
return v === false;
|
|
299
|
+
case 'str':
|
|
300
|
+
return v === '';
|
|
301
|
+
case 'bytes':
|
|
302
|
+
return v.length === 0;
|
|
303
|
+
case 'msg':
|
|
304
|
+
return v === null || v === undefined;
|
|
305
|
+
default:
|
|
306
|
+
return v === 0;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function _defaultFor(fd, classes) {
|
|
311
|
+
if (fd.rep) {
|
|
312
|
+
return [];
|
|
313
|
+
}
|
|
314
|
+
// Presence fields (explicit presence or sub-message) default to absent.
|
|
315
|
+
if (fd.has) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
switch (fd.k) {
|
|
319
|
+
case 'i64':
|
|
320
|
+
case 's64':
|
|
321
|
+
case 'sfx64':
|
|
322
|
+
case 'u64':
|
|
323
|
+
case 'fx64':
|
|
324
|
+
return 0n;
|
|
325
|
+
case 'f32':
|
|
326
|
+
case 'f64':
|
|
327
|
+
case 'i32':
|
|
328
|
+
case 's32':
|
|
329
|
+
case 'sfx32':
|
|
330
|
+
case 'u32':
|
|
331
|
+
case 'fx32':
|
|
332
|
+
case 'enum':
|
|
333
|
+
return 0;
|
|
334
|
+
case 'bool':
|
|
335
|
+
return false;
|
|
336
|
+
case 'str':
|
|
337
|
+
return '';
|
|
338
|
+
case 'bytes':
|
|
339
|
+
return Buffer.alloc(0);
|
|
340
|
+
case 'msg':
|
|
341
|
+
return null;
|
|
342
|
+
default:
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function _encodeVarint(n) {
|
|
348
|
+
const parts = [];
|
|
349
|
+
while (n >= 128) {
|
|
350
|
+
parts.push((n % 128) | 128);
|
|
351
|
+
n = Math.floor(n / 128);
|
|
352
|
+
}
|
|
353
|
+
parts.push(n);
|
|
354
|
+
return Buffer.from(parts);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function _decodeVarint(buf, pos) {
|
|
358
|
+
let n = 0;
|
|
359
|
+
let shift = 0;
|
|
360
|
+
while (pos < buf.length) {
|
|
361
|
+
const b = buf[pos++];
|
|
362
|
+
n += (b & 127) * 2 ** shift;
|
|
363
|
+
shift += 7;
|
|
364
|
+
if ((b & 128) === 0) {
|
|
365
|
+
return [n, pos];
|
|
366
|
+
}
|
|
367
|
+
if (shift >= 64) {
|
|
368
|
+
throw new Error('pbwire: varint too long');
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
throw new Error('pbwire: truncated varint');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// _makeClass builds one message class over a MessageType. cls._FIELDS is the
|
|
375
|
+
// table the engine above reads; wire bytes always come from the C core.
|
|
376
|
+
// _fromWire converts C-validated wire values into an instance. Unlike
|
|
377
|
+
// fromObject it performs no validation and no coercion: the decoder has
|
|
378
|
+
// already checked every value against the schema, so nested plains only
|
|
379
|
+
// need wrapping and every present field only needs marking. The encode
|
|
380
|
+
// boundary (addon tree build) remains the loudness net for bad data.
|
|
381
|
+
function _fromWire(cls, plain) {
|
|
382
|
+
// new keeps V8's hidden-class shape (measured faster than Object.create
|
|
383
|
+
// + defineProperty here); the constructor's defaults stand for absent
|
|
384
|
+
// fields, present ones are converted and marked below.
|
|
385
|
+
const inst = new cls();
|
|
386
|
+
const classes = cls._CLASSES;
|
|
387
|
+
for (const fd of cls._FIELDS) {
|
|
388
|
+
const v = plain[fd.n];
|
|
389
|
+
if (v === undefined) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (fd.k === 'msg') {
|
|
393
|
+
if (fd.rep) {
|
|
394
|
+
inst[fd.js] = v.map((item) => _fromWire(classes[fd.msg], item));
|
|
395
|
+
} else {
|
|
396
|
+
inst[fd.js] = _fromWire(classes[fd.msg], v);
|
|
397
|
+
}
|
|
398
|
+
} else if (fd.rep) {
|
|
399
|
+
inst[fd.js] = v.slice();
|
|
400
|
+
} else {
|
|
401
|
+
inst[fd.js] = v;
|
|
402
|
+
}
|
|
403
|
+
inst._present.add(fd.js);
|
|
404
|
+
}
|
|
405
|
+
return inst;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function _makeClass(full, descB64, fields) {
|
|
409
|
+
class Compat {
|
|
410
|
+
constructor(props) {
|
|
411
|
+
props = props || {};
|
|
412
|
+
Object.defineProperty(this, '_present', { value: new Set(), enumerable: false, writable: true });
|
|
413
|
+
for (const fd of fields) {
|
|
414
|
+
const v = props[fd.js] !== undefined ? props[fd.js] : props[fd.n];
|
|
415
|
+
if (v === undefined) {
|
|
416
|
+
this[fd.js] = _defaultFor(fd);
|
|
417
|
+
} else {
|
|
418
|
+
this[fd.js] = v;
|
|
419
|
+
// fromObject normalizes below; direct construction marks as set.
|
|
420
|
+
this._present.add(fd.js);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
static create(props) {
|
|
425
|
+
return this.fromObject(props || {});
|
|
426
|
+
}
|
|
427
|
+
static encode(message) {
|
|
428
|
+
const inst = (message instanceof this) ? message : this.fromObject(message);
|
|
429
|
+
// The dynamic encoder takes proto names; toObject() shows camelCase.
|
|
430
|
+
const plain = _toPlain(this, inst, { keepCase: true }, this._ENUMS, this._REVENUMS);
|
|
431
|
+
const wire = _type(descB64, full).encode(plain);
|
|
432
|
+
return { finish: () => wire };
|
|
433
|
+
}
|
|
434
|
+
static encodeDelimited(message) {
|
|
435
|
+
const wire = this.encode(message).finish();
|
|
436
|
+
const out = Buffer.concat([_encodeVarint(wire.length), wire]);
|
|
437
|
+
return { finish: () => out };
|
|
438
|
+
}
|
|
439
|
+
static decode(bytes) {
|
|
440
|
+
const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
|
|
441
|
+
const m = _type(descB64, full).decode(buf);
|
|
442
|
+
const plain = {};
|
|
443
|
+
for (const fd of fields) {
|
|
444
|
+
const v = m.get(fd.n);
|
|
445
|
+
if (v !== undefined) {
|
|
446
|
+
plain[fd.n] = v;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return _fromWire(Compat, plain);
|
|
450
|
+
}
|
|
451
|
+
static decodeDelimited(bytes) {
|
|
452
|
+
const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
|
|
453
|
+
const pair = _decodeVarint(buf, 0);
|
|
454
|
+
return this.decode(buf.subarray(pair[1], pair[1] + pair[0]));
|
|
455
|
+
}
|
|
456
|
+
static verify(obj) {
|
|
457
|
+
if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
458
|
+
return 'object expected';
|
|
459
|
+
}
|
|
460
|
+
const seen = new Map();
|
|
461
|
+
for (const fd of fields) {
|
|
462
|
+
let v = obj[fd.js];
|
|
463
|
+
if (v === undefined) {
|
|
464
|
+
v = obj[fd.n];
|
|
465
|
+
}
|
|
466
|
+
if (v === null || v === undefined) {
|
|
467
|
+
if (fd.req) {
|
|
468
|
+
return fd.n + ': required field missing';
|
|
469
|
+
}
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const bad = _verifyField(fd, v, fd.n, this._ENUMS, this._CLASSES);
|
|
473
|
+
if (bad !== null) {
|
|
474
|
+
return bad;
|
|
475
|
+
}
|
|
476
|
+
if (fd.oneof !== null) {
|
|
477
|
+
if (seen.has(fd.oneof)) {
|
|
478
|
+
return fd.oneof + ': multiple values';
|
|
479
|
+
}
|
|
480
|
+
seen.set(fd.oneof, true);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
static fromObject(obj) {
|
|
486
|
+
if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
487
|
+
throw new TypeError('pbwire: fromObject expects an object');
|
|
488
|
+
}
|
|
489
|
+
const inst = new this();
|
|
490
|
+
for (const fd of fields) {
|
|
491
|
+
let v = obj[fd.js] !== undefined ? obj[fd.js] : obj[fd.n];
|
|
492
|
+
if (v === undefined) {
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (v === null) {
|
|
496
|
+
inst[fd.js] = fd.rep ? [] : null;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
// One validation logic for verify() and fromObject(): normalize,
|
|
500
|
+
// then reject with the same reason verify() would give.
|
|
501
|
+
const nv = _fromValue(fd, v, fd.n, this._ENUMS, this._CLASSES);
|
|
502
|
+
const bad = _verifyField(fd, nv, fd.n, this._ENUMS, this._CLASSES);
|
|
503
|
+
if (bad !== null) {
|
|
504
|
+
throw new TypeError('pbwire: ' + bad);
|
|
505
|
+
}
|
|
506
|
+
inst[fd.js] = nv;
|
|
507
|
+
inst._present.add(fd.js);
|
|
508
|
+
}
|
|
509
|
+
return inst;
|
|
510
|
+
}
|
|
511
|
+
static toObject(message, opts) {
|
|
512
|
+
const inst = (message instanceof this) ? message : this.fromObject(message);
|
|
513
|
+
return _toPlain(this, inst, opts || {}, this._ENUMS, this._REVENUMS);
|
|
514
|
+
}
|
|
515
|
+
toObject(opts) {
|
|
516
|
+
return this.constructor.toObject(this, opts);
|
|
517
|
+
}
|
|
518
|
+
toJSON() {
|
|
519
|
+
return this.constructor.toObject(this, { enums: 'String', longs: 'String', bytes: 'base64' });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
Compat._FIELDS = fields;
|
|
523
|
+
return Compat;
|
|
524
|
+
}
|
|
525
|
+
module.exports = { _makeClass };
|
package/lib/pbwire.js
CHANGED
|
@@ -107,6 +107,40 @@ class MessageType {
|
|
|
107
107
|
return this._copy(addon.encodeObject(this._handle, value));
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/** Parse JSON mapping text into a Message; throws on malformed input.
|
|
111
|
+
* A second true argument skips unknown field names instead of failing. */
|
|
112
|
+
fromJson(json, ignore = false) {
|
|
113
|
+
if (typeof json !== 'string') {
|
|
114
|
+
throw new TypeError('pbwire: fromJson expects a string');
|
|
115
|
+
}
|
|
116
|
+
const message = new Message(this);
|
|
117
|
+
message._handle = addon.jsonToMessage(this._handle, json, ignore);
|
|
118
|
+
return message;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Parse text format into a Message; throws on malformed input. */
|
|
122
|
+
fromText(text) {
|
|
123
|
+
if (typeof text !== 'string') {
|
|
124
|
+
throw new TypeError('pbwire: fromText expects a string');
|
|
125
|
+
}
|
|
126
|
+
const message = new Message(this);
|
|
127
|
+
message._handle = addon.textToMessage(this._handle, text);
|
|
128
|
+
return message;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Decode one length-delimited frame: {message, read}, where read counts
|
|
132
|
+
* the length prefix plus the frame, so concatenated frames decode in a
|
|
133
|
+
* loop. Throws on a truncated frame. */
|
|
134
|
+
decodeDelimited(bytes) {
|
|
135
|
+
if (!Buffer.isBuffer(bytes)) {
|
|
136
|
+
throw new TypeError('pbwire: decodeDelimited expects a Buffer');
|
|
137
|
+
}
|
|
138
|
+
const res = addon.decodeDelimited(this._handle, bytes);
|
|
139
|
+
const message = new Message(this);
|
|
140
|
+
message._handle = res.message;
|
|
141
|
+
return { message, read: res.read };
|
|
142
|
+
}
|
|
143
|
+
|
|
110
144
|
/** Decode wire bytes into a Message; throws on malformed input. */
|
|
111
145
|
decode(bytes) {
|
|
112
146
|
if (!Buffer.isBuffer(bytes)) {
|
|
@@ -152,6 +186,12 @@ class Message {
|
|
|
152
186
|
|
|
153
187
|
/** Store a field value by name; `undefined`/`null` clears the field. */
|
|
154
188
|
set(name, value) {
|
|
189
|
+
addon.set(this._tree(), name, value);
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** The mutable tree, decoding the retained bytes once when needed. */
|
|
194
|
+
_tree() {
|
|
155
195
|
this._live();
|
|
156
196
|
if (this._wire !== null) {
|
|
157
197
|
// Writing needs a mutable tree: decode the retained bytes once.
|
|
@@ -159,8 +199,28 @@ class Message {
|
|
|
159
199
|
this._wire = null;
|
|
160
200
|
this._slices = null;
|
|
161
201
|
}
|
|
162
|
-
|
|
163
|
-
|
|
202
|
+
return this._handle;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** JSON mapping text of this message. */
|
|
206
|
+
toJson() {
|
|
207
|
+
return addon.messageToJson(this._tree());
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Protobuf text format of this message; a false argument drops unknown
|
|
211
|
+
* fields instead of printing them numerically. */
|
|
212
|
+
toText(printUnknown = true) {
|
|
213
|
+
return addon.messageToText(this._tree(), printUnknown);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Field presence by name: set (even to a zero) reads true. */
|
|
217
|
+
has(name) {
|
|
218
|
+
return addon.has(this._tree(), name);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** One length-delimited frame (varint length + message). */
|
|
222
|
+
encodeDelimited() {
|
|
223
|
+
return addon.encodeDelimited(this._tree());
|
|
164
224
|
}
|
|
165
225
|
|
|
166
226
|
/** Wire bytes of this message. */
|
package/package.json
CHANGED
package/runtime/addon.c
CHANGED
|
@@ -2528,6 +2528,279 @@ message_free(napi_env env, napi_callback_info info)
|
|
|
2528
2528
|
return out;
|
|
2529
2529
|
}
|
|
2530
2530
|
|
|
2531
|
+
/* JSON text of a tree (protobuf JSON mapping). */
|
|
2532
|
+
static napi_value
|
|
2533
|
+
message_to_json(napi_env env, napi_callback_info info)
|
|
2534
|
+
{
|
|
2535
|
+
napi_value argv[1];
|
|
2536
|
+
napi_value out = NULL;
|
|
2537
|
+
message_handle *handle;
|
|
2538
|
+
char *text = NULL;
|
|
2539
|
+
size_t len = 0;
|
|
2540
|
+
pbw_status status;
|
|
2541
|
+
|
|
2542
|
+
if (!get_args(env, info, argv, 1)) {
|
|
2543
|
+
return NULL;
|
|
2544
|
+
}
|
|
2545
|
+
if (!unwrap_message(env, argv[0], &handle)) {
|
|
2546
|
+
return NULL;
|
|
2547
|
+
}
|
|
2548
|
+
status = pbw_json_print(handle->msg, &text, &len);
|
|
2549
|
+
if (status != PBW_OK) {
|
|
2550
|
+
return fail_status(env, "messageToJson", status);
|
|
2551
|
+
}
|
|
2552
|
+
if (napi_create_string_utf8(env, text, len, &out) != napi_ok) {
|
|
2553
|
+
pbw_free(text);
|
|
2554
|
+
return NULL;
|
|
2555
|
+
}
|
|
2556
|
+
pbw_free(text);
|
|
2557
|
+
return out;
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
/* Parses JSON mapping text into a new tree; an optional third boolean
|
|
2561
|
+
* argument skips unknown field names instead of failing. */
|
|
2562
|
+
static napi_value
|
|
2563
|
+
json_to_message(napi_env env, napi_callback_info info)
|
|
2564
|
+
{
|
|
2565
|
+
napi_value argv[3];
|
|
2566
|
+
size_t argc = 3;
|
|
2567
|
+
desc_handle *desc;
|
|
2568
|
+
message_handle *handle;
|
|
2569
|
+
pbw_message *msg = NULL;
|
|
2570
|
+
string_buf buf;
|
|
2571
|
+
const char *text;
|
|
2572
|
+
pbw_status status;
|
|
2573
|
+
pbw_json_parse_options opts;
|
|
2574
|
+
bool ignore = false;
|
|
2575
|
+
|
|
2576
|
+
if (napi_get_cb_info(env, info, &argc, argv, NULL, NULL) != napi_ok ||
|
|
2577
|
+
argc < 2) {
|
|
2578
|
+
napi_throw_type_error(env, NULL, "pbwire: expected (desc, json)");
|
|
2579
|
+
return NULL;
|
|
2580
|
+
}
|
|
2581
|
+
if (!unwrap(env, argv[0], (void **)&desc, "message descriptor handle")) {
|
|
2582
|
+
return NULL;
|
|
2583
|
+
}
|
|
2584
|
+
if (argc > 2) {
|
|
2585
|
+
napi_valuetype want = napi_undefined;
|
|
2586
|
+
|
|
2587
|
+
if (napi_typeof(env, argv[2], &want) != napi_ok ||
|
|
2588
|
+
want != napi_boolean ||
|
|
2589
|
+
napi_get_value_bool(env, argv[2], &ignore) != napi_ok) {
|
|
2590
|
+
napi_throw_type_error(env, NULL,
|
|
2591
|
+
"pbwire: ignore flag must be a boolean");
|
|
2592
|
+
return NULL;
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
text = string_bytes(env, argv[1], &buf);
|
|
2596
|
+
if (text == NULL) {
|
|
2597
|
+
return NULL;
|
|
2598
|
+
}
|
|
2599
|
+
opts.ignore_unknown_fields = ignore;
|
|
2600
|
+
status = pbw_json_parse_opts(desc->table->desc, text, buf.len, &opts,
|
|
2601
|
+
&msg);
|
|
2602
|
+
string_free(&buf, text);
|
|
2603
|
+
if (status != PBW_OK) {
|
|
2604
|
+
return fail_status(env, "jsonToMessage", status);
|
|
2605
|
+
}
|
|
2606
|
+
handle = calloc(1, sizeof(*handle));
|
|
2607
|
+
if (handle == NULL) {
|
|
2608
|
+
pbw_message_free(msg);
|
|
2609
|
+
return fail_memory(env);
|
|
2610
|
+
}
|
|
2611
|
+
handle->msg = msg;
|
|
2612
|
+
return new_message_value(env, desc, argv[0], handle);
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2615
|
+
/* Text format of a tree. */
|
|
2616
|
+
static napi_value
|
|
2617
|
+
message_to_text(napi_env env, napi_callback_info info)
|
|
2618
|
+
{
|
|
2619
|
+
napi_value argv[2];
|
|
2620
|
+
size_t argc = 2;
|
|
2621
|
+
napi_value out = NULL;
|
|
2622
|
+
message_handle *handle;
|
|
2623
|
+
char *text = NULL;
|
|
2624
|
+
size_t len = 0;
|
|
2625
|
+
pbw_status status;
|
|
2626
|
+
/* Absent means print (the binding's standing behavior); the testee
|
|
2627
|
+
* passes the runner's print_unknown_fields flag explicitly. */
|
|
2628
|
+
bool print_unknown = true;
|
|
2629
|
+
|
|
2630
|
+
if (napi_get_cb_info(env, info, &argc, argv, NULL, NULL) != napi_ok ||
|
|
2631
|
+
argc < 1) {
|
|
2632
|
+
napi_throw_type_error(env, NULL, "pbwire: expected (message)");
|
|
2633
|
+
return NULL;
|
|
2634
|
+
}
|
|
2635
|
+
if (!unwrap_message(env, argv[0], &handle)) {
|
|
2636
|
+
return NULL;
|
|
2637
|
+
}
|
|
2638
|
+
if (argc > 1) {
|
|
2639
|
+
napi_valuetype want = napi_undefined;
|
|
2640
|
+
|
|
2641
|
+
if (napi_typeof(env, argv[1], &want) != napi_ok ||
|
|
2642
|
+
want != napi_boolean ||
|
|
2643
|
+
napi_get_value_bool(env, argv[1], &print_unknown) != napi_ok) {
|
|
2644
|
+
napi_throw_type_error(env, NULL,
|
|
2645
|
+
"pbwire: print flag must be a boolean");
|
|
2646
|
+
return NULL;
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
status = pbw_text_print_opts(handle->msg, print_unknown, &text, &len);
|
|
2650
|
+
if (status != PBW_OK) {
|
|
2651
|
+
return fail_status(env, "messageToText", status);
|
|
2652
|
+
}
|
|
2653
|
+
if (napi_create_string_utf8(env, text, len, &out) != napi_ok) {
|
|
2654
|
+
pbw_free(text);
|
|
2655
|
+
return NULL;
|
|
2656
|
+
}
|
|
2657
|
+
pbw_free(text);
|
|
2658
|
+
return out;
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
/* Parses text format into a new tree. */
|
|
2662
|
+
static napi_value
|
|
2663
|
+
text_to_message(napi_env env, napi_callback_info info)
|
|
2664
|
+
{
|
|
2665
|
+
napi_value argv[2];
|
|
2666
|
+
desc_handle *desc;
|
|
2667
|
+
message_handle *handle;
|
|
2668
|
+
pbw_message *msg = NULL;
|
|
2669
|
+
string_buf buf;
|
|
2670
|
+
const char *text;
|
|
2671
|
+
pbw_status status;
|
|
2672
|
+
|
|
2673
|
+
if (!get_args(env, info, argv, 2)) {
|
|
2674
|
+
return NULL;
|
|
2675
|
+
}
|
|
2676
|
+
if (!unwrap(env, argv[0], (void **)&desc, "message descriptor handle")) {
|
|
2677
|
+
return NULL;
|
|
2678
|
+
}
|
|
2679
|
+
text = string_bytes(env, argv[1], &buf);
|
|
2680
|
+
if (text == NULL) {
|
|
2681
|
+
return NULL;
|
|
2682
|
+
}
|
|
2683
|
+
status = pbw_text_parse(desc->table->desc, text, buf.len, &msg);
|
|
2684
|
+
string_free(&buf, text);
|
|
2685
|
+
if (status != PBW_OK) {
|
|
2686
|
+
return fail_status(env, "textToMessage", status);
|
|
2687
|
+
}
|
|
2688
|
+
handle = calloc(1, sizeof(*handle));
|
|
2689
|
+
if (handle == NULL) {
|
|
2690
|
+
pbw_message_free(msg);
|
|
2691
|
+
return fail_memory(env);
|
|
2692
|
+
}
|
|
2693
|
+
handle->msg = msg;
|
|
2694
|
+
return new_message_value(env, desc, argv[0], handle);
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
/* Presence of one field by name: set (even to a zero) reads true. */
|
|
2698
|
+
static napi_value
|
|
2699
|
+
message_has(napi_env env, napi_callback_info info)
|
|
2700
|
+
{
|
|
2701
|
+
napi_value argv[2];
|
|
2702
|
+
napi_value out = NULL;
|
|
2703
|
+
message_handle *handle;
|
|
2704
|
+
field_entry *entry;
|
|
2705
|
+
int present;
|
|
2706
|
+
|
|
2707
|
+
if (!get_args(env, info, argv, 2)) {
|
|
2708
|
+
return NULL;
|
|
2709
|
+
}
|
|
2710
|
+
if (!unwrap_message(env, argv[0], &handle)) {
|
|
2711
|
+
return NULL;
|
|
2712
|
+
}
|
|
2713
|
+
entry = field_by_name(env, handle, argv[1]);
|
|
2714
|
+
if (entry == NULL) {
|
|
2715
|
+
return NULL;
|
|
2716
|
+
}
|
|
2717
|
+
present = pbw_message_get_field(handle->msg, entry->field) != NULL;
|
|
2718
|
+
if (napi_get_boolean(env, present, &out) != napi_ok) {
|
|
2719
|
+
return NULL;
|
|
2720
|
+
}
|
|
2721
|
+
return out;
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
/* One length-delimited frame (varint length + message) as a Buffer. */
|
|
2725
|
+
static napi_value
|
|
2726
|
+
encode_delimited(napi_env env, napi_callback_info info)
|
|
2727
|
+
{
|
|
2728
|
+
napi_value argv[1];
|
|
2729
|
+
napi_value out = NULL;
|
|
2730
|
+
message_handle *handle;
|
|
2731
|
+
uint8_t *wire = NULL;
|
|
2732
|
+
size_t wire_len = 0;
|
|
2733
|
+
pbw_status status;
|
|
2734
|
+
|
|
2735
|
+
if (!get_args(env, info, argv, 1)) {
|
|
2736
|
+
return NULL;
|
|
2737
|
+
}
|
|
2738
|
+
if (!unwrap_message(env, argv[0], &handle)) {
|
|
2739
|
+
return NULL;
|
|
2740
|
+
}
|
|
2741
|
+
status = pbw_encode_delimited(handle->msg, &wire, &wire_len);
|
|
2742
|
+
if (status != PBW_OK) {
|
|
2743
|
+
return fail_status(env, "encodeDelimited", status);
|
|
2744
|
+
}
|
|
2745
|
+
if (napi_create_buffer_copy(env, wire_len, wire, NULL, &out) != napi_ok) {
|
|
2746
|
+
pbw_free(wire);
|
|
2747
|
+
return NULL;
|
|
2748
|
+
}
|
|
2749
|
+
pbw_free(wire);
|
|
2750
|
+
return out;
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
/* One frame from a Buffer: {message, read}, where read counts the length
|
|
2754
|
+
* prefix plus the frame, so concatenated frames decode in a loop. */
|
|
2755
|
+
static napi_value
|
|
2756
|
+
decode_delimited(napi_env env, napi_callback_info info)
|
|
2757
|
+
{
|
|
2758
|
+
napi_value argv[2];
|
|
2759
|
+
napi_value out = NULL;
|
|
2760
|
+
napi_value message_value = NULL;
|
|
2761
|
+
napi_value read_value = NULL;
|
|
2762
|
+
desc_handle *desc;
|
|
2763
|
+
message_handle *handle;
|
|
2764
|
+
pbw_message *msg = NULL;
|
|
2765
|
+
void *data = NULL;
|
|
2766
|
+
size_t len = 0;
|
|
2767
|
+
size_t consumed = 0;
|
|
2768
|
+
pbw_status status;
|
|
2769
|
+
|
|
2770
|
+
if (!get_args(env, info, argv, 2)) {
|
|
2771
|
+
return NULL;
|
|
2772
|
+
}
|
|
2773
|
+
if (!unwrap(env, argv[0], (void **)&desc, "message descriptor handle")) {
|
|
2774
|
+
return NULL;
|
|
2775
|
+
}
|
|
2776
|
+
if (napi_get_buffer_info(env, argv[1], &data, &len) != napi_ok) {
|
|
2777
|
+
return fail_argument(env, "decodeDelimited expects a Buffer");
|
|
2778
|
+
}
|
|
2779
|
+
status = pbw_decode_delimited(desc->table->desc, data, len, &msg,
|
|
2780
|
+
&consumed);
|
|
2781
|
+
if (status != PBW_OK) {
|
|
2782
|
+
return fail_status(env, "decodeDelimited", status);
|
|
2783
|
+
}
|
|
2784
|
+
handle = calloc(1, sizeof(*handle));
|
|
2785
|
+
if (handle == NULL) {
|
|
2786
|
+
pbw_message_free(msg);
|
|
2787
|
+
return fail_memory(env);
|
|
2788
|
+
}
|
|
2789
|
+
handle->msg = msg;
|
|
2790
|
+
message_value = new_message_value(env, desc, argv[0], handle);
|
|
2791
|
+
if (message_value == NULL) {
|
|
2792
|
+
return NULL;
|
|
2793
|
+
}
|
|
2794
|
+
if (napi_create_object(env, &out) != napi_ok ||
|
|
2795
|
+
napi_set_named_property(env, out, "message", message_value) !=
|
|
2796
|
+
napi_ok ||
|
|
2797
|
+
napi_create_uint32(env, (uint32_t)consumed, &read_value) != napi_ok ||
|
|
2798
|
+
napi_set_named_property(env, out, "read", read_value) != napi_ok) {
|
|
2799
|
+
return NULL;
|
|
2800
|
+
}
|
|
2801
|
+
return out;
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2531
2804
|
NAPI_MODULE_INIT()
|
|
2532
2805
|
{
|
|
2533
2806
|
napi_property_descriptor properties[] = {
|
|
@@ -2554,6 +2827,19 @@ NAPI_MODULE_INIT()
|
|
|
2554
2827
|
{"copyEncoded", NULL, copy_encoded, NULL, NULL, NULL, napi_enumerable,
|
|
2555
2828
|
NULL},
|
|
2556
2829
|
{"free", NULL, message_free, NULL, NULL, NULL, napi_enumerable, NULL},
|
|
2830
|
+
{"messageToJson", NULL, message_to_json, NULL, NULL, NULL,
|
|
2831
|
+
napi_enumerable, NULL},
|
|
2832
|
+
{"jsonToMessage", NULL, json_to_message, NULL, NULL, NULL,
|
|
2833
|
+
napi_enumerable, NULL},
|
|
2834
|
+
{"messageToText", NULL, message_to_text, NULL, NULL, NULL,
|
|
2835
|
+
napi_enumerable, NULL},
|
|
2836
|
+
{"textToMessage", NULL, text_to_message, NULL, NULL, NULL,
|
|
2837
|
+
napi_enumerable, NULL},
|
|
2838
|
+
{"has", NULL, message_has, NULL, NULL, NULL, napi_enumerable, NULL},
|
|
2839
|
+
{"encodeDelimited", NULL, encode_delimited, NULL, NULL, NULL,
|
|
2840
|
+
napi_enumerable, NULL},
|
|
2841
|
+
{"decodeDelimited", NULL, decode_delimited, NULL, NULL, NULL,
|
|
2842
|
+
napi_enumerable, NULL},
|
|
2557
2843
|
};
|
|
2558
2844
|
|
|
2559
2845
|
if (napi_define_properties(env, exports,
|