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.
- checksums.yaml +4 -4
- data/README.md +51 -20
- data/ext/pbwire_ext/pbwire_ext.c +348 -17
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '0481bd05f90b2ad08b8d4df6c1d7e88f6c9e41821fa6eeaa03827342f5876da4'
|
|
4
|
+
data.tar.gz: 5475631f128badcfa9653bb09a8b6cfbce0a2b8f9792ca4061ce4f7729c2af9f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5704241ce640fe958967e6b368b6804901a3956217ecddf0be0661427b4d887bcfabef9b06deb625f79ea95260f4bb7379b7a4bbd26d324a6ef48467df6cbc31
|
|
7
|
+
data.tar.gz: 6cb90723b3fde234173da87eddc04e0b6bf4caa757eedd1de33f64fbf796869ab40f27f6bf1bf2d1de048390433da52a70bebca8c3373f0c36740baf40ad35c6
|
data/README.md
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
# pbwire Ruby binding
|
|
2
2
|
|
|
3
3
|
`lib/pbwire.rb` loads a compiled C extension (`ext/pbwire_ext/`) that links the
|
|
4
|
-
pbwire C core statically. No gems
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
pbwire C core statically. No gems at run time.
|
|
5
|
+
|
|
6
|
+
Most code never touches the dynamic surface: generate a gem-shaped facade
|
|
7
|
+
once (`make -C codegen gen-ruby`) and use it like the library it
|
|
8
|
+
mirrors — same `new`/`encode`/`decode`/`to_h` calls, same observable
|
|
9
|
+
behavior, but the wire format stays in the C core:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
require 'addressbook' # generated; embeds its own FileDescriptorSet
|
|
13
|
+
|
|
14
|
+
person = Pbwire::Gen::Tutorial_Person.new(name: 'alice', id: 7)
|
|
15
|
+
Pbwire::Gen::Tutorial_Person.encode(person) # => binary String, no schema
|
|
16
|
+
back = Pbwire::Gen::Tutorial_Person.decode(wire)
|
|
17
|
+
back.name # => "alice"
|
|
18
|
+
back.to_h # => {name: "alice", id: 7, ...}
|
|
19
|
+
back.has_email? # => false
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Presence follows the value: a non-nil assignment marks the field, so
|
|
23
|
+
`new(opt_i32: 0)` is written while `new` leaves it absent; oneof keeps
|
|
24
|
+
only its last member. `to_h` uses proto-name symbols and renders enums
|
|
25
|
+
as name symbols, exactly like the gem. The one deliberate difference:
|
|
26
|
+
validation happens at the encode boundary (plain Ruby objects until
|
|
27
|
+
then), where a mistyped field raises `TypeError`.
|
|
28
|
+
|
|
29
|
+
The dynamic API is still there for gateways and schemas known only at
|
|
30
|
+
runtime:
|
|
8
31
|
|
|
9
32
|
```ruby
|
|
10
33
|
require 'pbwire'
|
|
@@ -135,17 +158,19 @@ parent is closed.
|
|
|
135
158
|
|
|
136
159
|
Repeated scalars (packed or unpacked on the wire), repeated strings/bytes and
|
|
137
160
|
repeated messages are supported; a map field reads as an Array of entry
|
|
138
|
-
messages. Groups (proto2 legacy)
|
|
139
|
-
|
|
140
|
-
Extensions are read by number, not name.
|
|
161
|
+
messages. Groups (proto2 legacy) map like messages (START_GROUP framing on
|
|
162
|
+
the wire). Extensions are read by number, not name.
|
|
141
163
|
|
|
142
164
|
## Deliberately skipped
|
|
143
165
|
|
|
144
166
|
- **No `protoc` plugin, no codegen.** The descriptor set is the schema; nothing
|
|
145
167
|
is generated and there is no hand-built schema path.
|
|
146
|
-
- **
|
|
147
|
-
|
|
148
|
-
|
|
168
|
+
- **Formats beyond binary/JSON/text.** `to_json`/`from_json` (with an
|
|
169
|
+
optional ignore-unknown flag), `to_text`/`from_text`, deterministic
|
|
170
|
+
encoding (`encode_deterministic`, map entries sorted by key) and delimited
|
|
171
|
+
framing (`encode_delimited`, `decode_delimited` returning `[message, read]`)
|
|
172
|
+
wrap the C API; `to_builder` materializes a decoded view into a canonical
|
|
173
|
+
tree. Unknown fields round-trip byte for byte in every form.
|
|
149
174
|
|
|
150
175
|
## Benchmark
|
|
151
176
|
|
|
@@ -183,17 +208,23 @@ The win is architectural, not a micro-optimization:
|
|
|
183
208
|
|
|
184
209
|
## Conformance
|
|
185
210
|
|
|
186
|
-
`conformance/testee.rb` drives the binary
|
|
187
|
-
`conformance_test_runner` through this binding
|
|
188
|
-
the cached protobuf checkout from
|
|
211
|
+
`conformance/testee.rb` drives the binary, JSON and text formats of the
|
|
212
|
+
official `conformance_test_runner` through this binding
|
|
213
|
+
(`make conformance-ruby`, reusing the cached protobuf checkout from
|
|
214
|
+
`make conformance-setup`):
|
|
189
215
|
|
|
190
216
|
```
|
|
191
|
-
CONFORMANCE SUITE PASSED:
|
|
217
|
+
CONFORMANCE SUITE PASSED: 2744 successes, 0 skipped, 0 expected failures,
|
|
218
|
+
0 unexpected failures.
|
|
219
|
+
CONFORMANCE SUITE PASSED: 445 successes, 0 skipped, 0 expected failures,
|
|
192
220
|
0 unexpected failures.
|
|
193
221
|
```
|
|
194
222
|
|
|
195
|
-
|
|
196
|
-
binary
|
|
223
|
+
Only JSPB answers `skipped`; every other verdict matches the reference
|
|
224
|
+
implementation exactly. (The binary suite runs without the shared failure
|
|
225
|
+
list: the wire reader's validation rejects the overlong tag varints the
|
|
226
|
+
core tree decoder truncates, so the two listed cases parse-fail like the
|
|
227
|
+
reference. Text output honors the runner's `print_unknown_fields` flag.)
|
|
197
228
|
|
|
198
229
|
## Layout
|
|
199
230
|
|
|
@@ -205,7 +236,7 @@ binary verdicts match the reference implementation exactly.
|
|
|
205
236
|
| example | [`examples/roundtrip.rb`](examples/roundtrip.rb) |
|
|
206
237
|
| self-check | [`test/test_pbwire.rb`](test/test_pbwire.rb) |
|
|
207
238
|
| benchmark | [`benchmarks/bench.rb`](benchmarks/bench.rb) | the four standard tiers against the `google-protobuf` gem |
|
|
208
|
-
| conformance | [`conformance/testee.rb`](conformance/testee.rb) | binary
|
|
239
|
+
| conformance | [`conformance/testee.rb`](conformance/testee.rb) | binary, JSON and text through the official runner |
|
|
209
240
|
| fixture | [`test/data/sample.proto`](test/data/sample.proto) and the checked-in `sample.desc` |
|
|
210
241
|
|
|
211
242
|
Regenerate the descriptor set after editing the fixture (it is checked in, so the
|
|
@@ -215,9 +246,9 @@ tests need no `protoc`):
|
|
|
215
246
|
(cd bindings/ruby/test/data && protoc --descriptor_set_out=sample.desc sample.proto)
|
|
216
247
|
```
|
|
217
248
|
|
|
218
|
-
##
|
|
249
|
+
## Generated models
|
|
219
250
|
|
|
220
|
-
`protoc-gen-pbwire
|
|
251
|
+
`protoc-gen-pbwire` (in `../../codegen`) generates classes with keyword-argument
|
|
221
252
|
constructors and accessors, `#encode(schema)` and `.decode(schema, wire)`. The
|
|
222
253
|
committed output is `test/gen/` and `examples/gen/`; `examples/roundtrip.rb`
|
|
223
|
-
uses typed assignments only. See
|
|
254
|
+
uses typed assignments only. See `../../codegen/README.md`.
|
data/ext/pbwire_ext/pbwire_ext.c
CHANGED
|
@@ -74,7 +74,6 @@ typedef struct {
|
|
|
74
74
|
uint8_t packed;
|
|
75
75
|
uint8_t required;
|
|
76
76
|
uint8_t oneof; /* 1-based oneof group, 0 = none */
|
|
77
|
-
uint8_t unsupported;
|
|
78
77
|
VALUE name; /* frozen UTF-8 String */
|
|
79
78
|
VALUE sub; /* Type of a MESSAGE field, else Qnil */
|
|
80
79
|
} field_t;
|
|
@@ -455,7 +454,6 @@ type_for(VALUE schema_obj, const pbw_descriptor *desc)
|
|
|
455
454
|
f->packed = pbw_field_is_packed(field) != 0;
|
|
456
455
|
f->required = pbw_field_label(field) == PBW_LABEL_REQUIRED;
|
|
457
456
|
f->oneof = (uint8_t)pbw_field_oneof_index(field);
|
|
458
|
-
f->unsupported = f->type == PBW_TYPE_GROUP;
|
|
459
457
|
f->name = rb_utf8_str_new_cstr(pbw_field_name(field));
|
|
460
458
|
rb_obj_freeze(f->name);
|
|
461
459
|
f->sub = Qnil;
|
|
@@ -471,7 +469,7 @@ type_for(VALUE schema_obj, const pbw_descriptor *desc)
|
|
|
471
469
|
const pbw_field *field = pbw_descriptor_field_at(desc, i);
|
|
472
470
|
field_t *f = &type->fields[i];
|
|
473
471
|
|
|
474
|
-
if (f->type == PBW_TYPE_MESSAGE) {
|
|
472
|
+
if (f->type == PBW_TYPE_MESSAGE || f->type == PBW_TYPE_GROUP) {
|
|
475
473
|
f->sub = type_for(schema_obj, pbw_field_message_type(field));
|
|
476
474
|
}
|
|
477
475
|
}
|
|
@@ -702,6 +700,40 @@ rd_skip(rdr *r, uint32_t fnum, unsigned wt, unsigned depth)
|
|
|
702
700
|
}
|
|
703
701
|
}
|
|
704
702
|
|
|
703
|
+
/* Body of a group field: bytes from here to the matching END_GROUP tag.
|
|
704
|
+
* Nested groups (known or skipped) are consumed whole by rd_skip; an
|
|
705
|
+
* END_GROUP for another number is malformed. */
|
|
706
|
+
static int
|
|
707
|
+
rd_group_body(rdr *r, uint32_t fnum, unsigned depth, const uint8_t **body,
|
|
708
|
+
size_t *body_len)
|
|
709
|
+
{
|
|
710
|
+
const uint8_t *start = r->p;
|
|
711
|
+
|
|
712
|
+
if (depth >= RB_PBW_MAX_DEPTH) {
|
|
713
|
+
return 0;
|
|
714
|
+
}
|
|
715
|
+
for (;;) {
|
|
716
|
+
const uint8_t *tag_start = r->p;
|
|
717
|
+
uint32_t inner_fnum;
|
|
718
|
+
unsigned inner_wt;
|
|
719
|
+
|
|
720
|
+
if (!rd_tag(r, &inner_fnum, &inner_wt)) {
|
|
721
|
+
return 0;
|
|
722
|
+
}
|
|
723
|
+
if (inner_wt == 4) {
|
|
724
|
+
if (inner_fnum != fnum) {
|
|
725
|
+
return 0;
|
|
726
|
+
}
|
|
727
|
+
*body = start;
|
|
728
|
+
*body_len = (size_t)(tag_start - start);
|
|
729
|
+
return 1;
|
|
730
|
+
}
|
|
731
|
+
if (!rd_skip(r, inner_fnum, inner_wt, depth + 1)) {
|
|
732
|
+
return 0;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
705
737
|
/* A packed run must hold a whole number of complete values; returns the count
|
|
706
738
|
* or -1. */
|
|
707
739
|
static long
|
|
@@ -853,7 +885,7 @@ scan_message(message_t *m, type_t *type, const uint8_t *data, size_t off,
|
|
|
853
885
|
continue;
|
|
854
886
|
}
|
|
855
887
|
f = type_field_by_number(type, fnum);
|
|
856
|
-
if (f == NULL ||
|
|
888
|
+
if (f == NULL ||
|
|
857
889
|
(wt != (unsigned)f->wire &&
|
|
858
890
|
!(wt == 2 && f->repeated && packable((pbw_type)f->type)))) {
|
|
859
891
|
if (!rd_skip(&r, fnum, wt, depth)) {
|
|
@@ -865,6 +897,34 @@ scan_message(message_t *m, type_t *type, const uint8_t *data, size_t off,
|
|
|
865
897
|
if (seen != NULL && f->required) {
|
|
866
898
|
seen[f->index] = 1;
|
|
867
899
|
}
|
|
900
|
+
/* Groups share the sub-message scan: the body runs to the matching
|
|
901
|
+
* END_GROUP tag instead of a length prefix. */
|
|
902
|
+
if (f->type == PBW_TYPE_GROUP) {
|
|
903
|
+
const uint8_t *body;
|
|
904
|
+
size_t body_len;
|
|
905
|
+
|
|
906
|
+
if (!rd_group_body(&r, fnum, depth, &body, &body_len)) {
|
|
907
|
+
status = PBW_ERR_INVALID;
|
|
908
|
+
goto done;
|
|
909
|
+
}
|
|
910
|
+
{
|
|
911
|
+
type_t *sub = NULL;
|
|
912
|
+
|
|
913
|
+
TypedData_Get_Struct(f->sub, type_t, &type_data_type, sub);
|
|
914
|
+
status = scan_message(NULL, sub, body, 0, body_len,
|
|
915
|
+
depth + 1, missing);
|
|
916
|
+
if (status != PBW_OK) {
|
|
917
|
+
goto done;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
if (m != NULL &&
|
|
921
|
+
!slice_add(m, f, (size_t)(body - data), body_len, 1, 1,
|
|
922
|
+
oneof_last)) {
|
|
923
|
+
status = PBW_ERR_NOMEM;
|
|
924
|
+
goto done;
|
|
925
|
+
}
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
868
928
|
if (wt == 2) {
|
|
869
929
|
uint64_t plen;
|
|
870
930
|
size_t payload;
|
|
@@ -1109,7 +1169,7 @@ field_value(message_t *m, field_t *f)
|
|
|
1109
1169
|
if (last < 0) {
|
|
1110
1170
|
return Qnil;
|
|
1111
1171
|
}
|
|
1112
|
-
if (f->type == PBW_TYPE_MESSAGE) {
|
|
1172
|
+
if (f->type == PBW_TYPE_MESSAGE || f->type == PBW_TYPE_GROUP) {
|
|
1113
1173
|
uint32_t n = 0;
|
|
1114
1174
|
|
|
1115
1175
|
for (i = 0; i < m->nslices; i++) {
|
|
@@ -1152,7 +1212,8 @@ field_value(message_t *m, field_t *f)
|
|
|
1152
1212
|
if (s->fidx != f->index) {
|
|
1153
1213
|
continue;
|
|
1154
1214
|
}
|
|
1155
|
-
if (f->type == PBW_TYPE_MESSAGE
|
|
1215
|
+
if (f->type == PBW_TYPE_MESSAGE ||
|
|
1216
|
+
f->type == PBW_TYPE_GROUP) {
|
|
1156
1217
|
rb_ary_push(array, message_wire_new(f->sub, m->buffer,
|
|
1157
1218
|
s->off, s->len,
|
|
1158
1219
|
m->depth + 1));
|
|
@@ -1307,7 +1368,8 @@ validate_value(VALUE owner, field_t *f, VALUE value)
|
|
|
1307
1368
|
value_error(owner, f, "a String");
|
|
1308
1369
|
}
|
|
1309
1370
|
return;
|
|
1310
|
-
case PBW_TYPE_MESSAGE:
|
|
1371
|
+
case PBW_TYPE_MESSAGE:
|
|
1372
|
+
case PBW_TYPE_GROUP: {
|
|
1311
1373
|
message_t *sub;
|
|
1312
1374
|
|
|
1313
1375
|
if (!rb_obj_is_kind_of(value, cMessage)) {
|
|
@@ -1335,10 +1397,6 @@ validate_value(VALUE owner, field_t *f, VALUE value)
|
|
|
1335
1397
|
}
|
|
1336
1398
|
return;
|
|
1337
1399
|
}
|
|
1338
|
-
case PBW_TYPE_GROUP:
|
|
1339
|
-
rb_raise(eError, "%s: group fields are not supported",
|
|
1340
|
-
RSTRING_PTR(f->name));
|
|
1341
|
-
return;
|
|
1342
1400
|
case PBW_TYPE_FLOAT:
|
|
1343
1401
|
case PBW_TYPE_DOUBLE:
|
|
1344
1402
|
if (!RB_INTEGER_TYPE_P(value) && !RB_FLOAT_TYPE_P(value)) {
|
|
@@ -1370,7 +1428,8 @@ value_build(field_t *f, VALUE value, int *moved)
|
|
|
1370
1428
|
StringValue(value);
|
|
1371
1429
|
return pbw_value_new_bytes(RSTRING_PTR(value),
|
|
1372
1430
|
(size_t)RSTRING_LEN(value));
|
|
1373
|
-
case PBW_TYPE_MESSAGE:
|
|
1431
|
+
case PBW_TYPE_MESSAGE:
|
|
1432
|
+
case PBW_TYPE_GROUP: {
|
|
1374
1433
|
message_t *sub;
|
|
1375
1434
|
pbw_message *tree;
|
|
1376
1435
|
pbw_value *built;
|
|
@@ -1415,7 +1474,8 @@ static VALUE
|
|
|
1415
1474
|
item_to_ruby(VALUE owner, field_t *f, pbw_value *value)
|
|
1416
1475
|
{
|
|
1417
1476
|
switch ((pbw_type)f->type) {
|
|
1418
|
-
case PBW_TYPE_MESSAGE:
|
|
1477
|
+
case PBW_TYPE_MESSAGE:
|
|
1478
|
+
case PBW_TYPE_GROUP: {
|
|
1419
1479
|
pbw_message *child = pbw_value_get_message(value);
|
|
1420
1480
|
|
|
1421
1481
|
return child == NULL ? Qnil : message_borrowed(owner, f->sub, child);
|
|
@@ -1640,10 +1700,6 @@ message_get(VALUE self, VALUE key)
|
|
|
1640
1700
|
type_t *type = message_type_of(self);
|
|
1641
1701
|
field_t *f = field_lookup(type, key);
|
|
1642
1702
|
|
|
1643
|
-
if (f->unsupported) {
|
|
1644
|
-
rb_raise(eError, "%s: group fields are not supported",
|
|
1645
|
-
RSTRING_PTR(f->name));
|
|
1646
|
-
}
|
|
1647
1703
|
if (message->kind == MSG_WIRE) {
|
|
1648
1704
|
return field_value(message, f);
|
|
1649
1705
|
}
|
|
@@ -1746,6 +1802,273 @@ message_decode(VALUE self, VALUE bytes)
|
|
|
1746
1802
|
(size_t)RSTRING_LEN(buffer), 0);
|
|
1747
1803
|
}
|
|
1748
1804
|
|
|
1805
|
+
/* The tree behind a message for printing/encoding: builders hold one, wire
|
|
1806
|
+
* views decode their bytes into a fresh tree the caller frees. */
|
|
1807
|
+
static pbw_message *
|
|
1808
|
+
message_print_tree(VALUE self, message_t *message, int *free_tree)
|
|
1809
|
+
{
|
|
1810
|
+
if (message->kind == MSG_BUILDER) {
|
|
1811
|
+
*free_tree = 0;
|
|
1812
|
+
return message_tree(self, NULL);
|
|
1813
|
+
}
|
|
1814
|
+
{
|
|
1815
|
+
type_t *type = message_type_of(self);
|
|
1816
|
+
pbw_message *tree = NULL;
|
|
1817
|
+
pbw_status status = pbw_decode(type->desc,
|
|
1818
|
+
(const uint8_t *)message->data + message->off,
|
|
1819
|
+
message->len, &tree);
|
|
1820
|
+
if (status != PBW_OK) {
|
|
1821
|
+
rb_raise(eError, "%s: %s", type_name(type),
|
|
1822
|
+
pbw_strerror(status));
|
|
1823
|
+
}
|
|
1824
|
+
*free_tree = 1;
|
|
1825
|
+
return tree;
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
static VALUE
|
|
1830
|
+
message_to_json(VALUE self)
|
|
1831
|
+
{
|
|
1832
|
+
message_t *message;
|
|
1833
|
+
pbw_message *tree;
|
|
1834
|
+
int free_tree = 0;
|
|
1835
|
+
char *text = NULL;
|
|
1836
|
+
size_t len = 0;
|
|
1837
|
+
pbw_status status;
|
|
1838
|
+
VALUE out;
|
|
1839
|
+
|
|
1840
|
+
TypedData_Get_Struct(self, message_t, &message_data_type, message);
|
|
1841
|
+
if (message->kind == MSG_CLOSED) {
|
|
1842
|
+
rb_raise(eError, "%s: message is closed",
|
|
1843
|
+
type_name(message_type_of(self)));
|
|
1844
|
+
}
|
|
1845
|
+
tree = message_print_tree(self, message, &free_tree);
|
|
1846
|
+
status = pbw_json_print(tree, &text, &len);
|
|
1847
|
+
if (free_tree) {
|
|
1848
|
+
pbw_message_free(tree);
|
|
1849
|
+
}
|
|
1850
|
+
if (status != PBW_OK) {
|
|
1851
|
+
rb_raise(eError, "%s: %s", type_name(message_type_of(self)),
|
|
1852
|
+
pbw_strerror(status));
|
|
1853
|
+
}
|
|
1854
|
+
out = rb_utf8_str_new(text, (long)len);
|
|
1855
|
+
pbw_free(text);
|
|
1856
|
+
return out;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
/* Text format of a message; an optional false argument drops unknown
|
|
1860
|
+
* fields (the runner's print_unknown_fields flag) instead of printing
|
|
1861
|
+
* them numerically. */
|
|
1862
|
+
static VALUE
|
|
1863
|
+
message_to_text(int argc, VALUE *argv, VALUE self)
|
|
1864
|
+
{
|
|
1865
|
+
message_t *message;
|
|
1866
|
+
pbw_message *tree;
|
|
1867
|
+
int free_tree = 0;
|
|
1868
|
+
char *text = NULL;
|
|
1869
|
+
size_t len = 0;
|
|
1870
|
+
pbw_status status;
|
|
1871
|
+
VALUE out;
|
|
1872
|
+
/* Absent means print (the binding's standing behavior). */
|
|
1873
|
+
int print_unknown = 1;
|
|
1874
|
+
VALUE flag;
|
|
1875
|
+
|
|
1876
|
+
if (rb_scan_args(argc, argv, "01", &flag) == 1) {
|
|
1877
|
+
print_unknown = RTEST(flag);
|
|
1878
|
+
}
|
|
1879
|
+
TypedData_Get_Struct(self, message_t, &message_data_type, message);
|
|
1880
|
+
if (message->kind == MSG_CLOSED) {
|
|
1881
|
+
rb_raise(eError, "%s: message is closed",
|
|
1882
|
+
type_name(message_type_of(self)));
|
|
1883
|
+
}
|
|
1884
|
+
tree = message_print_tree(self, message, &free_tree);
|
|
1885
|
+
status = pbw_text_print_opts(tree, print_unknown, &text, &len);
|
|
1886
|
+
if (free_tree) {
|
|
1887
|
+
pbw_message_free(tree);
|
|
1888
|
+
}
|
|
1889
|
+
if (status != PBW_OK) {
|
|
1890
|
+
rb_raise(eError, "%s: %s", type_name(message_type_of(self)),
|
|
1891
|
+
pbw_strerror(status));
|
|
1892
|
+
}
|
|
1893
|
+
out = rb_utf8_str_new(text, (long)len);
|
|
1894
|
+
pbw_free(text);
|
|
1895
|
+
return out;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
/* Parses text (JSON mapping or text format) into a new builder tree. */
|
|
1899
|
+
static VALUE
|
|
1900
|
+
message_parse(VALUE self, VALUE text, int json, int ignore_unknown)
|
|
1901
|
+
{
|
|
1902
|
+
message_t *message;
|
|
1903
|
+
type_t *type;
|
|
1904
|
+
pbw_message *tree = NULL;
|
|
1905
|
+
pbw_status status;
|
|
1906
|
+
|
|
1907
|
+
TypedData_Get_Struct(self, message_t, &message_data_type, message);
|
|
1908
|
+
if (message->kind == MSG_CLOSED) {
|
|
1909
|
+
rb_raise(eError, "%s: message is closed",
|
|
1910
|
+
type_name(message_type_of(self)));
|
|
1911
|
+
}
|
|
1912
|
+
type = message_type_of(self);
|
|
1913
|
+
StringValue(text);
|
|
1914
|
+
if (json) {
|
|
1915
|
+
pbw_json_parse_options opts;
|
|
1916
|
+
|
|
1917
|
+
opts.ignore_unknown_fields = ignore_unknown;
|
|
1918
|
+
status = pbw_json_parse_opts(type->desc, RSTRING_PTR(text),
|
|
1919
|
+
(size_t)RSTRING_LEN(text), &opts, &tree);
|
|
1920
|
+
} else {
|
|
1921
|
+
status = pbw_text_parse(type->desc, RSTRING_PTR(text),
|
|
1922
|
+
(size_t)RSTRING_LEN(text), &tree);
|
|
1923
|
+
}
|
|
1924
|
+
if (status != PBW_OK) {
|
|
1925
|
+
rb_raise(eError, "%s: %s", type_name(type), pbw_strerror(status));
|
|
1926
|
+
}
|
|
1927
|
+
return message_builder_new(message->type, message->holder, tree, Qnil,
|
|
1928
|
+
1);
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
/* An optional trailing true ignores unknown JSON fields (the conformance
|
|
1932
|
+
* JSON_IGNORE_UNKNOWN category); by default they fail, like text. */
|
|
1933
|
+
static VALUE
|
|
1934
|
+
message_from_json(int argc, VALUE *argv, VALUE self)
|
|
1935
|
+
{
|
|
1936
|
+
VALUE text;
|
|
1937
|
+
VALUE ignore = Qfalse;
|
|
1938
|
+
|
|
1939
|
+
rb_scan_args(argc, argv, "11", &text, &ignore);
|
|
1940
|
+
return message_parse(self, text, 1, RTEST(ignore));
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
/* Materializes a decoded view into a builder holding the parsed tree, so
|
|
1944
|
+
* re-encoding is canonical (oneof collapses to the last member, like the
|
|
1945
|
+
* reference): wire views hand their own bytes back instead. */
|
|
1946
|
+
static VALUE
|
|
1947
|
+
message_to_builder(VALUE self)
|
|
1948
|
+
{
|
|
1949
|
+
message_t *message;
|
|
1950
|
+
type_t *type;
|
|
1951
|
+
pbw_message *tree = NULL;
|
|
1952
|
+
pbw_status status;
|
|
1953
|
+
|
|
1954
|
+
TypedData_Get_Struct(self, message_t, &message_data_type, message);
|
|
1955
|
+
if (message->kind == MSG_CLOSED) {
|
|
1956
|
+
rb_raise(eError, "%s: message is closed",
|
|
1957
|
+
type_name(message_type_of(self)));
|
|
1958
|
+
}
|
|
1959
|
+
if (message->kind == MSG_BUILDER) {
|
|
1960
|
+
return self;
|
|
1961
|
+
}
|
|
1962
|
+
type = message_type_of(self);
|
|
1963
|
+
status = pbw_decode(type->desc,
|
|
1964
|
+
(const uint8_t *)message->data + message->off,
|
|
1965
|
+
message->len, &tree);
|
|
1966
|
+
if (status != PBW_OK) {
|
|
1967
|
+
rb_raise(eError, "%s: %s", type_name(type), pbw_strerror(status));
|
|
1968
|
+
}
|
|
1969
|
+
return message_builder_new(message->type, message->holder, tree, Qnil,
|
|
1970
|
+
1);
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
static VALUE
|
|
1974
|
+
message_from_text(VALUE self, VALUE text)
|
|
1975
|
+
{
|
|
1976
|
+
return message_parse(self, text, 0, 0);
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
static VALUE
|
|
1980
|
+
message_encode_deterministic(VALUE self)
|
|
1981
|
+
{
|
|
1982
|
+
message_t *message;
|
|
1983
|
+
pbw_message *tree;
|
|
1984
|
+
int free_tree = 0;
|
|
1985
|
+
uint8_t *wire = NULL;
|
|
1986
|
+
size_t wire_len = 0;
|
|
1987
|
+
pbw_status status;
|
|
1988
|
+
VALUE out;
|
|
1989
|
+
|
|
1990
|
+
TypedData_Get_Struct(self, message_t, &message_data_type, message);
|
|
1991
|
+
if (message->kind == MSG_CLOSED) {
|
|
1992
|
+
rb_raise(eError, "%s: message is closed",
|
|
1993
|
+
type_name(message_type_of(self)));
|
|
1994
|
+
}
|
|
1995
|
+
tree = message_print_tree(self, message, &free_tree);
|
|
1996
|
+
status = pbw_encode_deterministic(tree, &wire, &wire_len);
|
|
1997
|
+
if (free_tree) {
|
|
1998
|
+
pbw_message_free(tree);
|
|
1999
|
+
}
|
|
2000
|
+
if (status != PBW_OK) {
|
|
2001
|
+
rb_raise(eError, "%s: %s", type_name(message_type_of(self)),
|
|
2002
|
+
pbw_strerror(status));
|
|
2003
|
+
}
|
|
2004
|
+
out = rb_str_new((const char *)wire, (long)wire_len);
|
|
2005
|
+
pbw_free(wire);
|
|
2006
|
+
return out;
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
static VALUE
|
|
2010
|
+
message_encode_delimited(VALUE self)
|
|
2011
|
+
{
|
|
2012
|
+
message_t *message;
|
|
2013
|
+
pbw_message *tree;
|
|
2014
|
+
int free_tree = 0;
|
|
2015
|
+
uint8_t *wire = NULL;
|
|
2016
|
+
size_t wire_len = 0;
|
|
2017
|
+
pbw_status status;
|
|
2018
|
+
VALUE out;
|
|
2019
|
+
|
|
2020
|
+
TypedData_Get_Struct(self, message_t, &message_data_type, message);
|
|
2021
|
+
if (message->kind == MSG_CLOSED) {
|
|
2022
|
+
rb_raise(eError, "%s: message is closed",
|
|
2023
|
+
type_name(message_type_of(self)));
|
|
2024
|
+
}
|
|
2025
|
+
tree = message_print_tree(self, message, &free_tree);
|
|
2026
|
+
status = pbw_encode_delimited(tree, &wire, &wire_len);
|
|
2027
|
+
if (free_tree) {
|
|
2028
|
+
pbw_message_free(tree);
|
|
2029
|
+
}
|
|
2030
|
+
if (status != PBW_OK) {
|
|
2031
|
+
rb_raise(eError, "%s: %s", type_name(message_type_of(self)),
|
|
2032
|
+
pbw_strerror(status));
|
|
2033
|
+
}
|
|
2034
|
+
out = rb_str_new((const char *)wire, (long)wire_len);
|
|
2035
|
+
pbw_free(wire);
|
|
2036
|
+
return out;
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
/* One frame from a byte string: [message, bytes consumed]. The message is
|
|
2040
|
+
* a builder holding the decoded tree. */
|
|
2041
|
+
static VALUE
|
|
2042
|
+
message_decode_delimited(VALUE self, VALUE bytes)
|
|
2043
|
+
{
|
|
2044
|
+
message_t *message;
|
|
2045
|
+
type_t *type;
|
|
2046
|
+
pbw_message *tree = NULL;
|
|
2047
|
+
size_t consumed = 0;
|
|
2048
|
+
pbw_status status;
|
|
2049
|
+
VALUE out;
|
|
2050
|
+
VALUE pair[2];
|
|
2051
|
+
|
|
2052
|
+
TypedData_Get_Struct(self, message_t, &message_data_type, message);
|
|
2053
|
+
if (message->kind == MSG_CLOSED) {
|
|
2054
|
+
rb_raise(eError, "%s: message is closed",
|
|
2055
|
+
type_name(message_type_of(self)));
|
|
2056
|
+
}
|
|
2057
|
+
type = message_type_of(self);
|
|
2058
|
+
StringValue(bytes);
|
|
2059
|
+
status = pbw_decode_delimited(type->desc, RSTRING_PTR(bytes),
|
|
2060
|
+
(size_t)RSTRING_LEN(bytes), &tree,
|
|
2061
|
+
&consumed);
|
|
2062
|
+
if (status != PBW_OK) {
|
|
2063
|
+
rb_raise(eError, "%s: %s", type_name(type), pbw_strerror(status));
|
|
2064
|
+
}
|
|
2065
|
+
pair[0] = message_builder_new(message->type, message->holder, tree,
|
|
2066
|
+
Qnil, 1);
|
|
2067
|
+
pair[1] = SIZET2NUM(consumed);
|
|
2068
|
+
out = rb_ary_new_from_values(2, pair);
|
|
2069
|
+
return out;
|
|
2070
|
+
}
|
|
2071
|
+
|
|
1749
2072
|
static VALUE
|
|
1750
2073
|
message_close(VALUE self)
|
|
1751
2074
|
{
|
|
@@ -1828,6 +2151,14 @@ Init_pbwire_ext(void)
|
|
|
1828
2151
|
rb_define_method(cMessage, "[]", message_get, 1);
|
|
1829
2152
|
rb_define_method(cMessage, "encode", message_encode, 0);
|
|
1830
2153
|
rb_define_method(cMessage, "decode", message_decode, 1);
|
|
2154
|
+
rb_define_method(cMessage, "to_json", message_to_json, 0);
|
|
2155
|
+
rb_define_method(cMessage, "to_text", message_to_text, -1);
|
|
2156
|
+
rb_define_method(cMessage, "from_json", message_from_json, -1);
|
|
2157
|
+
rb_define_method(cMessage, "to_builder", message_to_builder, 0);
|
|
2158
|
+
rb_define_method(cMessage, "from_text", message_from_text, 1);
|
|
2159
|
+
rb_define_method(cMessage, "encode_deterministic", message_encode_deterministic, 0);
|
|
2160
|
+
rb_define_method(cMessage, "encode_delimited", message_encode_delimited, 0);
|
|
2161
|
+
rb_define_method(cMessage, "decode_delimited", message_decode_delimited, 1);
|
|
1831
2162
|
rb_define_method(cMessage, "close", message_close, 0);
|
|
1832
2163
|
rb_define_method(cMessage, "release", message_close, 0);
|
|
1833
2164
|
rb_define_method(cMessage, "closed?", message_closed_p, 0);
|