protod 0.1.0
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 +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
|