protod 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,328 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Protod
4
+ class Interpreter
5
+ class Builtin
6
+ def self.setup!
7
+ google_protobuf = Protod.find_or_register_package('google.protobuf')
8
+ google_type = Protod.find_or_register_package('google.type', url: 'https://github.com/googleapis/googleapis.git')
9
+
10
+ Interpreter.register_for('RBS::Types::Bases::Any', parent: google_protobuf, path: 'google/protobuf/any.proto', force: false, ignore: true) do
11
+ def proto_ident
12
+ 'Any'
13
+ end
14
+
15
+ def to_pb_from(rb)
16
+ i = Protod::Interpreter.find_by(rb.class)
17
+
18
+ raise NotImplementedError, "Not found the interpreter for #{rb.class.name}. You can define a interpreter using Protod::Interpreter.register_for" unless i
19
+
20
+ value = if i.pb_const
21
+ pb = i.to_pb_from(rb)
22
+
23
+ pb.protod__object_id = rb.object_id.to_s if pb.respond_to?(:protod__object_id)
24
+
25
+ i.pb_const.encode(pb)
26
+ elsif i.proto_ident
27
+ pb = i.to_pb_from(rb)
28
+
29
+ case i.proto_ident
30
+ when 'bool'
31
+ # ref: https://github.com/ruby-protobuf/protobuf/blob/b866fc5667226d0582f328ab24c648e578c5a380/lib/protobuf/field/bool_field.rb#L46
32
+ [pb ? 1 : 0].pack('C')
33
+ when 'double'
34
+ # ref: https://github.com/ruby-protobuf/protobuf/blob/b866fc5667226d0582f328ab24c648e578c5a380/lib/protobuf/field/double_field.rb#L16
35
+ [pb].pack('E')
36
+ when 'sint64', 'sint32'
37
+ # ref: https://github.com/ruby-protobuf/protobuf/blob/b866fc5667226d0582f328ab24c648e578c5a380/lib/protobuf/field/signed_integer_field.rb#L20-L24
38
+ if pb >= 0
39
+ varint_encode(pb << 1)
40
+ else
41
+ varint_encode(~(pb << 1))
42
+ end
43
+ when 'uint64'
44
+ # ref: https://github.com/ruby-protobuf/protobuf/blob/b866fc5667226d0582f328ab24c648e578c5a380/lib/protobuf/field/varint_field.rb#L68
45
+ varint_encode(pb)
46
+ when 'string', 'bytes'
47
+ # ref: https://github.com/ruby-protobuf/protobuf/blob/b866fc5667226d0582f328ab24c648e578c5a380/lib/protobuf/field/string_field.rb#L37-L43
48
+ v = "" + pb
49
+ if i.proto_ident == 'string' && v.encoding != ::Encoding::UTF_8
50
+ v.encode!(::Encoding::UTF_8, :invalid => :replace, :undef => :replace, :replace => "")
51
+ end
52
+ v.force_encoding(::Encoding::BINARY)
53
+ else
54
+ raise NotImplementedError, "Unsupported #{i.proto_ident} on #{__method__} for #{const.name}"
55
+ end
56
+ end
57
+
58
+ Google::Protobuf::Any.new(type_url: "/#{i.proto_full_ident}", value: value)
59
+ end
60
+
61
+ def to_rb_from(pb)
62
+ i = Protod::Interpreter.find_by_proto(pb.type_url.split('/').last)
63
+
64
+ raise NotImplementedError, "Not found the interpreter for #{pb.type_url}. You can define a interpreter using Protod::Interpreter.register_for" unless i
65
+
66
+ value = if i.pb_const
67
+ i.pb_const.decode(pb.value)
68
+ elsif i.proto_ident
69
+ case i.proto_ident
70
+ when 'bool'
71
+ pb.value.unpack('C').first == 1
72
+ when 'double'
73
+ pb.value.unpack('E').first
74
+ when 'sint64', 'sint32'
75
+ v = varint_decode(StringIO.new(pb.value))
76
+
77
+ (v & 1).zero? ? v >> 1 : ~v >> 1
78
+ when 'uint64'
79
+ varint_decode(StringIO.new(pb.value))
80
+ when 'string', 'bytes'
81
+ pb.value.dup.force_encoding(i.proto_ident == 'string' ? ::Encoding::UTF_8 : ::Encoding::BINARY)
82
+ else
83
+ raise NotImplementedError, "Unsupported #{i.proto_ident} on #{__method__} for #{const.name}"
84
+ end
85
+ end
86
+
87
+ i.to_rb_from(value)
88
+ end
89
+
90
+ private
91
+
92
+ # ref: https://github.com/ruby-protobuf/protobuf/blob/b866fc5667226d0582f328ab24c648e578c5a380/lib/protobuf/varint_pure.rb#L10-L29
93
+
94
+ def varint_encode(value)
95
+ bytes = []
96
+ until value < 128
97
+ bytes << (0x80 | (value & 0x7f))
98
+ value >>= 7
99
+ end
100
+ (bytes << value).pack('C*')
101
+ end
102
+
103
+ def varint_decode(stream)
104
+ value = index = 0
105
+ begin
106
+ byte = stream.readbyte
107
+ value |= (byte & 0x7f) << (7 * index)
108
+ index += 1
109
+ end while (byte & 0x80).nonzero?
110
+ value
111
+ end
112
+ end
113
+
114
+ Interpreter.register_for(*%w[RBS::Types::Bases::Void RBS::Types::Bases::Nil], force: false, ignore: true) do
115
+ def proto_ident
116
+ nil
117
+ end
118
+
119
+ def to_pb_from(rb)
120
+ nil
121
+ end
122
+
123
+ def to_rb_from(pb)
124
+ nil
125
+ end
126
+ end
127
+
128
+ Interpreter.register_for('TrueClass', 'FalseClass', 'RBS::Types::Bases::Bool', force: false, ignore: true) do
129
+ def proto_ident
130
+ 'bool'
131
+ end
132
+
133
+ def to_pb_from(rb)
134
+ rb ? true : false
135
+ end
136
+
137
+ def to_rb_from(pb)
138
+ pb
139
+ end
140
+ end
141
+
142
+ Interpreter.register_for('Numeric', force: false, ignore: true) do
143
+ def proto_ident
144
+ 'double'
145
+ end
146
+
147
+ def to_pb_from(rb)
148
+ rb.to_f
149
+ end
150
+
151
+ def to_rb_from(pb)
152
+ pb
153
+ end
154
+ end
155
+
156
+ Interpreter.register_for('Integer', force: false, ignore: true) do
157
+ def proto_ident
158
+ 'sint64'
159
+ end
160
+
161
+ def to_pb_from(rb)
162
+ rb.to_i
163
+ end
164
+
165
+ def to_rb_from(pb)
166
+ pb
167
+ end
168
+ end
169
+
170
+ Interpreter.register_for('Fixnum', force: false, ignore: true) do
171
+ def proto_ident
172
+ 'sint32'
173
+ end
174
+
175
+ def to_pb_from(rb)
176
+ rb.to_i
177
+ end
178
+
179
+ def to_rb_from(pb)
180
+ pb
181
+ end
182
+ end
183
+
184
+ Interpreter.register_for('String', force: false, ignore: true) do
185
+ def proto_ident
186
+ 'string'
187
+ end
188
+
189
+ def to_pb_from(rb)
190
+ rb.to_s
191
+ end
192
+
193
+ def to_rb_from(pb)
194
+ pb
195
+ end
196
+ end
197
+
198
+ Interpreter.register_for('BigDecimal', parent: google_type, path: 'google/type/decimal.proto', force: false, ignore: true) do
199
+ def proto_ident
200
+ 'Decimal'
201
+ end
202
+
203
+ def to_pb_from(rb)
204
+ Google::Type::Decimal.new(value: rb.to_s)
205
+ end
206
+
207
+ def to_rb_from(pb)
208
+ BigDecimal(pb.value)
209
+ end
210
+ end
211
+
212
+ Interpreter.register_for('Date', parent: google_type, path: 'google/type/date.proto', force: false, ignore: true) do
213
+ def proto_ident
214
+ 'Date'
215
+ end
216
+
217
+ def to_pb_from(rb)
218
+ Google::Type::Date.new(year: rb.year, month: rb.month, day: rb.day)
219
+ end
220
+
221
+ def to_rb_from(pb)
222
+ ::Date.new(pb.year, pb.month, pb.day)
223
+ end
224
+ end
225
+
226
+ Interpreter.register_for('Time', 'DateTime', 'ActiveSupport::TimeWithZone', parent: google_protobuf, path: 'google/protobuf/timestamp.proto', force: false, ignore: true) do
227
+ def proto_ident
228
+ 'Timestamp'
229
+ end
230
+
231
+ def to_pb_from(rb)
232
+ Google::Protobuf::Timestamp.new(seconds: rb.to_i, nanos: rb.nsec)
233
+ end
234
+
235
+ def to_rb_from(pb)
236
+ ::Time.zone.at(pb.seconds, pb.nanos, :nanosecond)
237
+ end
238
+ end
239
+
240
+ Interpreter.register_for('Protod::Types::Binary', force: false, ignore: true) do
241
+ def proto_ident
242
+ 'bytes'
243
+ end
244
+
245
+ def to_pb_from(rb)
246
+ rb
247
+ end
248
+
249
+ def to_rb_from(pb)
250
+ pb
251
+ end
252
+ end
253
+
254
+ Interpreter.register_for('Protod::Types::UnsignedInteger', force: false, ignore: true) do
255
+ def proto_ident
256
+ 'uint64'
257
+ end
258
+
259
+ def to_pb_from(rb)
260
+ rb.to_i
261
+ end
262
+
263
+ def to_rb_from(pb)
264
+ pb
265
+ end
266
+ end
267
+
268
+ Interpreter.register_for('Array', force: false, ignore: true) do
269
+ def proto_message
270
+ Protod::Proto::Message.new(
271
+ ident: proto_ident,
272
+ fields: [Protod::Proto::Field.build_from('RBS::Types::Bases::Any', ident: 'values', optional: true, repeated: true)]
273
+ )
274
+ end
275
+
276
+ def to_pb_from(rb)
277
+ i = Protod::Interpreter.find_by('RBS::Types::Bases::Any')
278
+
279
+ pb_const.new(values: rb.map { i.to_pb_from(_1) })
280
+ end
281
+
282
+ def to_rb_from(pb)
283
+ i = Protod::Interpreter.find_by('RBS::Types::Bases::Any')
284
+
285
+ pb.values.map { i.to_rb_from(_1) }
286
+ end
287
+ end
288
+
289
+ Interpreter.register_for('Hash', parent: google_protobuf, path: 'google/protobuf/struct.proto', force: false, ignore: true) do
290
+ def proto_ident
291
+ 'Struct'
292
+ end
293
+
294
+ def to_pb_from(rb)
295
+ Google::Protobuf::Struct.from_hash(rb)
296
+ end
297
+
298
+ def to_rb_from(pb)
299
+ pb.to_h
300
+ end
301
+ end
302
+
303
+ Interpreter.register_for(*['Data', 'Struct'].select(&:safe_constantize), force: false, ignore: true) do
304
+ def proto_message
305
+ Protod::Proto::Message.new(
306
+ ident: proto_ident,
307
+ fields: const.memebers.map do |name|
308
+ Protod::Proto::Field.build_from('RBS::Types::Bases::Any', ident: name, optional: true)
309
+ end
310
+ )
311
+ end
312
+
313
+ def to_pb_from(rb)
314
+ attributes = const.members.map { [_1, rb.public_send(_1)] }.to_h
315
+
316
+ pb_const.new(**attributes)
317
+ end
318
+
319
+ def to_rb_from(pb)
320
+ attributes = const.members.map { [_1, pb.public_send(_1)] }.to_h
321
+
322
+ const.new(**attributes)
323
+ end
324
+ end
325
+ end
326
+ end
327
+ end
328
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Protod
4
+ class Interpreter
5
+ class Rpc
6
+ def self.setup!
7
+ Interpreter.register_for('Protod::Rpc::Request::Base', force: false, ignore: true) do
8
+ def proto_ident
9
+ "Request"
10
+ end
11
+
12
+ def proto_message
13
+ m = Protod.rbs_method_type_for(const.ruby_ident)
14
+
15
+ fields = [
16
+ if const.ruby_ident.singleton
17
+ nil
18
+ else
19
+ Protod::Proto::Field.build_from(const.ruby_ident.const_name, ident: const.ruby_ident.const_name)
20
+ end,
21
+ *m.type.required_positionals.map do |arg|
22
+ raise ArgumentError, "Unsupported non-named argument : #{const.ruby_ident}" unless arg.name
23
+
24
+ Protod::Proto::Field.build_from_rbs(arg.type, on: const.ruby_ident, ident: arg.name)
25
+ end,
26
+ *m.type.optional_positionals.map do |arg|
27
+ raise ArgumentError, "Unsupported non-named argument : #{const.ruby_ident}" unless arg.name
28
+
29
+ Protod::Proto::Field.build_from_rbs(arg.type, on: const.ruby_ident, ident: arg.name, required: false, optional: true)
30
+ end,
31
+ m.type.rest_positionals&.then do |arg|
32
+ raise ArgumentError, "Unsupported non-named argument : #{const.ruby_ident}" unless arg.name
33
+
34
+ Protod::Proto::Field.build_from_rbs(arg.type, on: const.ruby_ident, ident: arg.name, as_rest: true, required: false, repeated: true)
35
+ end,
36
+ *m.type.required_keywords.map do |name, arg|
37
+ Protod::Proto::Field.build_from_rbs(arg.type, on: const.ruby_ident, ident: name, as_keyword: true)
38
+ end,
39
+ *m.type.optional_keywords.map do |name, arg|
40
+ Protod::Proto::Field.build_from_rbs(arg.type, on: const.ruby_ident, ident: name, as_keyword: true, required: false, optional: true)
41
+ end,
42
+ m.type.rest_keywords&.then do |arg|
43
+ # Not supporting for now because it's complicated and I can't understand the specification of ruby and rbs about this completely
44
+ raise ArgumentError, "Unsupported rest keywords argument : #{const.ruby_ident}"
45
+
46
+ raise ArgumentError, "Unsupported non-named argument : #{const.ruby_ident}" unless arg.name
47
+
48
+ Protod::Proto::Field.build_from_rbs(arg.type, on: const.ruby_ident, ident: arg.name, as_keyword: true, as_rest: true, required: false, optional: true)
49
+ end
50
+ ].compact
51
+
52
+ Protod::Proto::Message.new(ident: proto_ident, fields: fields)
53
+ end
54
+
55
+ def to_rb_from(pb)
56
+ args = []
57
+ kwargs = {}
58
+ receiver_id = nil
59
+
60
+ proto_message.fields.each.with_index do |f, i|
61
+ next if Protod::Proto.omits_field?(pb, f.ident) && f.required.!
62
+
63
+ f_pb = pb.public_send(f.ident)
64
+
65
+ receiver_id = f_pb.protod__object_id if i == 0 && f_pb.respond_to?(:protod__object_id)
66
+
67
+ arg = if f.repeated
68
+ f_pb&.map { f.interpreter.to_rb_from(_1) }
69
+ else
70
+ f.interpreter.to_rb_from(f_pb)
71
+ end
72
+
73
+ if f.as_keyword
74
+ kwargs[f.ident.to_sym] = arg
75
+ elsif f.as_rest
76
+ args = [*args, *arg]
77
+ else
78
+ args.push(arg)
79
+ end
80
+ end
81
+
82
+ receiver = if const.ruby_ident.singleton
83
+ const.ruby_ident.const_name.constantize
84
+ else
85
+ args.shift
86
+ end
87
+
88
+ Protod::Rpc::Handler::RequestPacket.new(receiver_id: receiver_id, receiver: receiver, args: args, kwargs: kwargs)
89
+ end
90
+ end
91
+
92
+ Interpreter.register_for('Protod::Rpc::Response::Base', force: false, ignore: true) do
93
+ def proto_ident
94
+ "Response"
95
+ end
96
+
97
+ def proto_message
98
+ m = Protod.rbs_method_type_for(const.ruby_ident)
99
+
100
+ fields = if m.type.return_type
101
+ [Protod::Proto::Field.build_from_rbs(m.type.return_type, on: const.ruby_ident, ident: 'value')]
102
+ else
103
+ []
104
+ end
105
+
106
+ Protod::Proto::Message.new(ident: proto_ident, fields: fields)
107
+ end
108
+
109
+ def to_pb_from(rb)
110
+ return unless rb
111
+
112
+ f = proto_message.find('value', by: :ident, as: 'Protod::Proto::Field')
113
+
114
+ return unless f
115
+
116
+ pb_maker = ->(v) do
117
+ f.interpreter.to_pb_from(v).tap do
118
+ _1.protod__object_id = v.object_id.to_s if _1.respond_to?(:protod__object_id)
119
+ end
120
+ end
121
+
122
+ pb = f.repeated ? rb.map { pb_maker.call(_1) } : pb_maker.call(rb)
123
+
124
+ pb_const.new(value: pb)
125
+ end
126
+ end
127
+
128
+ Interpreter.register_for('Protod::Rpc::Request::Receiver::Base', force: false, ignore: true) do
129
+ def proto_ident
130
+ "Request"
131
+ end
132
+
133
+ def proto_message
134
+ f = Protod::Proto::Oneof.new(ident: Protod::Rpc::Request::Receiver::ONEOF_NAME)
135
+
136
+ [
137
+ *proto_fields,
138
+ *const.ruby_ident.const_name.constantize.ancestors.drop(1).filter_map do |c|
139
+ r = Protod::Rpc::Request.find_by(c.name) if c.name
140
+ Interpreter.find_by(r) if r
141
+ end.flat_map(&:proto_fields)
142
+ ].each { f.find_or_push(_1, by: :ident, into: :fields) }
143
+
144
+ Protod::Proto::Message.new(ident: proto_ident, fields: [f])
145
+ end
146
+
147
+ def to_rb_from(pb)
148
+ procedure = pb.public_send(Protod::Rpc::Request::Receiver::ONEOF_NAME)
149
+
150
+ raise Protod::Rpc::Handler::InvalidArgument, "Not set procedure" unless procedure
151
+
152
+ f = proto_message
153
+ .find(Protod::Rpc::Request::Receiver::ONEOF_NAME, by: :ident, as: 'Protod::Proto::Oneof')
154
+ .find(procedure, by: :ident, as: 'Protod::Proto::Field')
155
+
156
+ raise Protod::Rpc::Handler::InvalidArgument, "Not found acceptable procedure : #{procedure}" unless f
157
+
158
+ f.interpreter.to_rb_from(pb.public_send(procedure)).tap { _1.procedure = procedure }
159
+ end
160
+
161
+ def proto_fields
162
+ const.procedures.map do |ruby_ident|
163
+ Protod::Proto::Field.build_from(Protod::Rpc::Request.find_by(ruby_ident), ident: ruby_ident.method_name)
164
+ end
165
+ end
166
+ end
167
+
168
+ Interpreter.register_for('Protod::Rpc::Response::Receiver::Base', force: false, ignore: true) do
169
+ def proto_ident
170
+ "Response"
171
+ end
172
+
173
+ def proto_message
174
+ f = Protod::Proto::Oneof.new(ident: Protod::Rpc::Response::Receiver::ONEOF_NAME)
175
+
176
+ [
177
+ *proto_fields,
178
+ *const.ruby_ident.const_name.constantize.ancestors.drop(1).filter_map do |c|
179
+ r = Protod::Rpc::Response.find_by(c.name) if c.name
180
+ Interpreter.find_by(r) if r
181
+ end.flat_map(&:proto_fields)
182
+ ].each { f.find_or_push(_1, by: :ident, into: :fields) }
183
+
184
+ Protod::Proto::Message.new(ident: proto_ident, fields: [f])
185
+ end
186
+
187
+ def to_pb_from(packet)
188
+ f = proto_message
189
+ .find(Protod::Rpc::Response::Receiver::ONEOF_NAME, by: :ident, as: 'Protod::Proto::Oneof')
190
+ .find(packet.procedure, by: :ident, as: 'Protod::Proto::Field')
191
+
192
+ pb = f.interpreter.to_pb_from(packet.object)
193
+
194
+ pb_const.new(packet.procedure.to_sym => pb)
195
+ end
196
+
197
+ def proto_fields
198
+ const.procedures.map do |ruby_ident|
199
+ Protod::Proto::Field.build_from(Protod::Rpc::Response.find_by(ruby_ident), ident: ruby_ident.method_name)
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Protod
4
+ class Interpreter
5
+ class << self
6
+ def register_for(*const_names, with: nil, parent: nil, path: nil, force: true, ignore: false, &body)
7
+ base = if with
8
+ find_by(with)&.class or raise ArgumentError, "Not found the interpreter for #{with}"
9
+ else
10
+ Base
11
+ end
12
+
13
+ Class.new(base, &body).tap do |interpreter_const|
14
+ const_names.each do
15
+ c = Protod::RubyIdent.absolute_of(_1).safe_constantize
16
+
17
+ raise ArgumentError, "Not found to constantize for #{_1}" unless c
18
+
19
+ next if map.key?(c) && ignore
20
+
21
+ raise ArgumentError, "Interpreter already regsitered for #{c.name}" if map.key?(c) && !force
22
+
23
+ map[c] = { const: interpreter_const, parent: parent, path: path }
24
+ end
25
+ end
26
+ end
27
+
28
+ def find_by(const_or_name, with_register_from_ancestor: false)
29
+ const_name = const_or_name.is_a?(::String) ? Protod::RubyIdent.absolute_of(const_or_name) : nil
30
+ const = const_or_name.is_a?(::String) ? const_or_name.safe_constantize : const_or_name
31
+ entryables = const.respond_to?(:ancestors) ? const.ancestors : [const]
32
+ entry_const = entryables.compact.find { map.key?(_1) }
33
+ entry = entry_const && map.fetch(entry_const)
34
+
35
+ return unless entry
36
+
37
+ unless entry.is_a?(Base)
38
+ entry = map[entry_const] = entry.fetch(:const).new(entry_const, **entry.except(:const)).tap do
39
+ _1.extend SkipNilAbility
40
+ _1.extend ProtoMessageCacheable
41
+ end
42
+ end
43
+
44
+ return entry unless with_register_from_ancestor
45
+
46
+ unless map[const].is_a?(Base)
47
+ map[const] = entry.class.new(const, parent: entry.parent, path: entry.path).tap do
48
+ _1.extend SkipNilAbility
49
+ _1.extend ProtoMessageCacheable
50
+ end
51
+ end
52
+
53
+ map.fetch(const)
54
+ end
55
+
56
+ def find_by_proto(full_ident)
57
+ raise NotImplementedError, "You need to call Protod::Interpreter.setup_reverse_lookup! before" unless @reverse_map
58
+
59
+ @reverse_map[full_ident]
60
+ end
61
+
62
+ def keys
63
+ map.keys
64
+ end
65
+
66
+ def reverse_keys
67
+ @reverse_map&.keys
68
+ end
69
+
70
+ def clear!
71
+ @map = nil
72
+ @reverse_map = nil
73
+ end
74
+
75
+ def setup_reverse_lookup!
76
+ @reverse_map = keys.map { find_by(_1) }.uniq.map { [_1.proto_full_ident, _1] }.to_h
77
+ end
78
+
79
+ private
80
+
81
+ def map
82
+ @map ||= {}
83
+ end
84
+ end
85
+
86
+ module SkipNilAbility
87
+ def to_pb_from(rb)
88
+ return if rb.nil?
89
+ super
90
+ end
91
+
92
+ def to_rb_from(pb)
93
+ return if pb.nil?
94
+ super
95
+ end
96
+ end
97
+
98
+ module ProtoMessageCacheable
99
+ def proto_message
100
+ if @_proto_message.nil?
101
+ @_proto_message = super&.tap do |m|
102
+ next if const.ancestors.any? { _1.in?([Protod::Rpc::Request::Base, Protod::Rpc::Response::Base]) }
103
+
104
+ m.fields.unshift(Protod::Proto::Field.build_from('::String', ident: 'protod__object_id', optional: true))
105
+ end
106
+
107
+ @_proto_message = false unless @_proto_message
108
+ end
109
+
110
+ @_proto_message ? @_proto_message : nil
111
+ end
112
+ end
113
+
114
+ class Base
115
+ attr_reader :const, :parent, :path
116
+
117
+ def initialize(const, parent: nil, path: nil)
118
+ @const = const
119
+ @parent = parent
120
+ @path = path
121
+ end
122
+
123
+ def ==(other)
124
+ const == other.const && parent == other.parent && path == other.path
125
+ end
126
+
127
+ def bindable?
128
+ return false unless proto_message
129
+ return false if bound?
130
+ true
131
+ end
132
+
133
+ def bound?
134
+ return false unless proto_message
135
+
136
+ parent.nil?.!
137
+ end
138
+
139
+ def set_parent(v)
140
+ raise ArgumentError, "Can't set parent #{v.full_ident} for #{proto_ident} as #{const.name} : already set #{parent.full_ident}" unless parent.nil?
141
+ @parent = v
142
+ end
143
+
144
+ def package
145
+ return parent if parent.is_a?(Protod::Proto::Package)
146
+
147
+ parent&.ancestor_as(Protod::Proto::Package)
148
+ end
149
+
150
+ def proto_path
151
+ @path || package&.proto_path
152
+ end
153
+
154
+ def proto_full_ident
155
+ [parent&.full_ident, proto_ident].compact.join('.').presence
156
+ end
157
+
158
+ def proto_ident
159
+ Protod::Proto::Ident.build_from(const.name)
160
+ end
161
+
162
+ def proto_message
163
+ nil
164
+ end
165
+
166
+ def pb_const
167
+ proto_message&.pb_const ||
168
+ Google::Protobuf::DescriptorPool.generated_pool.lookup(proto_full_ident)&.msgclass
169
+ end
170
+
171
+ def to_pb_from(rb)
172
+ raise NotImplementedError, "You need to implement #{__method__} for #{const.name}"
173
+ end
174
+
175
+ def to_rb_from(pb)
176
+ raise NotImplementedError, "You need to implement #{__method__} for #{const.name}"
177
+ end
178
+ end
179
+ end
180
+ end