@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 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
- - **No conformance testee.** The official suite is covered through the C core
212
- and `conformance/`, not through this binding. The differential test above is
213
- what keeps the addon's wire reader honest against the core's.
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
- Also out of scope here: JSON/text format (`pbw_json_print`/`pbw_text_print`),
216
- field presence introspection (`pbw_message_field_count`), and delimited framing.
217
- Each is one addon export away when a consumer needs it.
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
- addon.set(this._handle, name, value);
163
- return this;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pbwire/pbwire",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Dynamic protobuf over the pbwire C ABI, schema at runtime, no generated code",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
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,