grpc-server-reflection 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/LICENSE +21 -0
- data/README.md +39 -0
- data/grpc/reflection/v1/reflection.proto +146 -0
- data/grpc/reflection/v1/reflection_pb.rb +26 -0
- data/grpc/reflection/v1/reflection_services_pb.rb +50 -0
- data/grpc/reflection/v1alpha/reflection.proto +136 -0
- data/grpc/reflection/v1alpha/reflection_pb.rb +26 -0
- data/grpc/reflection/v1alpha/reflection_services_pb.rb +45 -0
- data/lib/grpc_server_reflection/descriptor_registry/dependency_resolver.rb +42 -0
- data/lib/grpc_server_reflection/descriptor_registry/file_descriptor_indexer.rb +68 -0
- data/lib/grpc_server_reflection/descriptor_registry/object_space_indexer.rb +153 -0
- data/lib/grpc_server_reflection/descriptor_registry/proto_builder.rb +259 -0
- data/lib/grpc_server_reflection/descriptor_registry/type_mapping.rb +63 -0
- data/lib/grpc_server_reflection/descriptor_registry.rb +92 -0
- data/lib/grpc_server_reflection/service.rb +154 -0
- data/lib/grpc_server_reflection/version.rb +3 -0
- data/lib/grpc_server_reflection.rb +6 -0
- metadata +145 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
module GrpcServerReflection
|
|
2
|
+
class DescriptorRegistry
|
|
3
|
+
class ObjectSpaceIndexer
|
|
4
|
+
include TypeMapping
|
|
5
|
+
|
|
6
|
+
def initialize(allowed_services:)
|
|
7
|
+
@allowed_services = allowed_services
|
|
8
|
+
@proto_builder = ProtoBuilder.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def build_index(files_by_symbol:, serialized_files:, dependencies:, service_names:)
|
|
12
|
+
file_messages = {} # filename => { file_descriptor:, descriptors: [] }
|
|
13
|
+
file_enums = {} # filename => [Google::Protobuf::EnumDescriptor, ...]
|
|
14
|
+
file_services = {} # filename => [{ service_name:, klass: }, ...]
|
|
15
|
+
|
|
16
|
+
scan_object_space(file_messages, file_enums, file_services, service_names)
|
|
17
|
+
|
|
18
|
+
symbol_to_filename = build_symbol_index(file_messages, file_enums, file_services)
|
|
19
|
+
|
|
20
|
+
build_serialized_files(
|
|
21
|
+
file_messages, file_enums, file_services, symbol_to_filename,
|
|
22
|
+
files_by_symbol: files_by_symbol, serialized_files: serialized_files, dependencies: dependencies
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def scan_object_space(file_messages, file_enums, file_services, service_names)
|
|
29
|
+
ObjectSpace.each_object(Class).each do |klass|
|
|
30
|
+
scan_message_class(klass, file_messages)
|
|
31
|
+
scan_service_class(klass, file_services, service_names)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
ObjectSpace.each_object(Google::Protobuf::EnumDescriptor).each do |enum_desc|
|
|
35
|
+
fd = enum_desc.file_descriptor
|
|
36
|
+
next unless fd
|
|
37
|
+
|
|
38
|
+
filename = fd.name
|
|
39
|
+
file_enums[filename] ||= []
|
|
40
|
+
file_enums[filename] << enum_desc
|
|
41
|
+
|
|
42
|
+
file_messages[filename] ||= { file_descriptor: fd, descriptors: [] }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def scan_message_class(klass, file_messages)
|
|
47
|
+
return unless safe_respond_to?(klass, :descriptor)
|
|
48
|
+
|
|
49
|
+
desc = safe_call(klass, :descriptor)
|
|
50
|
+
return unless desc.is_a?(Google::Protobuf::Descriptor)
|
|
51
|
+
|
|
52
|
+
fd = desc.file_descriptor
|
|
53
|
+
return unless fd
|
|
54
|
+
|
|
55
|
+
filename = fd.name
|
|
56
|
+
file_messages[filename] ||= { file_descriptor: fd, descriptors: [] }
|
|
57
|
+
file_messages[filename][:descriptors] << desc
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def scan_service_class(klass, file_services, service_names)
|
|
61
|
+
return unless safe_respond_to?(klass, :service_name)
|
|
62
|
+
|
|
63
|
+
begin
|
|
64
|
+
return unless klass.included_modules.include?(GRPC::GenericService)
|
|
65
|
+
rescue StandardError
|
|
66
|
+
return
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
service_name = klass.service_name
|
|
70
|
+
return if service_name.nil? || service_name.empty?
|
|
71
|
+
return if service_names.include?(service_name)
|
|
72
|
+
return if @allowed_services && !@allowed_services.include?(service_name)
|
|
73
|
+
|
|
74
|
+
service_names << service_name
|
|
75
|
+
|
|
76
|
+
filename = find_service_filename(klass)
|
|
77
|
+
if filename
|
|
78
|
+
file_services[filename] ||= []
|
|
79
|
+
file_services[filename] << { service_name: service_name, klass: klass }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def find_service_filename(klass)
|
|
84
|
+
return nil unless klass.respond_to?(:rpc_descs)
|
|
85
|
+
|
|
86
|
+
klass.rpc_descs.each_value do |rpc_desc|
|
|
87
|
+
input_type = rpc_desc.input
|
|
88
|
+
input_type = input_type.type if input_type.is_a?(GRPC::RpcDesc::Stream)
|
|
89
|
+
if safe_respond_to?(input_type, :descriptor)
|
|
90
|
+
input_desc = safe_call(input_type, :descriptor)
|
|
91
|
+
if input_desc && input_desc.respond_to?(:file_descriptor) && input_desc.file_descriptor
|
|
92
|
+
return input_desc.file_descriptor.name
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
nil
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def build_symbol_index(file_messages, file_enums, file_services)
|
|
100
|
+
symbol_to_filename = {}
|
|
101
|
+
|
|
102
|
+
file_messages.each do |filename, data|
|
|
103
|
+
data[:descriptors].each { |desc| symbol_to_filename[desc.name] = filename }
|
|
104
|
+
end
|
|
105
|
+
file_enums.each do |filename, enums|
|
|
106
|
+
enums.each { |desc| symbol_to_filename[desc.name] = filename }
|
|
107
|
+
end
|
|
108
|
+
file_services.each do |filename, entries|
|
|
109
|
+
entries.each do |entry|
|
|
110
|
+
symbol_to_filename[entry[:service_name]] = filename
|
|
111
|
+
if entry[:klass].respond_to?(:rpc_descs)
|
|
112
|
+
entry[:klass].rpc_descs.each_key do |method_name|
|
|
113
|
+
symbol_to_filename["#{entry[:service_name]}.#{method_name}"] = filename
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
symbol_to_filename
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def build_serialized_files(file_messages, file_enums, file_services, symbol_to_filename, files_by_symbol:, serialized_files:, dependencies:)
|
|
123
|
+
all_filenames = (file_messages.keys + file_services.keys + file_enums.keys).uniq
|
|
124
|
+
|
|
125
|
+
all_filenames.each do |filename|
|
|
126
|
+
msgs = file_messages[filename]
|
|
127
|
+
fd_obj = msgs ? msgs[:file_descriptor] : nil
|
|
128
|
+
msg_descriptors = msgs ? msgs[:descriptors] : []
|
|
129
|
+
enum_descriptors = file_enums[filename] || []
|
|
130
|
+
svc_entries = file_services[filename] || []
|
|
131
|
+
|
|
132
|
+
file_deps = []
|
|
133
|
+
serialized = @proto_builder.build_file_descriptor_proto(
|
|
134
|
+
filename, fd_obj, msg_descriptors, enum_descriptors, svc_entries, symbol_to_filename, file_deps
|
|
135
|
+
)
|
|
136
|
+
serialized_files[filename] = serialized
|
|
137
|
+
dependencies[filename] = file_deps
|
|
138
|
+
|
|
139
|
+
msg_descriptors.each { |desc| files_by_symbol[desc.name] = filename }
|
|
140
|
+
enum_descriptors.each { |desc| files_by_symbol[desc.name] = filename }
|
|
141
|
+
svc_entries.each do |entry|
|
|
142
|
+
files_by_symbol[entry[:service_name]] = filename
|
|
143
|
+
if entry[:klass].respond_to?(:rpc_descs)
|
|
144
|
+
entry[:klass].rpc_descs.each_key do |method_name|
|
|
145
|
+
files_by_symbol["#{entry[:service_name]}.#{method_name}"] = filename
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
module GrpcServerReflection
|
|
2
|
+
class DescriptorRegistry
|
|
3
|
+
class ProtoBuilder
|
|
4
|
+
include TypeMapping
|
|
5
|
+
|
|
6
|
+
def initialize
|
|
7
|
+
@dependency_resolver = DependencyResolver.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def build_file_descriptor_proto(filename, fd_obj, msg_descriptors, enum_descriptors, svc_entries, symbol_to_filename, out_deps)
|
|
11
|
+
package = extract_package(msg_descriptors, enum_descriptors, svc_entries)
|
|
12
|
+
dependencies = []
|
|
13
|
+
|
|
14
|
+
local_symbols = {}
|
|
15
|
+
msg_descriptors.each { |desc| local_symbols[desc.name] = true }
|
|
16
|
+
enum_descriptors.each { |desc| local_symbols[desc.name] = true }
|
|
17
|
+
|
|
18
|
+
file_proto = Google::Protobuf::FileDescriptorProto.new(
|
|
19
|
+
name: filename,
|
|
20
|
+
package: package,
|
|
21
|
+
syntax: fd_obj ? fd_obj.syntax.to_s : 'proto3'
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Deduplicate and separate top-level from nested
|
|
25
|
+
seen_names = {}
|
|
26
|
+
top_level = []
|
|
27
|
+
nested = []
|
|
28
|
+
|
|
29
|
+
msg_descriptors.each do |desc|
|
|
30
|
+
short_name = remove_package(desc.name, package)
|
|
31
|
+
next if seen_names[short_name]
|
|
32
|
+
seen_names[short_name] = true
|
|
33
|
+
|
|
34
|
+
if short_name.include?('.')
|
|
35
|
+
nested << desc
|
|
36
|
+
else
|
|
37
|
+
top_level << desc
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Build top-level messages
|
|
42
|
+
top_level_protos = {}
|
|
43
|
+
top_level.each do |desc|
|
|
44
|
+
short_name = remove_package(desc.name, package)
|
|
45
|
+
msg_proto = build_message_descriptor_proto(desc, package)
|
|
46
|
+
top_level_protos[short_name] = msg_proto
|
|
47
|
+
file_proto.message_type << msg_proto
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Nest child messages inside their parents
|
|
51
|
+
nested.sort_by { |desc| remove_package(desc.name, package).count('.') }.each do |desc|
|
|
52
|
+
short_name = remove_package(desc.name, package)
|
|
53
|
+
parts = short_name.split('.')
|
|
54
|
+
child_name = parts.last
|
|
55
|
+
parent_name = parts[0..-2].join('.')
|
|
56
|
+
|
|
57
|
+
child_proto = build_message_descriptor_proto(desc, package)
|
|
58
|
+
child_proto.name = child_name
|
|
59
|
+
|
|
60
|
+
parent = top_level_protos[parent_name]
|
|
61
|
+
if parent
|
|
62
|
+
parent.nested_type << child_proto
|
|
63
|
+
top_level_protos[short_name] = child_proto
|
|
64
|
+
else
|
|
65
|
+
file_proto.message_type << child_proto
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Add enums (top-level and nested)
|
|
70
|
+
seen_enums = {}
|
|
71
|
+
enum_descriptors.each do |enum_desc|
|
|
72
|
+
short_name = remove_package(enum_desc.name, package)
|
|
73
|
+
next if seen_enums[short_name]
|
|
74
|
+
seen_enums[short_name] = true
|
|
75
|
+
|
|
76
|
+
enum_proto = build_enum_descriptor_proto(enum_desc, package)
|
|
77
|
+
|
|
78
|
+
if short_name.include?('.')
|
|
79
|
+
parts = short_name.split('.')
|
|
80
|
+
parent_name = parts[0..-2].join('.')
|
|
81
|
+
enum_proto.name = parts.last
|
|
82
|
+
if top_level_protos[parent_name]
|
|
83
|
+
top_level_protos[parent_name].enum_type << enum_proto
|
|
84
|
+
else
|
|
85
|
+
file_proto.enum_type << enum_proto
|
|
86
|
+
end
|
|
87
|
+
else
|
|
88
|
+
file_proto.enum_type << enum_proto
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Add services
|
|
93
|
+
svc_entries.each do |entry|
|
|
94
|
+
file_proto.service << build_service_descriptor_proto(entry, package)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Collect cross-file dependencies
|
|
98
|
+
@dependency_resolver.collect_file_dependencies(file_proto, local_symbols, dependencies, symbol_to_filename)
|
|
99
|
+
dependencies.uniq!
|
|
100
|
+
dependencies.each { |dep| file_proto.dependency << dep }
|
|
101
|
+
out_deps.concat(dependencies)
|
|
102
|
+
|
|
103
|
+
Google::Protobuf::FileDescriptorProto.encode(file_proto)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def build_message_descriptor_proto(desc, package)
|
|
109
|
+
short_name = remove_package(desc.name, package)
|
|
110
|
+
|
|
111
|
+
msg_proto = Google::Protobuf::DescriptorProto.new(name: short_name)
|
|
112
|
+
pool = Google::Protobuf::DescriptorPool.generated_pool
|
|
113
|
+
|
|
114
|
+
desc.each do |field|
|
|
115
|
+
field_proto = Google::Protobuf::FieldDescriptorProto.new(
|
|
116
|
+
name: field.name,
|
|
117
|
+
number: field.number,
|
|
118
|
+
type: proto_field_type(field.type),
|
|
119
|
+
label: proto_field_label(field.label),
|
|
120
|
+
json_name: field.json_name
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if field.type == :message || field.type == :enum
|
|
124
|
+
if field.submsg_name && field.submsg_name.include?('_MapEntry_')
|
|
125
|
+
map_entry = build_map_entry(field, pool)
|
|
126
|
+
if map_entry
|
|
127
|
+
msg_proto.nested_type << map_entry
|
|
128
|
+
field_proto.type_name = ".#{desc.name}.#{map_entry.name}"
|
|
129
|
+
else
|
|
130
|
+
field_proto.type_name = ".#{field.submsg_name}"
|
|
131
|
+
end
|
|
132
|
+
elsif field.submsg_name
|
|
133
|
+
field_proto.type_name = ".#{field.submsg_name}"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
msg_proto.field << field_proto
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Add oneofs
|
|
141
|
+
oneof_index = 0
|
|
142
|
+
desc.each_oneof do |oneof|
|
|
143
|
+
msg_proto.oneof_decl << Google::Protobuf::OneofDescriptorProto.new(name: oneof.name)
|
|
144
|
+
|
|
145
|
+
desc.each do |field|
|
|
146
|
+
if belongs_to_oneof?(field, oneof)
|
|
147
|
+
msg_proto.field.each do |fp|
|
|
148
|
+
if fp.name == field.name
|
|
149
|
+
fp.oneof_index = oneof_index
|
|
150
|
+
break
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
oneof_index += 1
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
msg_proto
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def build_enum_descriptor_proto(enum_desc, package)
|
|
162
|
+
short_name = remove_package(enum_desc.name, package)
|
|
163
|
+
|
|
164
|
+
enum_proto = Google::Protobuf::EnumDescriptorProto.new(name: short_name)
|
|
165
|
+
|
|
166
|
+
enum_desc.each do |name, number|
|
|
167
|
+
enum_proto.value << Google::Protobuf::EnumValueDescriptorProto.new(
|
|
168
|
+
name: name.to_s,
|
|
169
|
+
number: number
|
|
170
|
+
)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
enum_proto
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def build_service_descriptor_proto(entry, package)
|
|
177
|
+
short_name = remove_package(entry[:service_name], package)
|
|
178
|
+
klass = entry[:klass]
|
|
179
|
+
|
|
180
|
+
svc_proto = Google::Protobuf::ServiceDescriptorProto.new(name: short_name)
|
|
181
|
+
|
|
182
|
+
if klass.respond_to?(:rpc_descs)
|
|
183
|
+
klass.rpc_descs.each do |method_name, rpc_desc|
|
|
184
|
+
input_type = rpc_desc.input
|
|
185
|
+
output_type = rpc_desc.output
|
|
186
|
+
client_streaming = input_type.is_a?(GRPC::RpcDesc::Stream)
|
|
187
|
+
server_streaming = output_type.is_a?(GRPC::RpcDesc::Stream)
|
|
188
|
+
input_type = input_type.type if client_streaming
|
|
189
|
+
output_type = output_type.type if server_streaming
|
|
190
|
+
|
|
191
|
+
input_name = descriptor_full_name(input_type)
|
|
192
|
+
output_name = descriptor_full_name(output_type)
|
|
193
|
+
|
|
194
|
+
method_proto = Google::Protobuf::MethodDescriptorProto.new(
|
|
195
|
+
name: method_name.to_s,
|
|
196
|
+
input_type: ".#{input_name}",
|
|
197
|
+
output_type: ".#{output_name}",
|
|
198
|
+
client_streaming: client_streaming,
|
|
199
|
+
server_streaming: server_streaming
|
|
200
|
+
)
|
|
201
|
+
svc_proto['method'] << method_proto
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
svc_proto
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def build_map_entry(field, pool)
|
|
209
|
+
entry_desc = pool.lookup(field.submsg_name)
|
|
210
|
+
return nil unless entry_desc
|
|
211
|
+
|
|
212
|
+
raw_suffix = field.submsg_name.split('_MapEntry_').last
|
|
213
|
+
entry_name = raw_suffix.split('_').map(&:capitalize).join + 'Entry'
|
|
214
|
+
|
|
215
|
+
entry_proto = Google::Protobuf::DescriptorProto.new(
|
|
216
|
+
name: entry_name,
|
|
217
|
+
options: Google::Protobuf::MessageOptions.new(map_entry: true)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
entry_desc.each do |entry_field|
|
|
221
|
+
fp = Google::Protobuf::FieldDescriptorProto.new(
|
|
222
|
+
name: entry_field.name,
|
|
223
|
+
number: entry_field.number,
|
|
224
|
+
type: proto_field_type(entry_field.type),
|
|
225
|
+
label: proto_field_label(entry_field.label)
|
|
226
|
+
)
|
|
227
|
+
if (entry_field.type == :message || entry_field.type == :enum) && entry_field.submsg_name
|
|
228
|
+
fp.type_name = ".#{entry_field.submsg_name}"
|
|
229
|
+
end
|
|
230
|
+
entry_proto.field << fp
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
entry_proto
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def extract_package(msg_descriptors, enum_descriptors, svc_entries)
|
|
237
|
+
all_names = []
|
|
238
|
+
svc_entries.each { |e| all_names << e[:service_name] }
|
|
239
|
+
msg_descriptors.each { |d| all_names << d.name }
|
|
240
|
+
enum_descriptors.each { |d| all_names << d.name }
|
|
241
|
+
|
|
242
|
+
return '' if all_names.empty?
|
|
243
|
+
|
|
244
|
+
shortest = all_names.min_by { |n| n.split('.').length }
|
|
245
|
+
parts = shortest.split('.')
|
|
246
|
+
parts.length > 1 ? parts[0..-2].join('.') : ''
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def belongs_to_oneof?(field, oneof)
|
|
250
|
+
oneof.each do |oneof_field|
|
|
251
|
+
return true if oneof_field.name == field.name
|
|
252
|
+
end
|
|
253
|
+
false
|
|
254
|
+
rescue
|
|
255
|
+
false
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module GrpcServerReflection
|
|
2
|
+
class DescriptorRegistry
|
|
3
|
+
module TypeMapping
|
|
4
|
+
FIELD_TYPE_MAP = {
|
|
5
|
+
double: :TYPE_DOUBLE, float: :TYPE_FLOAT,
|
|
6
|
+
int64: :TYPE_INT64, uint64: :TYPE_UINT64, int32: :TYPE_INT32,
|
|
7
|
+
fixed64: :TYPE_FIXED64, fixed32: :TYPE_FIXED32,
|
|
8
|
+
bool: :TYPE_BOOL, string: :TYPE_STRING, bytes: :TYPE_BYTES,
|
|
9
|
+
uint32: :TYPE_UINT32, enum: :TYPE_ENUM,
|
|
10
|
+
sfixed32: :TYPE_SFIXED32, sfixed64: :TYPE_SFIXED64,
|
|
11
|
+
sint32: :TYPE_SINT32, sint64: :TYPE_SINT64,
|
|
12
|
+
message: :TYPE_MESSAGE,
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
LABEL_MAP = {
|
|
16
|
+
optional: :LABEL_OPTIONAL,
|
|
17
|
+
required: :LABEL_REQUIRED,
|
|
18
|
+
repeated: :LABEL_REPEATED,
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
def proto_field_type(type)
|
|
22
|
+
mapped = FIELD_TYPE_MAP[type]
|
|
23
|
+
unless mapped
|
|
24
|
+
warn "[grpc-server-reflection] Unknown protobuf field type: #{type.inspect}, defaulting to TYPE_STRING"
|
|
25
|
+
return :TYPE_STRING
|
|
26
|
+
end
|
|
27
|
+
mapped
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def proto_field_label(label)
|
|
31
|
+
LABEL_MAP[label] || :LABEL_OPTIONAL
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def descriptor_full_name(type)
|
|
35
|
+
if safe_respond_to?(type, :descriptor) && (desc = safe_call(type, :descriptor))
|
|
36
|
+
desc.name
|
|
37
|
+
else
|
|
38
|
+
type.name.gsub('::', '.')
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def safe_respond_to?(obj, method)
|
|
43
|
+
obj.respond_to?(method)
|
|
44
|
+
rescue StandardError, NotImplementedError
|
|
45
|
+
false
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def safe_call(obj, method)
|
|
49
|
+
obj.send(method)
|
|
50
|
+
rescue StandardError, NotImplementedError
|
|
51
|
+
nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def remove_package(full_name, package)
|
|
55
|
+
if package && !package.empty? && full_name.start_with?("#{package}.")
|
|
56
|
+
full_name.sub("#{package}.", '')
|
|
57
|
+
else
|
|
58
|
+
full_name
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
require_relative 'descriptor_registry/type_mapping'
|
|
3
|
+
require_relative 'descriptor_registry/dependency_resolver'
|
|
4
|
+
require_relative 'descriptor_registry/proto_builder'
|
|
5
|
+
require_relative 'descriptor_registry/file_descriptor_indexer'
|
|
6
|
+
require_relative 'descriptor_registry/object_space_indexer'
|
|
7
|
+
|
|
8
|
+
module GrpcServerReflection
|
|
9
|
+
class DescriptorRegistry
|
|
10
|
+
attr_reader :service_names
|
|
11
|
+
|
|
12
|
+
def initialize(services: nil, allowed_service_names: nil)
|
|
13
|
+
@files_by_symbol = {} # symbol => filename
|
|
14
|
+
@serialized_files = {} # filename => serialized bytes
|
|
15
|
+
@dependencies = {} # filename => [dep_filename, ...]
|
|
16
|
+
@service_names = Set.new
|
|
17
|
+
@extensions_by_type = {}
|
|
18
|
+
@allowed_services = if allowed_service_names
|
|
19
|
+
allowed_service_names
|
|
20
|
+
elsif services
|
|
21
|
+
services.map { |s| s.service_name }.compact
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
build_index
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def list_services
|
|
28
|
+
@service_names.to_a
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def find_file_by_name(filename)
|
|
32
|
+
@serialized_files[filename]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def find_filename_by_symbol(symbol)
|
|
36
|
+
@files_by_symbol[symbol]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def find_file_by_symbol(symbol)
|
|
40
|
+
filename = @files_by_symbol[symbol]
|
|
41
|
+
return nil unless filename
|
|
42
|
+
@serialized_files[filename]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def file_descriptors_with_dependencies(filename)
|
|
46
|
+
return [] unless @serialized_files.key?(filename)
|
|
47
|
+
|
|
48
|
+
visited = Set.new
|
|
49
|
+
result = []
|
|
50
|
+
collect_dependencies(filename, visited, result)
|
|
51
|
+
result
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def find_extension_numbers(type)
|
|
55
|
+
@extensions_by_type.fetch(type, [])
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def build_index
|
|
61
|
+
pool = Google::Protobuf::DescriptorPool.generated_pool
|
|
62
|
+
|
|
63
|
+
index_data = {
|
|
64
|
+
files_by_symbol: @files_by_symbol,
|
|
65
|
+
serialized_files: @serialized_files,
|
|
66
|
+
dependencies: @dependencies,
|
|
67
|
+
service_names: @service_names,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if pool.respond_to?(:each_file_descriptor)
|
|
71
|
+
FileDescriptorIndexer.new(allowed_services: @allowed_services).build_index(pool, **index_data)
|
|
72
|
+
else
|
|
73
|
+
ObjectSpaceIndexer.new(allowed_services: @allowed_services).build_index(**index_data)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def collect_dependencies(filename, visited, result)
|
|
78
|
+
return if visited.include?(filename)
|
|
79
|
+
visited << filename
|
|
80
|
+
|
|
81
|
+
serialized = @serialized_files[filename]
|
|
82
|
+
return unless serialized
|
|
83
|
+
|
|
84
|
+
result << serialized
|
|
85
|
+
|
|
86
|
+
deps = @dependencies[filename]
|
|
87
|
+
if deps
|
|
88
|
+
deps.each { |dep| collect_dependencies(dep, visited, result) }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|