protod 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.bundle/config +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +585 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +165 -0
- data/LICENSE.txt +21 -0
- data/README.md +37 -0
- data/Rakefile +12 -0
- data/lib/generators/protod/gruf_generator.rb +43 -0
- data/lib/generators/protod/task_generator.rb +23 -0
- data/lib/protod/configuration.rb +78 -0
- data/lib/protod/interpreter/active_record.rb +161 -0
- data/lib/protod/interpreter/builtin.rb +328 -0
- data/lib/protod/interpreter/rpc.rb +206 -0
- data/lib/protod/interpreter.rb +180 -0
- data/lib/protod/proto/builder.rb +70 -0
- data/lib/protod/proto/features.rb +32 -0
- data/lib/protod/proto/field.rb +88 -0
- data/lib/protod/proto/message.rb +95 -0
- data/lib/protod/proto/oneof.rb +46 -0
- data/lib/protod/proto/package.rb +120 -0
- data/lib/protod/proto/part.rb +191 -0
- data/lib/protod/proto/procedure.rb +42 -0
- data/lib/protod/proto/service.rb +42 -0
- data/lib/protod/protocol_buffers.rb +46 -0
- data/lib/protod/rake_task.rb +91 -0
- data/lib/protod/rpc/handler.rb +114 -0
- data/lib/protod/rpc/request.rb +68 -0
- data/lib/protod/rpc/response.rb +68 -0
- data/lib/protod/ruby_ident.rb +49 -0
- data/lib/protod/version.rb +5 -0
- data/lib/protod.rb +103 -0
- data/sig/protod.rbs +4 -0
- metadata +136 -0
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Protod
|
4
|
+
module Proto
|
5
|
+
class Builder
|
6
|
+
def initialize(root_package)
|
7
|
+
@root_package = root_package
|
8
|
+
@receivers = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def push_receiver(ruby_ident)
|
12
|
+
@receivers[ruby_ident.to_s] = true
|
13
|
+
end
|
14
|
+
|
15
|
+
def receiver_pushed?(ruby_ident)
|
16
|
+
@receivers.key?(ruby_ident.to_s)
|
17
|
+
end
|
18
|
+
|
19
|
+
def build
|
20
|
+
return if @root_package.built?
|
21
|
+
|
22
|
+
handler = Protod::Rpc::Handler.build_in(@root_package)
|
23
|
+
|
24
|
+
@receivers.keys.each do |receiver|
|
25
|
+
package = Protod.find_or_register_package("#{@root_package.full_ident}.#{receiver.underscore.gsub('/', '.')}")
|
26
|
+
|
27
|
+
request_field = Protod::Proto::Field.build_from(Protod::Rpc::Request.find_by(receiver), ident: receiver)
|
28
|
+
response_field = Protod::Proto::Field.build_from(Protod::Rpc::Response.find_by(receiver), ident: receiver)
|
29
|
+
|
30
|
+
handler.register_receiver(request_field, response_field)
|
31
|
+
|
32
|
+
request_message = package.bind(request_field.interpreter)
|
33
|
+
response_message = package.bind(response_field.interpreter)
|
34
|
+
|
35
|
+
bindable_interpreters_under(request_message, response_message).each do |i|
|
36
|
+
root_message_name = i.const.ruby_ident.singleton ? 'Singleton' : 'Instance'
|
37
|
+
|
38
|
+
package
|
39
|
+
.find_or_push(root_message_name, by: :ident, into: :messages)
|
40
|
+
.find_or_push(i.const.ruby_ident.method_name, by: :ident, into: :messages)
|
41
|
+
.bind(i)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
models_package = Protod.find_or_register_package("#{@root_package.full_ident}.models")
|
46
|
+
|
47
|
+
while (interpreters = bindable_interpreters_under(*@root_package.all_packages)).present?
|
48
|
+
interpreters.each { models_package.bind(_1) }
|
49
|
+
end
|
50
|
+
|
51
|
+
# For supporting an Array/Hash instance at the fields whiches type is google.protobuf.Any,
|
52
|
+
# make the proto definition for Array emerge even if it's not requried.
|
53
|
+
any_interpreter = Protod::Interpreter.find_by('RBS::Types::Bases::Any')
|
54
|
+
if @root_package.all_packages.flat_map(&:collect_fields).any? { _1.interpreter == any_interpreter }
|
55
|
+
i = Protod::Interpreter.find_by('Array')
|
56
|
+
|
57
|
+
models_package.bind(i) if i.bindable?
|
58
|
+
|
59
|
+
models_package.imports.push(Protod::Interpreter.find_by('Hash').proto_path)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def bindable_interpreters_under(*parts)
|
66
|
+
parts.flat_map(&:collect_fields).filter_map(&:interpreter).uniq.select(&:bindable?)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Protod
|
4
|
+
module Proto
|
5
|
+
class << self
|
6
|
+
# https://github.com/protocolbuffers/protobuf/blob/v3.12.0/docs/field_presence.md
|
7
|
+
def omits_field?(pb, name)
|
8
|
+
return false unless pb.respond_to?("has_#{name}?")
|
9
|
+
return false if pb.public_send("has_#{name}?")
|
10
|
+
true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Ident < ::String
|
15
|
+
class << self
|
16
|
+
def build_from(const_name)
|
17
|
+
return if const_name.blank?
|
18
|
+
|
19
|
+
new(const_name)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :const_name
|
24
|
+
|
25
|
+
def initialize(const_name)
|
26
|
+
@const_name = Protod::RubyIdent.absolute_of(const_name)
|
27
|
+
|
28
|
+
super(const_name.gsub('::', '__').delete_prefix('__'))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Protod
|
4
|
+
module Proto
|
5
|
+
class Field < Part
|
6
|
+
attribute :interpreter
|
7
|
+
attribute :as_keyword, :boolean, default: false
|
8
|
+
attribute :as_rest, :boolean, default: false
|
9
|
+
attribute :required, :boolean, default: true # whether to be not able to omit in Ruby
|
10
|
+
attribute :optional, :boolean, default: false # whether to be optional in proto
|
11
|
+
attribute :repeated, :boolean, default: false
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def build_from(const_or_name, **attributes)
|
15
|
+
i = Protod::Interpreter.find_by(const_or_name, with_register_from_ancestor: true)
|
16
|
+
|
17
|
+
raise NotImplementedError, "Not found the interpreter for #{const_or_name}. You can define a interpreter using Protod::Interpreter.register_for" unless i
|
18
|
+
|
19
|
+
new(interpreter: i, **attributes)
|
20
|
+
end
|
21
|
+
|
22
|
+
def build_from_rbs(type, on:, **attributes)
|
23
|
+
case type
|
24
|
+
when RBS::Types::Optional
|
25
|
+
build_from_rbs(type.type, on: on, **attributes.merge(optional: attributes[:repeated] ? false : true))
|
26
|
+
when RBS::Types::Union
|
27
|
+
real_type = type.types.find { _1.name.kind == :class && Protod.rbs_environment.class_decls.key?(_1.name) }
|
28
|
+
|
29
|
+
raise ArgumentError, "Not found declared class in union type on #{on}" unless real_type
|
30
|
+
|
31
|
+
build_from_rbs(real_type, on: on, **attributes)
|
32
|
+
when RBS::Types::Alias
|
33
|
+
alias_decl = Protod.rbs_environment.type_alias_decls[type.name]
|
34
|
+
|
35
|
+
raise ArgumentError, "Not found alias declaration of #{type.name.name} on #{on}" unless alias_decl
|
36
|
+
|
37
|
+
build_from_rbs(alias_decl.decl.type, on: on, **attributes)
|
38
|
+
when RBS::Types::ClassInstance
|
39
|
+
case
|
40
|
+
when should_repeated_with(type.name.to_s.safe_constantize)
|
41
|
+
build_from_rbs(type.args.first, on: on, **attributes.merge(optional: false, repeated: true))
|
42
|
+
when type.args.size > 0
|
43
|
+
raise NotImplementedError, "Unsupported rbs type : Record or Tuple on #{on}"
|
44
|
+
else
|
45
|
+
build_from(type.name.to_s, **attributes)
|
46
|
+
end
|
47
|
+
when RBS::Types::Bases::Base
|
48
|
+
build_from(type.class.name, **attributes)
|
49
|
+
else
|
50
|
+
binding.pry
|
51
|
+
raise NotImplementedError, "Unsupported rbs type : #{type.class.name} on #{on}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def should_repeated_with(const)
|
56
|
+
const&.ancestors&.include?(::Array)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def void?
|
61
|
+
interpreter ? interpreter.proto_ident.blank? : false
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_proto
|
65
|
+
raise ArgumentError, "Not set interpreter" unless interpreter
|
66
|
+
|
67
|
+
type_part = if interpreter.package && interpreter.package == ancestor_as(Protod::Proto::Package)
|
68
|
+
interpreter.proto_full_ident.delete_prefix("#{interpreter.package.full_ident}.")
|
69
|
+
else
|
70
|
+
interpreter.proto_full_ident
|
71
|
+
end
|
72
|
+
|
73
|
+
[
|
74
|
+
format_proto(''),
|
75
|
+
[
|
76
|
+
# optional ? 'optional' : nil,
|
77
|
+
repeated ? 'repeated' : nil,
|
78
|
+
type_part,
|
79
|
+
ident,
|
80
|
+
'=',
|
81
|
+
index
|
82
|
+
].compact.join(' '),
|
83
|
+
';'
|
84
|
+
].join
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Protod
|
4
|
+
module Proto
|
5
|
+
class Message < Part
|
6
|
+
include Findable
|
7
|
+
include FieldCollectable
|
8
|
+
include FieldNumeringable
|
9
|
+
include InterpreterBindable
|
10
|
+
|
11
|
+
attribute :messages, default: -> { [] }
|
12
|
+
attribute :fields, default: -> { [] }
|
13
|
+
|
14
|
+
findable_callback_for(:message, key: [:ident, :ruby_ident]) do |key, value|
|
15
|
+
@message_map ? @message_map.fetch(key)[value] : messages.find { _1.public_send(key) == value }
|
16
|
+
end
|
17
|
+
|
18
|
+
findable_callback_for(:field, :oneof, key: :ident) do |key, value|
|
19
|
+
@field_map ? @field_map.fetch(key)[value] : fields.find { _1.public_send(key) == value }
|
20
|
+
end
|
21
|
+
|
22
|
+
def has?(part, in_the:)
|
23
|
+
case in_the
|
24
|
+
when :fields
|
25
|
+
idents = fields.flat_map do |f|
|
26
|
+
case f
|
27
|
+
when Protod::Proto::Field
|
28
|
+
[f.ident]
|
29
|
+
when Protod::Proto::Oneof
|
30
|
+
[f.ident, *f.fields.map(&:ident)]
|
31
|
+
else
|
32
|
+
raise ArgumentError, "Unacceptable field : #{f.ident} of #{f.class.name}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
part.ident.in?(idents)
|
37
|
+
else
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def ruby_ident
|
43
|
+
ident.const_name
|
44
|
+
end
|
45
|
+
|
46
|
+
def full_ident
|
47
|
+
[parent&.full_ident, ident].compact.join('.').presence
|
48
|
+
end
|
49
|
+
|
50
|
+
def pb_const
|
51
|
+
raise NotImplementedError, "Can't call pb_const for #{ident} : not set parent yet" unless parent
|
52
|
+
|
53
|
+
Google::Protobuf::DescriptorPool.generated_pool.lookup(full_ident).msgclass
|
54
|
+
end
|
55
|
+
|
56
|
+
def freeze
|
57
|
+
messages.each { _1.depth = depth + 1 }
|
58
|
+
fields.each { _1.depth = depth + 1 }
|
59
|
+
|
60
|
+
messages.each.with_index(1) { |m, i| m.index = i }
|
61
|
+
numbering_fields_with(1)
|
62
|
+
|
63
|
+
@message_map = self.class.findable_keys_for(:message).index_with { |k| messages.index_by(&k.to_sym) }
|
64
|
+
@field_map = self.class.findable_keys_for(:field).index_with { |k| fields.index_by(&k.to_sym) }
|
65
|
+
|
66
|
+
super
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_proto
|
70
|
+
message_part = messages.map { _1.to_proto }.join("\n\n").presence
|
71
|
+
|
72
|
+
field_part = fields.filter_map do |f|
|
73
|
+
case f
|
74
|
+
when Protod::Proto::Field
|
75
|
+
next if f.void?
|
76
|
+
|
77
|
+
f.to_proto
|
78
|
+
when Protod::Proto::Oneof
|
79
|
+
f.to_proto
|
80
|
+
else
|
81
|
+
raise NotImplementedError, "Sorry, this is bug forgetting to implement for #{f.class.name}"
|
82
|
+
end
|
83
|
+
end.join("\n").presence
|
84
|
+
|
85
|
+
body = [message_part, field_part].compact.join("\n\n").presence
|
86
|
+
body = "\n#{body}\n" if body
|
87
|
+
|
88
|
+
[
|
89
|
+
format_proto("message %s {%s", ident, body),
|
90
|
+
format_proto("}")
|
91
|
+
].join
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Protod
|
4
|
+
module Proto
|
5
|
+
class Oneof < Part
|
6
|
+
include Findable
|
7
|
+
include FieldNumeringable
|
8
|
+
|
9
|
+
attribute :fields, default: -> { [] }
|
10
|
+
|
11
|
+
findable_callback_for(:field, :oneof, key: :ident) do |key, value|
|
12
|
+
@field_map ? @field_map.fetch(key)[value] : fields.find { _1.public_send(key) == value }
|
13
|
+
end
|
14
|
+
|
15
|
+
def freeze
|
16
|
+
fields.each { _1.depth = depth + 1 }
|
17
|
+
|
18
|
+
@field_map = self.class.findable_keys_for(:field).index_with { |k| fields.index_by(&k.to_sym) }
|
19
|
+
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_proto
|
24
|
+
field_part = fields.filter_map do |f|
|
25
|
+
case f
|
26
|
+
when Protod::Proto::Field
|
27
|
+
next if f.void?
|
28
|
+
|
29
|
+
f.to_proto
|
30
|
+
when Protod::Proto::Oneof
|
31
|
+
f.to_proto
|
32
|
+
else
|
33
|
+
raise NotImplementedError, "Sorry, this is bug forgetting to implement for #{f.class.name}"
|
34
|
+
end
|
35
|
+
end.join("\n").presence
|
36
|
+
|
37
|
+
field_part = "\n#{field_part}\n" if field_part
|
38
|
+
|
39
|
+
[
|
40
|
+
format_proto("oneof %s {%s", ident, field_part),
|
41
|
+
format_proto("}")
|
42
|
+
].join
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Protod
|
4
|
+
module Proto
|
5
|
+
class Package < Part
|
6
|
+
include Findable
|
7
|
+
include FieldCollectable
|
8
|
+
include InterpreterBindable
|
9
|
+
|
10
|
+
attribute :url
|
11
|
+
attribute :branch
|
12
|
+
attribute :for_ruby, :string
|
13
|
+
attribute :for_java, :string
|
14
|
+
attribute :packages, default: -> { [] }
|
15
|
+
attribute :services, default: -> { [] }
|
16
|
+
attribute :messages, default: -> { [] }
|
17
|
+
attribute :imports, default: -> { [] }
|
18
|
+
|
19
|
+
findable_callback_for(:package, key: :full_ident) do |key, value|
|
20
|
+
@package_map ? @package_map.fetch(key)[value] : all_packages.find { _1.public_send(key) == value }
|
21
|
+
end
|
22
|
+
|
23
|
+
findable_callback_for(:service, key: [:ident, :ruby_ident]) do |key, value|
|
24
|
+
@service_map ? @service_map.fetch(key)[value] : services.find { _1.public_send(key) == value }
|
25
|
+
end
|
26
|
+
|
27
|
+
findable_callback_for(:message, key: [:ident, :ruby_ident]) do |key, value|
|
28
|
+
@message_map ? @message_map.fetch(key)[value] : messages.find { _1.public_send(key) == value }
|
29
|
+
end
|
30
|
+
|
31
|
+
class << self
|
32
|
+
def clear!
|
33
|
+
@packages = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def roots
|
37
|
+
@packages ||= []
|
38
|
+
end
|
39
|
+
|
40
|
+
def find_or_register_package(full_ident, **attributes)
|
41
|
+
full_ident.split('.').inject(nil) do |parent, ident|
|
42
|
+
current_packages = parent ? parent.packages : roots
|
43
|
+
|
44
|
+
current_packages.find { _1.ident == ident } || new.tap do
|
45
|
+
_1.assign_attributes(
|
46
|
+
parent: parent,
|
47
|
+
ident: ident,
|
48
|
+
for_ruby: parent&.for_ruby && "#{parent.for_ruby}::#{ident.camelize}",
|
49
|
+
for_java: parent&.for_java && "#{parent.for_java}.#{ident}"
|
50
|
+
)
|
51
|
+
|
52
|
+
current_packages.push(_1)
|
53
|
+
end
|
54
|
+
end.tap do
|
55
|
+
_1.assign_attributes(**attributes.compact) if attributes.compact.present?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def proto_path
|
61
|
+
full_ident.gsub('.', '/').then { "#{_1}.proto" }
|
62
|
+
end
|
63
|
+
|
64
|
+
def full_ident
|
65
|
+
[parent&.full_ident, ident].compact.join('.').presence if ident
|
66
|
+
end
|
67
|
+
|
68
|
+
def pb_const
|
69
|
+
for_ruby&.constantize || full_ident.split('.').map(&:camelize).join('::').constantize
|
70
|
+
end
|
71
|
+
|
72
|
+
def all_packages
|
73
|
+
packages.flat_map(&:all_packages).tap { _1.unshift(self) }
|
74
|
+
end
|
75
|
+
|
76
|
+
def empty?
|
77
|
+
services.empty? && messages.empty?
|
78
|
+
end
|
79
|
+
|
80
|
+
def external?
|
81
|
+
url.present?
|
82
|
+
end
|
83
|
+
|
84
|
+
def freeze
|
85
|
+
services.each.with_index(1) { |s, i| s.index = i }
|
86
|
+
messages.each.with_index(1) { |m, i| m.index = i }
|
87
|
+
|
88
|
+
@package_map = self.class.findable_keys_for(:package).index_with { |k| all_packages.index_by(&k.to_sym) }
|
89
|
+
@service_map = self.class.findable_keys_for(:service).index_with { |k| services.index_by(&k.to_sym) }
|
90
|
+
@message_map = self.class.findable_keys_for(:message).index_with { |k| messages.index_by(&k.to_sym) }
|
91
|
+
|
92
|
+
super
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_proto
|
96
|
+
syntax_part = format_proto("syntax = \"proto3\";")
|
97
|
+
|
98
|
+
package_part = format_proto("package %s;", full_ident)
|
99
|
+
|
100
|
+
option_part = [
|
101
|
+
for_ruby ? format_proto("option ruby_package = \"%s\";", for_ruby) : nil,
|
102
|
+
for_java ? format_proto("option java_package = \"%s\";", for_java) : nil,
|
103
|
+
].compact.join("\n").presence
|
104
|
+
|
105
|
+
import_part = [
|
106
|
+
*collect_fields.filter_map(&:interpreter).uniq.filter_map(&:proto_path).uniq.reject { _1 == proto_path },
|
107
|
+
*imports
|
108
|
+
].uniq.sort.map { format_proto('import "%s";', _1) }.join("\n").presence
|
109
|
+
|
110
|
+
message_part = messages.map { _1.to_proto }.join("\n\n").presence
|
111
|
+
service_part = services.map { _1.to_proto }.join("\n\n").presence
|
112
|
+
|
113
|
+
[
|
114
|
+
[syntax_part, package_part, option_part, import_part, message_part, service_part].compact.join("\n\n"),
|
115
|
+
"\n"
|
116
|
+
].join
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Protod
|
4
|
+
module Proto
|
5
|
+
class Part
|
6
|
+
include ActiveModel::Model
|
7
|
+
include ActiveModel::Attributes
|
8
|
+
|
9
|
+
attribute :parent
|
10
|
+
attribute :comment, :string
|
11
|
+
attribute :ident
|
12
|
+
attribute :depth, :integer, default: 0
|
13
|
+
attribute :index, :integer, default: 1
|
14
|
+
|
15
|
+
def ident=(value)
|
16
|
+
super(Protod::Proto::Ident.build_from(value.to_s))
|
17
|
+
|
18
|
+
raise ArgumentError, "Invalid grpc ident : #{value}. see https://protobuf.dev/reference/protobuf/proto3-spec/#identifiers" unless ident&.match(/\A[a-zA-Z][a-zA-Z0-9_]*\z/)
|
19
|
+
end
|
20
|
+
|
21
|
+
def root
|
22
|
+
parent ? parent.root : self
|
23
|
+
end
|
24
|
+
|
25
|
+
def ancestor_as(part_const)
|
26
|
+
parent.is_a?(part_const) ? parent : parent&.ancestor_as(part_const)
|
27
|
+
end
|
28
|
+
|
29
|
+
def push(part, into:, ignore: false)
|
30
|
+
already_pushed = has?(part, in_the: into)
|
31
|
+
|
32
|
+
raise ArgumentError, "Can't push already present #{part.ident} in #{ident}" if already_pushed && ignore.!
|
33
|
+
raise ArgumentError, "Can't push already bound to #{part.parent.ident} in #{ident}" if part.parent
|
34
|
+
|
35
|
+
part.assign_attributes(parent: self)
|
36
|
+
|
37
|
+
public_send(into).push(part) unless already_pushed
|
38
|
+
|
39
|
+
part
|
40
|
+
end
|
41
|
+
|
42
|
+
def has?(part, in_the:)
|
43
|
+
public_send(in_the).any? { _1.ident == part.ident }
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_proto
|
47
|
+
raise NotImplementedError, "Not defined #{self.class.name}##{__method__}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def freeze
|
51
|
+
super.tap do
|
52
|
+
(attributes.keys - %w[parent]).each do |attribute_name|
|
53
|
+
value = attributes.fetch(attribute_name)
|
54
|
+
|
55
|
+
value.freeze
|
56
|
+
|
57
|
+
value.each(&:freeze) if value.is_a?(::Array)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
alias_method :built?, :frozen?
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def format_proto(fmt, *args)
|
67
|
+
format("%s#{fmt}", ' ' * depth, *args)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
module Findable
|
72
|
+
extend ActiveSupport::Concern
|
73
|
+
|
74
|
+
class_methods do
|
75
|
+
def findable_callback_for(*part_class_names, key:, &body)
|
76
|
+
part_class_names.each do |part_class_name|
|
77
|
+
k = "Protod::Proto::#{part_class_name.to_s.classify}"
|
78
|
+
|
79
|
+
self._findable_keys_for ||= {}
|
80
|
+
self._findable_body_for ||= {}
|
81
|
+
|
82
|
+
self._findable_keys_for[k] = ::Array.wrap(key).map(&:to_s)
|
83
|
+
self._findable_body_for[k] = body
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def findable_keys_for(part_class_name)
|
88
|
+
k = "Protod::Proto::#{part_class_name.to_s.classify}"
|
89
|
+
|
90
|
+
_findable_keys_for[k] || []
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
included do
|
95
|
+
class_attribute :_findable_keys_for
|
96
|
+
class_attribute :_findable_body_for
|
97
|
+
|
98
|
+
def find(part, by:, as: nil)
|
99
|
+
by = by.to_s
|
100
|
+
value = case part
|
101
|
+
when ::String
|
102
|
+
part
|
103
|
+
when ::Symbol
|
104
|
+
part.to_s
|
105
|
+
else
|
106
|
+
part.public_send(by)
|
107
|
+
end
|
108
|
+
part = as ? as.safe_constantize&.allocate : part
|
109
|
+
keys = _findable_keys_for.fetch(part.class.name, nil)
|
110
|
+
body = _findable_body_for.fetch(part.class.name, nil)
|
111
|
+
|
112
|
+
raise ArgumentError, "Unsupported as : #{as}" if keys.blank? && as
|
113
|
+
raise ArgumentError, "Unsupported part : #{part.class.name}" if keys.blank?
|
114
|
+
raise ArgumentError, "Unsupported by : #{by}. #{keys.join(', ')} are available" unless by.in?(keys)
|
115
|
+
raise NotImplementedError, "Sorry, this is bug forgetting to implement for #{part.class.name} at #{self.class.name}" unless body
|
116
|
+
|
117
|
+
instance_exec(by, value, &body)
|
118
|
+
end
|
119
|
+
|
120
|
+
def find_or_push(part, into:, by:, as: nil, &body)
|
121
|
+
new_part = if part.is_a?(::String)
|
122
|
+
c = as ? as.safe_constantize : "Protod::Proto::#{into.to_s.classify}".constantize
|
123
|
+
|
124
|
+
raise ArgumentError, "Unsupported as : #{as}" unless c
|
125
|
+
|
126
|
+
c.new(by.to_sym => part)
|
127
|
+
else
|
128
|
+
part
|
129
|
+
end
|
130
|
+
|
131
|
+
as = part.is_a?(::String) ? new_part.class.name : nil
|
132
|
+
|
133
|
+
find(part, by: by, as: as) || push(new_part, into: into).tap { body&.call(_1) }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
module FieldCollectable
|
139
|
+
def collect_fields
|
140
|
+
collector = ->(part) do
|
141
|
+
case part
|
142
|
+
when Protod::Proto::Package
|
143
|
+
part.messages.flat_map { collector.call(_1) }
|
144
|
+
when Protod::Proto::Message
|
145
|
+
[
|
146
|
+
*part.fields.flat_map { collector.call(_1) },
|
147
|
+
*part.messages.flat_map { collector.call(_1) },
|
148
|
+
]
|
149
|
+
when Protod::Proto::Oneof
|
150
|
+
part.fields.flat_map { collector.call(_1) }
|
151
|
+
when Protod::Proto::Field
|
152
|
+
[part]
|
153
|
+
else
|
154
|
+
[]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
collector.call(self)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
module FieldNumeringable
|
163
|
+
def numbering_fields_with(start_index)
|
164
|
+
index = start_index
|
165
|
+
|
166
|
+
fields.each do |f|
|
167
|
+
case f
|
168
|
+
when Protod::Proto::Field
|
169
|
+
f.index = index
|
170
|
+
|
171
|
+
index = f.index + 1
|
172
|
+
when Protod::Proto::Oneof
|
173
|
+
index = f.numbering_fields_with(index)
|
174
|
+
else
|
175
|
+
raise NotImplementedError, "Sorry, this is bug forgetting to implement for #{f.class.name}"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
index
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
module InterpreterBindable
|
184
|
+
def bind(interpreter)
|
185
|
+
raise ArgumentError, "Not bindable interpreter #{interpreter.proto_full_ident} trying bound to #{ident}" unless interpreter.bindable?
|
186
|
+
interpreter.set_parent(self)
|
187
|
+
push(interpreter.proto_message, into: :messages, ignore: true)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Protod
|
4
|
+
module Proto
|
5
|
+
class Procedure < Part
|
6
|
+
attribute :singleton, :boolean, default: false
|
7
|
+
attribute :has_request, :boolean, default: true
|
8
|
+
attribute :has_response, :boolean, default: true
|
9
|
+
attribute :streaming_request, :boolean, default: false
|
10
|
+
attribute :streaming_response, :boolean, default: false
|
11
|
+
|
12
|
+
def ident=(value)
|
13
|
+
super(value.to_s.camelize)
|
14
|
+
end
|
15
|
+
|
16
|
+
def ruby_ident
|
17
|
+
raise ArgumentError, "Not set parent" unless parent
|
18
|
+
|
19
|
+
Protod::RubyIdent.new(const_name: parent.ruby_ident, method_name: ruby_method_name, singleton: singleton)
|
20
|
+
end
|
21
|
+
|
22
|
+
def ruby_method_name
|
23
|
+
ident.underscore
|
24
|
+
end
|
25
|
+
|
26
|
+
def request_ident
|
27
|
+
"#{ident}Request"
|
28
|
+
end
|
29
|
+
|
30
|
+
def response_ident
|
31
|
+
"#{ident}Response"
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_proto
|
35
|
+
request_part = format("%s%s", streaming_request ? 'stream ' : '', has_request ? request_ident : '')
|
36
|
+
response_part = format("%s%s", streaming_response ? 'stream ' : '', has_response ? response_ident : '')
|
37
|
+
|
38
|
+
format_proto("rpc %s (%s) returns (%s);", ident, request_part, response_part)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|