@pbwire/pbwire 0.1.7 → 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 +24 -6
- package/lib/pbwire.js +62 -2
- package/package.json +1 -1
- package/runtime/addon.c +286 -0
package/README.md
CHANGED
|
@@ -208,13 +208,31 @@ per-message finalizer.
|
|
|
208
208
|
- **No protoc plugin / code generation.** The schema is always loaded at run
|
|
209
209
|
time, like the Python binding: a `FileDescriptorSet` is the only schema
|
|
210
210
|
source, and there is no generated layout to keep in sync.
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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`.
|
|
214
217
|
|
|
215
|
-
|
|
216
|
-
|
|
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
|
+
```
|
|
229
|
+
|
|
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`.
|
|
218
236
|
|
|
219
237
|
## Generated models
|
|
220
238
|
|
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,
|