unibuf 0.1.0 → 0.1.2
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/.rubocop_todo.yml +178 -330
- data/CODE_OF_CONDUCT.md +132 -0
- data/README.adoc +443 -254
- data/docs/CAPNPROTO.adoc +436 -0
- data/docs/FLATBUFFERS.adoc +430 -0
- data/docs/PROTOBUF.adoc +515 -0
- data/docs/TXTPROTO.adoc +369 -0
- data/lib/unibuf/commands/convert.rb +60 -2
- data/lib/unibuf/commands/schema.rb +68 -11
- data/lib/unibuf/errors.rb +23 -26
- data/lib/unibuf/models/capnproto/enum_definition.rb +72 -0
- data/lib/unibuf/models/capnproto/field_definition.rb +81 -0
- data/lib/unibuf/models/capnproto/interface_definition.rb +70 -0
- data/lib/unibuf/models/capnproto/method_definition.rb +81 -0
- data/lib/unibuf/models/capnproto/schema.rb +84 -0
- data/lib/unibuf/models/capnproto/struct_definition.rb +96 -0
- data/lib/unibuf/models/capnproto/union_definition.rb +62 -0
- data/lib/unibuf/models/flatbuffers/enum_definition.rb +69 -0
- data/lib/unibuf/models/flatbuffers/field_definition.rb +88 -0
- data/lib/unibuf/models/flatbuffers/schema.rb +102 -0
- data/lib/unibuf/models/flatbuffers/struct_definition.rb +70 -0
- data/lib/unibuf/models/flatbuffers/table_definition.rb +73 -0
- data/lib/unibuf/models/flatbuffers/union_definition.rb +60 -0
- data/lib/unibuf/models/message.rb +10 -0
- data/lib/unibuf/models/values/scalar_value.rb +2 -2
- data/lib/unibuf/parsers/binary/wire_format_parser.rb +199 -19
- data/lib/unibuf/parsers/capnproto/binary_parser.rb +267 -0
- data/lib/unibuf/parsers/capnproto/grammar.rb +272 -0
- data/lib/unibuf/parsers/capnproto/list_reader.rb +208 -0
- data/lib/unibuf/parsers/capnproto/pointer_decoder.rb +163 -0
- data/lib/unibuf/parsers/capnproto/processor.rb +348 -0
- data/lib/unibuf/parsers/capnproto/segment_reader.rb +131 -0
- data/lib/unibuf/parsers/capnproto/struct_reader.rb +199 -0
- data/lib/unibuf/parsers/flatbuffers/binary_parser.rb +325 -0
- data/lib/unibuf/parsers/flatbuffers/grammar.rb +235 -0
- data/lib/unibuf/parsers/flatbuffers/processor.rb +299 -0
- data/lib/unibuf/parsers/textproto/grammar.rb +1 -1
- data/lib/unibuf/parsers/textproto/processor.rb +10 -0
- data/lib/unibuf/serializers/binary_serializer.rb +218 -0
- data/lib/unibuf/serializers/capnproto/binary_serializer.rb +402 -0
- data/lib/unibuf/serializers/capnproto/list_writer.rb +199 -0
- data/lib/unibuf/serializers/capnproto/pointer_encoder.rb +118 -0
- data/lib/unibuf/serializers/capnproto/segment_builder.rb +124 -0
- data/lib/unibuf/serializers/capnproto/struct_writer.rb +139 -0
- data/lib/unibuf/serializers/flatbuffers/binary_serializer.rb +167 -0
- data/lib/unibuf/validators/type_validator.rb +1 -1
- data/lib/unibuf/version.rb +1 -1
- data/lib/unibuf.rb +27 -0
- metadata +36 -1
data/docs/TXTPROTO.adoc
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
= Protocol Buffers Text Format (Textproto) Support
|
|
2
|
+
|
|
3
|
+
:toc:
|
|
4
|
+
:toclevels: 3
|
|
5
|
+
|
|
6
|
+
== Purpose
|
|
7
|
+
|
|
8
|
+
Unibuf provides complete support for Protocol Buffers text format (textproto), a human-readable representation of protobuf messages.
|
|
9
|
+
|
|
10
|
+
Features:
|
|
11
|
+
|
|
12
|
+
* Parse `.txtpb` and `.textproto` files
|
|
13
|
+
* No schema required for parsing (schema recommended for validation)
|
|
14
|
+
* Support all Protocol Buffers field types
|
|
15
|
+
* Nested messages and repeated fields
|
|
16
|
+
* Comments and whitespace handling
|
|
17
|
+
* Round-trip serialization
|
|
18
|
+
* Rich domain models
|
|
19
|
+
|
|
20
|
+
== Text Format Overview
|
|
21
|
+
|
|
22
|
+
Text format (textproto) is:
|
|
23
|
+
|
|
24
|
+
Human-readable::
|
|
25
|
+
Easy to read and edit by humans
|
|
26
|
+
|
|
27
|
+
Self-documenting::
|
|
28
|
+
Field names make structure obvious
|
|
29
|
+
|
|
30
|
+
Configuration-friendly::
|
|
31
|
+
Ideal for configuration files
|
|
32
|
+
|
|
33
|
+
Debug-friendly::
|
|
34
|
+
Easy to inspect message contents
|
|
35
|
+
|
|
36
|
+
== Parsing Text Format
|
|
37
|
+
|
|
38
|
+
=== Basic parsing
|
|
39
|
+
|
|
40
|
+
[source,ruby]
|
|
41
|
+
----
|
|
42
|
+
require "unibuf"
|
|
43
|
+
|
|
44
|
+
# Parse from file
|
|
45
|
+
message = Unibuf.parse_textproto_file("config.txtpb") # <1>
|
|
46
|
+
|
|
47
|
+
# Parse from string
|
|
48
|
+
text = <<~TEXTPROTO
|
|
49
|
+
name: "Alice"
|
|
50
|
+
id: 123
|
|
51
|
+
TEXTPROTO
|
|
52
|
+
|
|
53
|
+
message = Unibuf.parse_textproto(text) # <2>
|
|
54
|
+
|
|
55
|
+
# Access fields
|
|
56
|
+
puts message.find_field("name").value # => "Alice" # <3>
|
|
57
|
+
puts message.find_field("id").value # => 123 # <4>
|
|
58
|
+
----
|
|
59
|
+
<1> Parse from file
|
|
60
|
+
<2> Parse from string
|
|
61
|
+
<3> Access string field
|
|
62
|
+
<4> Access integer field
|
|
63
|
+
|
|
64
|
+
=== Parsing nested messages
|
|
65
|
+
|
|
66
|
+
[source,ruby]
|
|
67
|
+
----
|
|
68
|
+
text = <<~TEXTPROTO
|
|
69
|
+
name: "Alice"
|
|
70
|
+
address {
|
|
71
|
+
street: "123 Main St"
|
|
72
|
+
city: "Springfield"
|
|
73
|
+
}
|
|
74
|
+
TEXTPROTO
|
|
75
|
+
|
|
76
|
+
message = Unibuf.parse_textproto(text) # <1>
|
|
77
|
+
|
|
78
|
+
# Access nested message
|
|
79
|
+
address = message.find_field("address").value # <2>
|
|
80
|
+
puts address.find_field("street").value # => "123 Main St" # <3>
|
|
81
|
+
----
|
|
82
|
+
<1> Parse text with nested message
|
|
83
|
+
<2> Get nested message field
|
|
84
|
+
<3> Access field in nested message
|
|
85
|
+
|
|
86
|
+
=== Parsing repeated fields
|
|
87
|
+
|
|
88
|
+
[source,ruby]
|
|
89
|
+
----
|
|
90
|
+
text = <<~TEXTPROTO
|
|
91
|
+
name: "Alice"
|
|
92
|
+
tags: "important"
|
|
93
|
+
tags: "customer"
|
|
94
|
+
tags: "vip"
|
|
95
|
+
TEXTPROTO
|
|
96
|
+
|
|
97
|
+
message = Unibuf.parse_textproto(text) # <1>
|
|
98
|
+
|
|
99
|
+
# Access repeated field
|
|
100
|
+
tags = message.find_fields("tags") # <2>
|
|
101
|
+
tag_values = tags.map(&:value) # => ["important", "customer", "vip"] # <3>
|
|
102
|
+
----
|
|
103
|
+
<1> Parse text with repeated fields
|
|
104
|
+
<2> Find all fields with name "tags"
|
|
105
|
+
<3> Extract values
|
|
106
|
+
|
|
107
|
+
== Text Format Syntax
|
|
108
|
+
|
|
109
|
+
=== Field types
|
|
110
|
+
|
|
111
|
+
String fields::
|
|
112
|
+
+
|
|
113
|
+
[source,textproto]
|
|
114
|
+
----
|
|
115
|
+
name: "Alice"
|
|
116
|
+
description: "A person"
|
|
117
|
+
----
|
|
118
|
+
|
|
119
|
+
Integer fields::
|
|
120
|
+
+
|
|
121
|
+
[source,textproto]
|
|
122
|
+
----
|
|
123
|
+
id: 123
|
|
124
|
+
count: -42
|
|
125
|
+
large: 9223372036854775807
|
|
126
|
+
----
|
|
127
|
+
|
|
128
|
+
Float fields::
|
|
129
|
+
+
|
|
130
|
+
[source,textproto]
|
|
131
|
+
----
|
|
132
|
+
price: 19.99
|
|
133
|
+
ratio: 0.5
|
|
134
|
+
scientific: 1.23e-4
|
|
135
|
+
----
|
|
136
|
+
|
|
137
|
+
Boolean fields::
|
|
138
|
+
+
|
|
139
|
+
[source,textproto]
|
|
140
|
+
----
|
|
141
|
+
enabled: true
|
|
142
|
+
active: false
|
|
143
|
+
----
|
|
144
|
+
|
|
145
|
+
=== Nested messages
|
|
146
|
+
|
|
147
|
+
[source,textproto]
|
|
148
|
+
----
|
|
149
|
+
person {
|
|
150
|
+
name: "Alice"
|
|
151
|
+
address {
|
|
152
|
+
street: "123 Main St"
|
|
153
|
+
city: "Springfield"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
----
|
|
157
|
+
|
|
158
|
+
=== Repeated fields
|
|
159
|
+
|
|
160
|
+
[source,textproto]
|
|
161
|
+
----
|
|
162
|
+
# Multiple values for same field
|
|
163
|
+
tags: "tag1"
|
|
164
|
+
tags: "tag2"
|
|
165
|
+
tags: "tag3"
|
|
166
|
+
|
|
167
|
+
# Or as nested messages
|
|
168
|
+
people {
|
|
169
|
+
name: "Alice"
|
|
170
|
+
}
|
|
171
|
+
people {
|
|
172
|
+
name: "Bob"
|
|
173
|
+
}
|
|
174
|
+
----
|
|
175
|
+
|
|
176
|
+
=== Comments
|
|
177
|
+
|
|
178
|
+
[source,textproto]
|
|
179
|
+
----
|
|
180
|
+
# Line comment
|
|
181
|
+
name: "Alice" # Inline comment
|
|
182
|
+
|
|
183
|
+
/*
|
|
184
|
+
* Block comment
|
|
185
|
+
*/
|
|
186
|
+
id: 123
|
|
187
|
+
----
|
|
188
|
+
|
|
189
|
+
== Domain Models
|
|
190
|
+
|
|
191
|
+
=== Message model
|
|
192
|
+
|
|
193
|
+
[source,ruby]
|
|
194
|
+
----
|
|
195
|
+
message = Unibuf.parse_textproto_file("data.txtpb")
|
|
196
|
+
|
|
197
|
+
# Classification
|
|
198
|
+
message.nested? # Has nested messages?
|
|
199
|
+
message.scalar_only? # Only scalar values?
|
|
200
|
+
message.repeated_fields? # Has repeated fields?
|
|
201
|
+
|
|
202
|
+
# Queries
|
|
203
|
+
message.find_field("name") # Find single field
|
|
204
|
+
message.find_fields("tags") # Find all with name
|
|
205
|
+
message.field_names # All field names
|
|
206
|
+
message.repeated_field_names # Repeated field names
|
|
207
|
+
|
|
208
|
+
# Traversal
|
|
209
|
+
message.traverse_depth_first { |field| puts field.name }
|
|
210
|
+
message.traverse_breadth_first { |field| puts field.value }
|
|
211
|
+
message.depth # Maximum nesting depth
|
|
212
|
+
----
|
|
213
|
+
|
|
214
|
+
=== Field model
|
|
215
|
+
|
|
216
|
+
[source,ruby]
|
|
217
|
+
----
|
|
218
|
+
field = message.find_field("address")
|
|
219
|
+
|
|
220
|
+
# Properties
|
|
221
|
+
field.name # Field name
|
|
222
|
+
field.value # Field value
|
|
223
|
+
field.repeated? # Is repeated field?
|
|
224
|
+
|
|
225
|
+
# Type classification
|
|
226
|
+
field.scalar? # Scalar value?
|
|
227
|
+
field.message? # Nested message?
|
|
228
|
+
field.list? # List value?
|
|
229
|
+
field.map? # Map value?
|
|
230
|
+
----
|
|
231
|
+
|
|
232
|
+
=== Value types
|
|
233
|
+
|
|
234
|
+
ScalarValue::
|
|
235
|
+
Strings, integers, floats, booleans
|
|
236
|
+
|
|
237
|
+
MessageValue::
|
|
238
|
+
Nested messages
|
|
239
|
+
|
|
240
|
+
ListValue::
|
|
241
|
+
Repeated fields
|
|
242
|
+
|
|
243
|
+
MapValue::
|
|
244
|
+
Key-value maps
|
|
245
|
+
|
|
246
|
+
== Serialization
|
|
247
|
+
|
|
248
|
+
=== Serializing to text format
|
|
249
|
+
|
|
250
|
+
[source,ruby]
|
|
251
|
+
----
|
|
252
|
+
# Parse message
|
|
253
|
+
message = Unibuf.parse_textproto_file("input.txtpb") # <1>
|
|
254
|
+
|
|
255
|
+
# Modify if needed
|
|
256
|
+
# ... modifications ...
|
|
257
|
+
|
|
258
|
+
# Serialize back to text format
|
|
259
|
+
output = message.to_textproto # <2>
|
|
260
|
+
|
|
261
|
+
# Write to file
|
|
262
|
+
File.write("output.txtpb", output) # <3>
|
|
263
|
+
|
|
264
|
+
# Verify round-trip
|
|
265
|
+
reparsed = Unibuf.parse_textproto(output) # <4>
|
|
266
|
+
puts message == reparsed # => true # <5>
|
|
267
|
+
----
|
|
268
|
+
<1> Parse original
|
|
269
|
+
<2> Serialize to text
|
|
270
|
+
<3> Write output
|
|
271
|
+
<4> Parse again
|
|
272
|
+
<5> Verify equivalence
|
|
273
|
+
|
|
274
|
+
== With Schema Validation
|
|
275
|
+
|
|
276
|
+
=== Parsing with validation
|
|
277
|
+
|
|
278
|
+
[source,ruby]
|
|
279
|
+
----
|
|
280
|
+
# Load schema
|
|
281
|
+
schema = Unibuf.parse_schema("schema.proto") # <1>
|
|
282
|
+
|
|
283
|
+
# Parse text format
|
|
284
|
+
message = Unibuf.parse_textproto_file("data.txtpb") # <2>
|
|
285
|
+
|
|
286
|
+
# Validate
|
|
287
|
+
validator = Unibuf::Validators::SchemaValidator.new(schema) # <3>
|
|
288
|
+
validator.validate!(message, "Person") # <4>
|
|
289
|
+
----
|
|
290
|
+
<1> Load schema
|
|
291
|
+
<2> Parse text format
|
|
292
|
+
<3> Create validator
|
|
293
|
+
<4> Validate against schema
|
|
294
|
+
|
|
295
|
+
== Testing
|
|
296
|
+
|
|
297
|
+
=== Test coverage
|
|
298
|
+
|
|
299
|
+
Text format implementation includes:
|
|
300
|
+
|
|
301
|
+
Grammar tests (60+ tests)::
|
|
302
|
+
All syntax elements, strings, numbers, booleans, nested messages
|
|
303
|
+
|
|
304
|
+
Parser tests (40+ tests)::
|
|
305
|
+
Field parsing, message building, error handling
|
|
306
|
+
|
|
307
|
+
Integration tests (40+ tests)::
|
|
308
|
+
Real-world Protocol Buffer files (Google Fonts metadata)
|
|
309
|
+
|
|
310
|
+
Serialization tests (20+ tests)::
|
|
311
|
+
Round-trip verification, formatting
|
|
312
|
+
|
|
313
|
+
**Total: 160+ tests, 100% passing**
|
|
314
|
+
|
|
315
|
+
=== Running tests
|
|
316
|
+
|
|
317
|
+
[source,shell]
|
|
318
|
+
----
|
|
319
|
+
# Run textproto tests
|
|
320
|
+
bundle exec rspec spec/unibuf/parsers/textproto/
|
|
321
|
+
|
|
322
|
+
# Run specific test
|
|
323
|
+
bundle exec rspec spec/unibuf/parsers/textproto/grammar_spec.rb
|
|
324
|
+
----
|
|
325
|
+
|
|
326
|
+
== Real-World Examples
|
|
327
|
+
|
|
328
|
+
=== Google Fonts metadata
|
|
329
|
+
|
|
330
|
+
Unibuf successfully parses Google Fonts METADATA.pb files:
|
|
331
|
+
|
|
332
|
+
[source,ruby]
|
|
333
|
+
----
|
|
334
|
+
# Parse Google Font metadata
|
|
335
|
+
message = Unibuf.parse_file("METADATA.pb")
|
|
336
|
+
|
|
337
|
+
# Access font information
|
|
338
|
+
name = message.find_field("name")&.value
|
|
339
|
+
category = message.find_field("category")&.value
|
|
340
|
+
designers = message.find_fields("designer").map(&:value)
|
|
341
|
+
|
|
342
|
+
puts "Font: #{name}"
|
|
343
|
+
puts "Category: #{category}"
|
|
344
|
+
puts "Designers: #{designers.join(', ')}"
|
|
345
|
+
----
|
|
346
|
+
|
|
347
|
+
== References
|
|
348
|
+
|
|
349
|
+
Protocol Buffers text format::
|
|
350
|
+
https://protobuf.dev/reference/protobuf/textformat-spec/
|
|
351
|
+
|
|
352
|
+
Proto3 language guide::
|
|
353
|
+
https://protobuf.dev/programming-guides/proto3/
|
|
354
|
+
|
|
355
|
+
Encoding specification::
|
|
356
|
+
https://protobuf.dev/programming-guides/encoding/
|
|
357
|
+
|
|
358
|
+
== Support
|
|
359
|
+
|
|
360
|
+
For issues, questions, or contributions related to Protocol Buffers text format:
|
|
361
|
+
|
|
362
|
+
* GitHub Issues: https://github.com/lutaml/unibuf/issues
|
|
363
|
+
* Documentation: https://github.com/lutaml/unibuf/tree/main/docs
|
|
364
|
+
|
|
365
|
+
== Copyright and License
|
|
366
|
+
|
|
367
|
+
Copyright https://www.ribose.com[Ribose Inc.]
|
|
368
|
+
|
|
369
|
+
Licensed under the 3-clause BSD License.
|
|
@@ -42,12 +42,24 @@ module Unibuf
|
|
|
42
42
|
"Target format required"
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
valid_formats = %w[json yaml textproto]
|
|
45
|
+
valid_formats = %w[json yaml textproto binpb]
|
|
46
46
|
unless valid_formats.include?(target_format)
|
|
47
47
|
raise InvalidArgumentError,
|
|
48
48
|
"Invalid format '#{target_format}'. " \
|
|
49
49
|
"Valid formats: #{valid_formats.join(', ')}"
|
|
50
50
|
end
|
|
51
|
+
|
|
52
|
+
# Binary format requires schema
|
|
53
|
+
if target_format == "binpb" && !schema_file
|
|
54
|
+
raise InvalidArgumentError,
|
|
55
|
+
"Binary format requires --schema option"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Binary input requires schema
|
|
59
|
+
if binary_file?(file) && !schema_file
|
|
60
|
+
raise InvalidArgumentError,
|
|
61
|
+
"Binary input requires --schema option"
|
|
62
|
+
end
|
|
51
63
|
end
|
|
52
64
|
|
|
53
65
|
def load_message(file)
|
|
@@ -56,6 +68,8 @@ module Unibuf
|
|
|
56
68
|
load_from_json(file)
|
|
57
69
|
elsif yaml_file?(file)
|
|
58
70
|
load_from_yaml(file)
|
|
71
|
+
elsif binary_file?(file)
|
|
72
|
+
load_from_binary(file)
|
|
59
73
|
else
|
|
60
74
|
Unibuf.parse_file(file)
|
|
61
75
|
end
|
|
@@ -73,6 +87,11 @@ module Unibuf
|
|
|
73
87
|
Unibuf::Models::Message.from_hash(data)
|
|
74
88
|
end
|
|
75
89
|
|
|
90
|
+
def load_from_binary(file)
|
|
91
|
+
schema = load_schema
|
|
92
|
+
Unibuf.parse_binary_file(file, schema: schema)
|
|
93
|
+
end
|
|
94
|
+
|
|
76
95
|
def convert_message(message)
|
|
77
96
|
case target_format
|
|
78
97
|
when "json"
|
|
@@ -81,18 +100,45 @@ module Unibuf
|
|
|
81
100
|
message.to_yaml
|
|
82
101
|
when "textproto"
|
|
83
102
|
message.to_textproto
|
|
103
|
+
when "binpb"
|
|
104
|
+
schema = load_schema
|
|
105
|
+
message.to_binary(schema: schema, message_type: message_type)
|
|
84
106
|
end
|
|
85
107
|
end
|
|
86
108
|
|
|
87
109
|
def write_output(content)
|
|
88
110
|
if output_file
|
|
89
|
-
|
|
111
|
+
if target_format == "binpb"
|
|
112
|
+
File.binwrite(output_file, content)
|
|
113
|
+
else
|
|
114
|
+
File.write(output_file, content)
|
|
115
|
+
end
|
|
90
116
|
puts "Output written to #{output_file}" if verbose?
|
|
117
|
+
elsif target_format == "binpb"
|
|
118
|
+
$stdout.binmode
|
|
119
|
+
$stdout.write(content)
|
|
120
|
+
# Binary output to stdout
|
|
91
121
|
else
|
|
92
122
|
puts content
|
|
93
123
|
end
|
|
94
124
|
end
|
|
95
125
|
|
|
126
|
+
def load_schema
|
|
127
|
+
return @schema if @schema
|
|
128
|
+
|
|
129
|
+
unless schema_file
|
|
130
|
+
raise InvalidArgumentError,
|
|
131
|
+
"Schema required for binary format"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
unless File.exist?(schema_file)
|
|
135
|
+
raise FileNotFoundError,
|
|
136
|
+
"Schema file not found: #{schema_file}"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
@schema = Unibuf.parse_schema(schema_file)
|
|
140
|
+
end
|
|
141
|
+
|
|
96
142
|
def json_file?(file)
|
|
97
143
|
file.end_with?(".json")
|
|
98
144
|
end
|
|
@@ -101,6 +147,10 @@ module Unibuf
|
|
|
101
147
|
file.end_with?(".yaml", ".yml")
|
|
102
148
|
end
|
|
103
149
|
|
|
150
|
+
def binary_file?(file)
|
|
151
|
+
file.end_with?(".binpb", ".bin", ".pb")
|
|
152
|
+
end
|
|
153
|
+
|
|
104
154
|
def target_format
|
|
105
155
|
options[:to]
|
|
106
156
|
end
|
|
@@ -109,6 +159,14 @@ module Unibuf
|
|
|
109
159
|
options[:output]
|
|
110
160
|
end
|
|
111
161
|
|
|
162
|
+
def schema_file
|
|
163
|
+
options[:schema]
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def message_type
|
|
167
|
+
options[:message_type]
|
|
168
|
+
end
|
|
169
|
+
|
|
112
170
|
def verbose?
|
|
113
171
|
options[:verbose]
|
|
114
172
|
end
|
|
@@ -43,6 +43,8 @@ module Unibuf
|
|
|
43
43
|
Unibuf.parse_schema(file)
|
|
44
44
|
when ".fbs"
|
|
45
45
|
Unibuf.parse_flatbuffers_schema(file)
|
|
46
|
+
when ".capnp"
|
|
47
|
+
Unibuf.parse_capnproto_schema(file)
|
|
46
48
|
else
|
|
47
49
|
raise InvalidArgumentError,
|
|
48
50
|
"Unknown schema format: #{File.extname(file)}"
|
|
@@ -77,20 +79,75 @@ module Unibuf
|
|
|
77
79
|
|
|
78
80
|
def format_text(schema)
|
|
79
81
|
lines = []
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
lines << "
|
|
86
|
-
end
|
|
87
|
-
if schema.enums.any?
|
|
82
|
+
|
|
83
|
+
# Handle different schema types
|
|
84
|
+
if schema.respond_to?(:package)
|
|
85
|
+
# Proto3 schema
|
|
86
|
+
lines << "Package: #{schema.package}" if schema.package
|
|
87
|
+
lines << "Syntax: #{schema.syntax}"
|
|
88
88
|
lines << ""
|
|
89
|
-
lines << "
|
|
90
|
-
schema.
|
|
91
|
-
lines << " #{
|
|
89
|
+
lines << "Messages (#{schema.messages.size}):"
|
|
90
|
+
schema.messages.each do |msg|
|
|
91
|
+
lines << " #{msg.name} (#{msg.fields.size} fields)"
|
|
92
|
+
end
|
|
93
|
+
if schema.enums.any?
|
|
94
|
+
lines << ""
|
|
95
|
+
lines << "Enums (#{schema.enums.size}):"
|
|
96
|
+
schema.enums.each do |enum|
|
|
97
|
+
lines << " #{enum.name} (#{enum.values.size} values)"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
elsif schema.respond_to?(:file_id)
|
|
101
|
+
# Cap'n Proto schema
|
|
102
|
+
lines << "File ID: #{schema.file_id}" if schema.file_id
|
|
103
|
+
lines << ""
|
|
104
|
+
if schema.structs.any?
|
|
105
|
+
lines << "Structs (#{schema.structs.size}):"
|
|
106
|
+
schema.structs.each do |struct|
|
|
107
|
+
lines << " #{struct.name} (#{struct.fields.size} fields)"
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
if schema.enums.any?
|
|
111
|
+
lines << ""
|
|
112
|
+
lines << "Enums (#{schema.enums.size}):"
|
|
113
|
+
schema.enums.each do |enum|
|
|
114
|
+
lines << " #{enum.name} (#{enum.values.size} values)"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
if schema.interfaces.any?
|
|
118
|
+
lines << ""
|
|
119
|
+
lines << "Interfaces (#{schema.interfaces.size}):"
|
|
120
|
+
schema.interfaces.each do |interface|
|
|
121
|
+
lines << " #{interface.name} (#{interface.methods.size} methods)"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
else
|
|
125
|
+
# FlatBuffers schema
|
|
126
|
+
lines << "Namespace: #{schema.namespace}" if schema.namespace
|
|
127
|
+
lines << "Root Type: #{schema.root_type}" if schema.root_type
|
|
128
|
+
lines << ""
|
|
129
|
+
if schema.tables.any?
|
|
130
|
+
lines << "Tables (#{schema.tables.size}):"
|
|
131
|
+
schema.tables.each do |table|
|
|
132
|
+
lines << " #{table.name} (#{table.fields.size} fields)"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
if schema.structs.any?
|
|
136
|
+
lines << ""
|
|
137
|
+
lines << "Structs (#{schema.structs.size}):"
|
|
138
|
+
schema.structs.each do |struct|
|
|
139
|
+
lines << " #{struct.name} (#{struct.fields.size} fields)"
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
if schema.enums.any?
|
|
143
|
+
lines << ""
|
|
144
|
+
lines << "Enums (#{schema.enums.size}):"
|
|
145
|
+
schema.enums.each do |enum|
|
|
146
|
+
lines << " #{enum.name} (#{enum.values.size} values)"
|
|
147
|
+
end
|
|
92
148
|
end
|
|
93
149
|
end
|
|
150
|
+
|
|
94
151
|
lines.join("\n")
|
|
95
152
|
end
|
|
96
153
|
|
data/lib/unibuf/errors.rb
CHANGED
|
@@ -1,36 +1,33 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Unibuf
|
|
4
|
-
# Base error class
|
|
4
|
+
# Base error class
|
|
5
5
|
class Error < StandardError; end
|
|
6
6
|
|
|
7
|
-
#
|
|
7
|
+
# Parse error
|
|
8
8
|
class ParseError < Error; end
|
|
9
|
-
class SyntaxError < ParseError; end
|
|
10
|
-
class UnexpectedTokenError < ParseError; end
|
|
11
|
-
class UnterminatedStringError < ParseError; end
|
|
12
9
|
|
|
13
|
-
#
|
|
10
|
+
# Serialization error
|
|
11
|
+
class SerializationError < Error; end
|
|
12
|
+
|
|
13
|
+
# Validation error
|
|
14
14
|
class ValidationError < Error; end
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
# Schema validation error
|
|
16
17
|
class SchemaValidationError < ValidationError; end
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class FileNotFoundError <
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# CLI errors
|
|
33
|
-
class CLIError < Error; end
|
|
34
|
-
class InvalidArgumentError < CLIError; end
|
|
35
|
-
class CommandExecutionError < CLIError; end
|
|
18
|
+
|
|
19
|
+
# Type validation error
|
|
20
|
+
class TypeValidationError < ValidationError; end
|
|
21
|
+
|
|
22
|
+
# Invalid value error
|
|
23
|
+
class InvalidValueError < ValidationError; end
|
|
24
|
+
|
|
25
|
+
# Type coercion error
|
|
26
|
+
class TypeCoercionError < Error; end
|
|
27
|
+
|
|
28
|
+
# File not found error
|
|
29
|
+
class FileNotFoundError < Error; end
|
|
30
|
+
|
|
31
|
+
# Invalid argument error
|
|
32
|
+
class InvalidArgumentError < Error; end
|
|
36
33
|
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unibuf
|
|
4
|
+
module Models
|
|
5
|
+
module Capnproto
|
|
6
|
+
# Represents a Cap'n Proto enum definition
|
|
7
|
+
class EnumDefinition
|
|
8
|
+
attr_reader :name, :values, :annotations
|
|
9
|
+
|
|
10
|
+
def initialize(attributes = {})
|
|
11
|
+
@name = attributes[:name] || attributes["name"]
|
|
12
|
+
@values = attributes[:values] || attributes["values"] || {}
|
|
13
|
+
@annotations = Array(
|
|
14
|
+
attributes[:annotations] || attributes["annotations"],
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Queries
|
|
19
|
+
def value_names
|
|
20
|
+
values.keys
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def ordinals
|
|
24
|
+
values.values
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def find_value(name)
|
|
28
|
+
values[name]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def find_name_by_ordinal(ordinal)
|
|
32
|
+
values.find { |_name, ord| ord == ordinal }&.first
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Validation
|
|
36
|
+
def valid?
|
|
37
|
+
validate!
|
|
38
|
+
true
|
|
39
|
+
rescue ValidationError
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def validate!
|
|
44
|
+
raise ValidationError, "Enum name required" unless name
|
|
45
|
+
|
|
46
|
+
if values.empty?
|
|
47
|
+
raise ValidationError,
|
|
48
|
+
"Enum must have at least one value"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Check for duplicate ordinals
|
|
52
|
+
ordinal_counts = ordinals.tally
|
|
53
|
+
duplicates = ordinal_counts.select { |_ord, count| count > 1 }
|
|
54
|
+
unless duplicates.empty?
|
|
55
|
+
raise ValidationError,
|
|
56
|
+
"Duplicate ordinals in enum '#{name}': #{duplicates.keys.join(', ')}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
true
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def to_h
|
|
63
|
+
{
|
|
64
|
+
name: name,
|
|
65
|
+
values: values,
|
|
66
|
+
annotations: annotations,
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|