protobug 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,344 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "binary_encoding"
4
+ require_relative "errors"
5
+ require_relative "field"
6
+ require "stringio"
7
+
8
+ module Protobug
9
+ UNSET = Object.new
10
+ def UNSET.inspect
11
+ "<UNSET>"
12
+ end
13
+
14
+ def UNSET.===(other)
15
+ other.equal? UNSET
16
+ end
17
+ UNSET.freeze
18
+
19
+ module Message
20
+ def self.extended(base)
21
+ base.class_eval do
22
+ @full_name = nil
23
+ @fields_by_number = {}
24
+ @fields_by_json_name = {}
25
+ @fields_by_name = {}
26
+ @reserved_ranges = []
27
+ @oneofs = {}
28
+ extend BaseDescriptor
29
+ include Protobug::Message::InstanceMethods
30
+ end
31
+ end
32
+
33
+ attr_accessor :full_name
34
+ attr_reader :fields_by_number, :fields_by_name, :fields_by_json_name, :reserved_ranges, :oneofs
35
+
36
+ def freeze
37
+ fields_by_number.freeze
38
+ fields_by_name.freeze
39
+ fields_by_json_name.freeze
40
+ full_name.freeze
41
+ reserved_ranges.freeze
42
+ oneofs.each_value(&:freeze)
43
+ oneofs.freeze
44
+ super
45
+ end
46
+
47
+ def optional(number, name, **kwargs)
48
+ if kwargs[:cardinality] && kwargs[:cardinality] != :optional
49
+ raise DefinitionError,
50
+ "expected cardinality: :optional, got #{kwargs[:cardinality].inspect}"
51
+ end
52
+ field(number, name, cardinality: :optional, **kwargs)
53
+ end
54
+
55
+ def repeated(number, name, **kwargs)
56
+ if kwargs[:cardinality] && kwargs[:cardinality] != :repeated
57
+ raise DefinitionError,
58
+ "expected cardinality: :repeated, got #{kwargs[:cardinality].inspect}"
59
+ end
60
+ field(number, name, cardinality: :repeated, **kwargs)
61
+ end
62
+
63
+ def map(number, name, **kwargs)
64
+ if kwargs[:type] && kwargs[:type] != :map
65
+ raise DefinitionError,
66
+ "expected type: :map, got #{kwargs[:type].inspect}"
67
+ end
68
+ repeated(number, name, type: :map, **kwargs)
69
+ end
70
+
71
+ def required(number, name, **kwargs)
72
+ if kwargs[:cardinality] && kwargs[:cardinality] != :required
73
+ raise DefinitionError,
74
+ "expected cardinality: :required, got #{kwargs[:cardinality].inspect}"
75
+ end
76
+ field(number, name, cardinality: :required, **kwargs)
77
+ end
78
+
79
+ def reserved_range(range)
80
+ raise DefinitionError, "expected Range, got #{range.inspect}" unless range.is_a? Range
81
+
82
+ reserved_ranges << range
83
+ end
84
+
85
+ def decode_json(json, registry:, ignore_unknown_fields: false)
86
+ require "json"
87
+ hash = begin
88
+ JSON.parse(json, allow_blank: false, create_additions: false, allow_nan: false, allow_infinity: false)
89
+ rescue JSON::ParserError => e
90
+ raise DecodeError, "JSON failed to parse: #{e.message}"
91
+ end
92
+ raise DecodeError, "expected hash, got #{hash.inspect}" unless hash.is_a? Hash
93
+
94
+ decode_json_hash(hash, registry: registry, ignore_unknown_fields: ignore_unknown_fields)
95
+ end
96
+
97
+ def decode_json_hash(json, registry:, ignore_unknown_fields: false)
98
+ return UNSET if json.nil?
99
+ raise DecodeError, "expected hash for #{self} (#{full_name}), got #{json.inspect}" unless json.is_a? Hash
100
+
101
+ message = new
102
+
103
+ json.each do |key, value|
104
+ field = fields_by_json_name[key]
105
+ unless field
106
+ next if ignore_unknown_fields
107
+
108
+ raise(UnknownFieldError, "unknown field #{key.inspect} in #{full_name}")
109
+ end
110
+
111
+ if field.oneof && message.send(field.oneof) && !value.nil?
112
+ raise DecodeError, "multiple oneof fields set in #{full_name}: #{message.send(field.oneof)} and #{field.name}"
113
+ end
114
+
115
+ field.json_decode(value, message, ignore_unknown_fields, registry)
116
+ end
117
+
118
+ message
119
+ end
120
+
121
+ def decode(binary, registry:, object: new)
122
+ binary.binmode
123
+ loop do
124
+ header = BinaryEncoding.decode_varint(binary)
125
+ break if header.nil?
126
+
127
+ wire_type = header & 0b111
128
+ number = (header ^ wire_type) >> 3
129
+
130
+ unless number.positive?
131
+ raise DecodeError,
132
+ "unexpected field number #{number} in #{full_name || fields_by_name.inspect}"
133
+ end
134
+
135
+ field = fields_by_number[number]
136
+
137
+ if field
138
+ field.binary_decode(binary, object, registry, wire_type)
139
+ else
140
+ object.unknown_fields << [number, wire_type, BinaryEncoding.read_field_value(binary, wire_type)]
141
+ end
142
+ end
143
+ object
144
+ end
145
+
146
+ def encode(message)
147
+ raise EncodeError, "expected #{self}, got #{message.inspect}" unless message.is_a? self
148
+
149
+ buf = fields_by_number.each_with_object("".b) do |(_number, field), outbuf|
150
+ next unless message.send(field.haser)
151
+
152
+ value = message.instance_variable_get(field.ivar)
153
+
154
+ field.binary_encode(value, outbuf)
155
+ end
156
+ message.unknown_fields.each_with_object(buf) do |(number, wire_type, value), outbuf|
157
+ BinaryEncoding.encode_varint((number << 3) | wire_type, outbuf)
158
+ case wire_type
159
+ when 0, 5
160
+ BinaryEncoding.encode_varint(value, outbuf)
161
+ when 2
162
+ BinaryEncoding.encode_length(value, outbuf)
163
+ else
164
+ raise EncodeError, "unknown wire_type: #{wire_type}"
165
+ end
166
+ end
167
+ end
168
+
169
+ def field(number, name, type:, **kwargs)
170
+ field =
171
+ case type
172
+ when :message
173
+ Field::MessageField
174
+ when :enum
175
+ Field::EnumField
176
+ when :bytes
177
+ Field::BytesField
178
+ when :string
179
+ Field::StringField
180
+ when :map
181
+ kwargs.delete(:cardinality) if kwargs[:cardinality] == :repeated
182
+ Field::MapField
183
+ when :int64
184
+ Field::Int64Field
185
+ when :uint64
186
+ Field::UInt64Field
187
+ when :sint64
188
+ Field::SInt64Field
189
+ when :fixed64
190
+ Field::Fixed64Field
191
+ when :sfixed64
192
+ Field::SFixed64Field
193
+ when :int32
194
+ Field::Int32Field
195
+ when :uint32
196
+ Field::UInt32Field
197
+ when :sint32
198
+ Field::SInt32Field
199
+ when :fixed32
200
+ Field::Fixed32Field
201
+ when :sfixed32
202
+ Field::SFixed32Field
203
+ when :bool
204
+ Field::BoolField
205
+ when :float
206
+ Field::FloatField
207
+ when :double
208
+ Field::DoubleField
209
+ when :group
210
+ Field::GroupField
211
+ else
212
+ raise ArgumentError, "Unknown field type #{type.inspect}"
213
+ end.new(number, name, **kwargs).freeze
214
+
215
+ raise DefinitionError, "duplicate field number #{number}" if fields_by_number[number]
216
+
217
+ fields_by_number[number] = field
218
+ raise DefinitionError, "duplicate field name #{name}" if fields_by_name[name]
219
+
220
+ fields_by_name[name] = field
221
+
222
+ fields_by_json_name[name] = field
223
+ fields_by_json_name[field.json_name] = field
224
+
225
+ define_method(field.setter) do |value|
226
+ return instance_variable_set(field.ivar, UNSET) if value.nil? && field.optional? && field.proto3_optional?
227
+
228
+ field.validate!(value, self)
229
+ instance_variable_set(field.ivar, value)
230
+ end
231
+
232
+ define_method(name) do
233
+ value = instance_variable_get(field.ivar)
234
+ UNSET == value ? field.default : value
235
+ end
236
+
237
+ define_method(field.haser) do
238
+ value = instance_variable_get(field.ivar)
239
+ return false if UNSET == value
240
+
241
+ return false if (!field.optional? || !field.proto3_optional?) && !field.oneof && field.default == value
242
+
243
+ if field.repeated?
244
+ !value.empty?
245
+ else
246
+ true
247
+ end
248
+ end
249
+
250
+ define_method(field.clearer) do
251
+ instance_variable_set(field.ivar, UNSET)
252
+ end
253
+
254
+ field.define_adder(self) if field.repeated?
255
+
256
+ return unless field.oneof
257
+
258
+ unless oneofs[field.oneof]
259
+ oneofs[field.oneof] = ary = []
260
+ define_method(field.oneof) do
261
+ ary.find { |f| send(f.haser) }&.name
262
+ end
263
+ end
264
+ oneofs[field.oneof] << field
265
+ end
266
+
267
+ module InstanceMethods
268
+ def ==(other)
269
+ self.class.full_name == other.class.full_name &&
270
+ self.class.fields_by_name.all? do |name, _|
271
+ send(name) == other.send(name)
272
+ end
273
+ end
274
+ alias eql? ==
275
+
276
+ attr_reader :unknown_fields
277
+
278
+ def initialize
279
+ super
280
+ self.class.fields_by_name.each_value do |field|
281
+ instance_variable_set(field.ivar, UNSET)
282
+ end
283
+ @unknown_fields = []
284
+ end
285
+
286
+ def pretty_print(pp)
287
+ fields_with_values = self.class.fields_by_name.select do |_name, field|
288
+ send(field.haser)
289
+ end
290
+ pp.group 2, "#{self.class}.new(", ")" do
291
+ pp.breakable
292
+ fields_with_values.each_with_index do |(name, field), idx|
293
+ pp.nest 2 do
294
+ unless idx.zero?
295
+ pp.text ","
296
+ pp.breakable " "
297
+ end
298
+ pp.text "#{name}: "
299
+ pp.pp send(field.name)
300
+ end
301
+ end
302
+ end
303
+ end
304
+
305
+ def hash
306
+ self.class.fields_by_name.map { |name, _| send(name) }.hash
307
+ end
308
+
309
+ def to_text
310
+ fields_with_values = self.class.fields_by_name.select do |_name, field|
311
+ send(field.haser)
312
+ end
313
+
314
+ fields_with_values.map do |_name, field|
315
+ value = send(field.name)
316
+ field.to_text(value)
317
+ end.join("\n")
318
+ end
319
+
320
+ def to_proto
321
+ self.class.encode(self)
322
+ end
323
+
324
+ def as_json(print_unknown_fields: false)
325
+ fields_with_values = self.class.fields_by_name.select do |_name, field|
326
+ send(field.haser)
327
+ end
328
+
329
+ fields_with_values.to_h do |_name, field|
330
+ value = send(field.name)
331
+
332
+ [field.json_name, field.json_encode(value, print_unknown_fields: print_unknown_fields)]
333
+ end
334
+ end
335
+
336
+ def to_json(print_unknown_fields: false)
337
+ require "json"
338
+ JSON.generate(as_json(print_unknown_fields: print_unknown_fields), allow_infinity: true)
339
+ rescue JSON::GeneratorError => e
340
+ raise EncodeError, "failed to generate JSON: #{e}"
341
+ end
342
+ end
343
+ end
344
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protobug
4
+ class Registry
5
+ def initialize(&blk)
6
+ @registry = {}
7
+ return unless blk
8
+
9
+ yield self
10
+ freeze
11
+ end
12
+
13
+ def freeze
14
+ @registry.freeze
15
+ super
16
+ end
17
+
18
+ def register(klass)
19
+ unless klass.is_a? Protobug::BaseDescriptor
20
+ raise ArgumentError,
21
+ "expected Protobug::BaseDescriptor, got #{klass.inspect}"
22
+ end
23
+
24
+ full_name = klass.full_name
25
+ existing = @registry[full_name]
26
+ raise ArgumentError, "duplicate class #{full_name}" if existing && existing != klass
27
+
28
+ @registry[full_name] = klass
29
+ klass.freeze
30
+ end
31
+
32
+ def fetch(...)
33
+ @registry.fetch(...)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protobug
4
+ VERSION = "0.1.0"
5
+ end
data/lib/protobug.rb ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "protobug/version"
4
+
5
+ module Protobug
6
+ class Error < StandardError; end
7
+ # Your code goes here...
8
+ end
9
+
10
+ require_relative "protobug/base_descriptor"
11
+ require_relative "protobug/enum"
12
+ require_relative "protobug/message"
13
+ require_relative "protobug/registry"
14
+ require "date"
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: protobug
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Giddins
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-04-25 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - segiddins@segiddins.me
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - CHANGELOG.md
21
+ - CODE_OF_CONDUCT.md
22
+ - LICENSE.txt
23
+ - README.md
24
+ - lib/protobug.rb
25
+ - lib/protobug/base_descriptor.rb
26
+ - lib/protobug/binary_encoding.rb
27
+ - lib/protobug/enum.rb
28
+ - lib/protobug/errors.rb
29
+ - lib/protobug/field.rb
30
+ - lib/protobug/message.rb
31
+ - lib/protobug/registry.rb
32
+ - lib/protobug/version.rb
33
+ homepage: https://github.com/segiddins/protobug
34
+ licenses:
35
+ - MIT
36
+ metadata:
37
+ allowed_push_host: https://rubygems.org/
38
+ homepage_uri: https://github.com/segiddins/protobug
39
+ source_code_uri: https://github.com/segiddins/protobug
40
+ rubygems_mfa_required: 'true'
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 3.0.0
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubygems_version: 3.5.9
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: An embeddable protobuf compiler & runtime for Ruby
60
+ test_files: []