protostar 0.1.0.beta1
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/CHANGELOG.md +60 -0
- data/LICENSE.txt +21 -0
- data/README.md +647 -0
- data/THIRD_PARTY_NOTICES.md +61 -0
- data/exe/protoc-gen-protostar +7 -0
- data/exe/protostar +7 -0
- data/lib/protostar/builder/generator.rb +71 -0
- data/lib/protostar/builder/sink.rb +35 -0
- data/lib/protostar/builder.rb +965 -0
- data/lib/protostar/cli.rb +267 -0
- data/lib/protostar/codec/decode_generator.rb +199 -0
- data/lib/protostar/codec/decoder.rb +296 -0
- data/lib/protostar/codec/encode_generator.rb +224 -0
- data/lib/protostar/codec/encoder.rb +121 -0
- data/lib/protostar/codec/sizer.rb +91 -0
- data/lib/protostar/codec.rb +15 -0
- data/lib/protostar/compiler/ast.rb +193 -0
- data/lib/protostar/compiler/emitters/descriptor_set.rb +57 -0
- data/lib/protostar/compiler/emitters/google_ruby.rb +103 -0
- data/lib/protostar/compiler/emitters/grpc.rb +175 -0
- data/lib/protostar/compiler/emitters/protostar_rbs.rb +252 -0
- data/lib/protostar/compiler/emitters/rbs.rb +394 -0
- data/lib/protostar/compiler/emitters/ruby.rb +503 -0
- data/lib/protostar/compiler/emitters.rb +268 -0
- data/lib/protostar/compiler/include/LICENSE +32 -0
- data/lib/protostar/compiler/include/google/protobuf/any.proto +106 -0
- data/lib/protostar/compiler/include/google/protobuf/api.proto +229 -0
- data/lib/protostar/compiler/include/google/protobuf/compiler/plugin.proto +180 -0
- data/lib/protostar/compiler/include/google/protobuf/cpp_features.proto +87 -0
- data/lib/protostar/compiler/include/google/protobuf/descriptor.proto +1532 -0
- data/lib/protostar/compiler/include/google/protobuf/duration.proto +115 -0
- data/lib/protostar/compiler/include/google/protobuf/empty.proto +51 -0
- data/lib/protostar/compiler/include/google/protobuf/field_mask.proto +243 -0
- data/lib/protostar/compiler/include/google/protobuf/java_features.proto +132 -0
- data/lib/protostar/compiler/include/google/protobuf/source_context.proto +48 -0
- data/lib/protostar/compiler/include/google/protobuf/struct.proto +111 -0
- data/lib/protostar/compiler/include/google/protobuf/timestamp.proto +145 -0
- data/lib/protostar/compiler/include/google/protobuf/type.proto +217 -0
- data/lib/protostar/compiler/include/google/protobuf/wrappers.proto +157 -0
- data/lib/protostar/compiler/lexer.rb +659 -0
- data/lib/protostar/compiler/linker.rb +894 -0
- data/lib/protostar/compiler/option_interpreter.rb +363 -0
- data/lib/protostar/compiler/parser.rb +1696 -0
- data/lib/protostar/compiler/retention.rb +104 -0
- data/lib/protostar/compiler/run.rb +184 -0
- data/lib/protostar/compiler/source_info.rb +161 -0
- data/lib/protostar/compiler/symbol_table.rb +223 -0
- data/lib/protostar/compiler/text_format.rb +340 -0
- data/lib/protostar/compiler/validator.rb +944 -0
- data/lib/protostar/compiler.rb +304 -0
- data/lib/protostar/diagnostics/codes.rb +1307 -0
- data/lib/protostar/diagnostics/formats.rb +64 -0
- data/lib/protostar/diagnostics/renderer.rb +240 -0
- data/lib/protostar/diagnostics/source.rb +153 -0
- data/lib/protostar/diagnostics.rb +117 -0
- data/lib/protostar/dsl.rb +367 -0
- data/lib/protostar/enum.rb +70 -0
- data/lib/protostar/errors.rb +23 -0
- data/lib/protostar/google/protobuf/any_pb.rb +90 -0
- data/lib/protostar/google/protobuf/api_pb.rb +199 -0
- data/lib/protostar/google/protobuf/compiler/plugin_pb.rb +168 -0
- data/lib/protostar/google/protobuf/descriptor_pb.rb +1239 -0
- data/lib/protostar/google/protobuf/duration_pb.rb +99 -0
- data/lib/protostar/google/protobuf/empty_pb.rb +36 -0
- data/lib/protostar/google/protobuf/field_mask_pb.rb +228 -0
- data/lib/protostar/google/protobuf/source_context_pb.rb +33 -0
- data/lib/protostar/google/protobuf/struct_pb.rb +91 -0
- data/lib/protostar/google/protobuf/timestamp_pb.rb +129 -0
- data/lib/protostar/google/protobuf/type_pb.rb +202 -0
- data/lib/protostar/google/protobuf/wrappers_pb.rb +125 -0
- data/lib/protostar/google_protobuf_shim.rb +24 -0
- data/lib/protostar/json/encoder.rb +340 -0
- data/lib/protostar/json/errors.rb +34 -0
- data/lib/protostar/json/parser.rb +437 -0
- data/lib/protostar/json/path.rb +39 -0
- data/lib/protostar/json.rb +95 -0
- data/lib/protostar/lint/command.rb +197 -0
- data/lib/protostar/lint/config.rb +98 -0
- data/lib/protostar/lint/context.rb +126 -0
- data/lib/protostar/lint/directives.rb +56 -0
- data/lib/protostar/lint/fixer.rb +165 -0
- data/lib/protostar/lint/naming.rb +54 -0
- data/lib/protostar/lint/rule.rb +120 -0
- data/lib/protostar/lint/rules/comment_enum.rb +31 -0
- data/lib/protostar/lint/rules/comment_enum_value.rb +32 -0
- data/lib/protostar/lint/rules/comment_field.rb +33 -0
- data/lib/protostar/lint/rules/comment_message.rb +31 -0
- data/lib/protostar/lint/rules/comment_rpc.rb +31 -0
- data/lib/protostar/lint/rules/comment_service.rb +31 -0
- data/lib/protostar/lint/rules/edition_recommended.rb +31 -0
- data/lib/protostar/lint/rules/enum_first_value_zero.rb +34 -0
- data/lib/protostar/lint/rules/enum_pascal_case.rb +25 -0
- data/lib/protostar/lint/rules/enum_value_prefix.rb +38 -0
- data/lib/protostar/lint/rules/enum_value_upper_snake_case.rb +28 -0
- data/lib/protostar/lint/rules/enum_zero_value_suffix.rb +41 -0
- data/lib/protostar/lint/rules/field_lower_snake_case.rb +34 -0
- data/lib/protostar/lint/rules/field_not_required.rb +39 -0
- data/lib/protostar/lint/rules/field_number_reuse_risk.rb +45 -0
- data/lib/protostar/lint/rules/field_numbers_hot_range.rb +44 -0
- data/lib/protostar/lint/rules/file_lower_snake_case.rb +27 -0
- data/lib/protostar/lint/rules/import_no_public.rb +29 -0
- data/lib/protostar/lint/rules/import_no_weak.rb +28 -0
- data/lib/protostar/lint/rules/import_used.rb +33 -0
- data/lib/protostar/lint/rules/message_pascal_case.rb +27 -0
- data/lib/protostar/lint/rules/no_deprecated_features.rb +50 -0
- data/lib/protostar/lint/rules/no_groups.rb +34 -0
- data/lib/protostar/lint/rules/oneof_lower_snake_case.rb +27 -0
- data/lib/protostar/lint/rules/package_defined.rb +28 -0
- data/lib/protostar/lint/rules/package_directory_match.rb +33 -0
- data/lib/protostar/lint/rules/package_lower_snake_case.rb +28 -0
- data/lib/protostar/lint/rules/package_same_directory.rb +39 -0
- data/lib/protostar/lint/rules/package_version_suffix.rb +31 -0
- data/lib/protostar/lint/rules/reserved_names_with_numbers.rb +44 -0
- data/lib/protostar/lint/rules/rpc_pascal_case.rb +25 -0
- data/lib/protostar/lint/rules/rpc_request_response_unique.rb +40 -0
- data/lib/protostar/lint/rules/rpc_request_standard_name.rb +32 -0
- data/lib/protostar/lint/rules/rpc_response_standard_name.rb +31 -0
- data/lib/protostar/lint/rules/service_pascal_case.rb +25 -0
- data/lib/protostar/lint/rules/service_suffix.rb +28 -0
- data/lib/protostar/lint/rules/syntax_specified.rb +35 -0
- data/lib/protostar/lint/runner.rb +135 -0
- data/lib/protostar/lint.rb +122 -0
- data/lib/protostar/message/accessors.rb +86 -0
- data/lib/protostar/message/coercion.rb +171 -0
- data/lib/protostar/message/field_info.rb +145 -0
- data/lib/protostar/message/reflection.rb +149 -0
- data/lib/protostar/message.rb +572 -0
- data/lib/protostar/plugin.rb +94 -0
- data/lib/protostar/schema/default_value.rb +113 -0
- data/lib/protostar/schema/descriptor.rb +41 -0
- data/lib/protostar/schema/draft/copy.rb +88 -0
- data/lib/protostar/schema/draft/resolution.rb +177 -0
- data/lib/protostar/schema/draft.rb +779 -0
- data/lib/protostar/schema/enum.rb +106 -0
- data/lib/protostar/schema/extension_range.rb +37 -0
- data/lib/protostar/schema/features.rb +153 -0
- data/lib/protostar/schema/field.rb +201 -0
- data/lib/protostar/schema/file.rb +217 -0
- data/lib/protostar/schema/freezer.rb +61 -0
- data/lib/protostar/schema/message.rb +104 -0
- data/lib/protostar/schema/oneof.rb +38 -0
- data/lib/protostar/schema/options.rb +367 -0
- data/lib/protostar/schema/pool.rb +179 -0
- data/lib/protostar/schema/proto.rb +576 -0
- data/lib/protostar/schema/resolver.rb +118 -0
- data/lib/protostar/schema/service.rb +89 -0
- data/lib/protostar/schema/source_info.rb +53 -0
- data/lib/protostar/schema.rb +65 -0
- data/lib/protostar/short_names/any.rb +7 -0
- data/lib/protostar/short_names/duration.rb +7 -0
- data/lib/protostar/short_names/empty.rb +7 -0
- data/lib/protostar/short_names/field_mask.rb +7 -0
- data/lib/protostar/short_names/struct.rb +9 -0
- data/lib/protostar/short_names/timestamp.rb +7 -0
- data/lib/protostar/short_names/wrappers.rb +15 -0
- data/lib/protostar/text/extensions.rb +114 -0
- data/lib/protostar/text/parser.rb +439 -0
- data/lib/protostar/text/printer.rb +304 -0
- data/lib/protostar/text/tokenizer.rb +116 -0
- data/lib/protostar/text.rb +67 -0
- data/lib/protostar/types.rb +152 -0
- data/lib/protostar/unknown_field.rb +58 -0
- data/lib/protostar/version.rb +5 -0
- data/lib/protostar/view/generator.rb +298 -0
- data/lib/protostar/view/map.rb +194 -0
- data/lib/protostar/view/repeated.rb +143 -0
- data/lib/protostar/view/scan.rb +183 -0
- data/lib/protostar/view.rb +283 -0
- data/lib/protostar/well_known/any.rb +52 -0
- data/lib/protostar/well_known/duration.rb +53 -0
- data/lib/protostar/well_known/list_value.rb +63 -0
- data/lib/protostar/well_known/struct.rb +55 -0
- data/lib/protostar/well_known/timestamp.rb +64 -0
- data/lib/protostar/well_known/value.rb +60 -0
- data/lib/protostar/well_known.rb +38 -0
- data/lib/protostar/wire/io_source.rb +40 -0
- data/lib/protostar/wire/reader.rb +254 -0
- data/lib/protostar/wire/writer.rb +201 -0
- data/lib/protostar/wire.rb +96 -0
- data/lib/protostar.rb +95 -0
- data/shims/google-protobuf/lib/google/protobuf/any_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/api_pb.rb +22 -0
- data/shims/google-protobuf/lib/google/protobuf/compiler/plugin_pb.rb +25 -0
- data/shims/google-protobuf/lib/google/protobuf/descriptor_pb.rb +72 -0
- data/shims/google-protobuf/lib/google/protobuf/descriptor_pool.rb +164 -0
- data/shims/google-protobuf/lib/google/protobuf/descriptors.rb +553 -0
- data/shims/google-protobuf/lib/google/protobuf/duration_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/empty_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/field_mask_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/convert.rb +180 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/extensions.rb +154 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/json.rb +104 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/well_known.rb +140 -0
- data/shims/google-protobuf/lib/google/protobuf/map.rb +216 -0
- data/shims/google-protobuf/lib/google/protobuf/message.rb +544 -0
- data/shims/google-protobuf/lib/google/protobuf/message_exts.rb +41 -0
- data/shims/google-protobuf/lib/google/protobuf/plugin_pb.rb +4 -0
- data/shims/google-protobuf/lib/google/protobuf/repeated_field.rb +366 -0
- data/shims/google-protobuf/lib/google/protobuf/source_context_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/struct_pb.rb +20 -0
- data/shims/google-protobuf/lib/google/protobuf/timestamp_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/type_pb.rb +27 -0
- data/shims/google-protobuf/lib/google/protobuf/well_known_types.rb +37 -0
- data/shims/google-protobuf/lib/google/protobuf/wrappers_pb.rb +25 -0
- data/shims/google-protobuf/lib/google/protobuf.rb +107 -0
- data/shims/google-protobuf/lib/google/protobuf_ffi.rb +5 -0
- data/shims/google-protobuf/lib/google/protobuf_native.rb +5 -0
- data/sig/protostar/google/protobuf/any_pb.rbs +77 -0
- data/sig/protostar/google/protobuf/api_pb.rbs +242 -0
- data/sig/protostar/google/protobuf/compiler/plugin_pb.rbs +264 -0
- data/sig/protostar/google/protobuf/descriptor_pb.rbs +2220 -0
- data/sig/protostar/google/protobuf/duration_pb.rbs +82 -0
- data/sig/protostar/google/protobuf/empty_pb.rbs +18 -0
- data/sig/protostar/google/protobuf/field_mask_pb.rbs +170 -0
- data/sig/protostar/google/protobuf/source_context_pb.rbs +24 -0
- data/sig/protostar/google/protobuf/struct_pb.rbs +135 -0
- data/sig/protostar/google/protobuf/timestamp_pb.rbs +102 -0
- data/sig/protostar/google/protobuf/type_pb.rbs +304 -0
- data/sig/protostar/google/protobuf/wrappers_pb.rbs +169 -0
- data/sig/protostar/lint.rbs +216 -0
- data/sig/protostar.rbs +326 -0
- metadata +366 -0
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
module Schema
|
|
5
|
+
# The one place that knows protoc's shape. {File#to_proto} writes a
|
|
6
|
+
# file as the `FileDescriptorProto` message of the bootstrap classes
|
|
7
|
+
# (`Protostar::Google::Protobuf`), as protoc's `FileDescriptor::CopyTo`
|
|
8
|
+
# would; {File.from_proto} reads one back into a draft. Custom options
|
|
9
|
+
# exist as extension bytes inside options messages only here: written by
|
|
10
|
+
# {MessageValue#encode}, read by {MessageValue.decode}.
|
|
11
|
+
module Proto
|
|
12
|
+
# descriptor.proto's classes are autoloaded on first reference, which
|
|
13
|
+
# Ruby 3.2 and 3.3 can only do in the main Ractor
|
|
14
|
+
# @return [Module] `Protostar::Google::Protobuf`, descriptor.proto's message classes
|
|
15
|
+
def self.types
|
|
16
|
+
::Protostar::Google::Protobuf
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @param name [Symbol, String] a descriptor.proto message, e.g. :FieldOptions
|
|
20
|
+
# @return [Schema::Message] its descriptor, as the bootstrap defines it
|
|
21
|
+
def self.schema(name)
|
|
22
|
+
types.const_get(name).descriptor
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @param bytes [String] a serialized FileDescriptorSet
|
|
26
|
+
# @return [Array<String>] each serialized FileDescriptorProto
|
|
27
|
+
def self.each_file(bytes)
|
|
28
|
+
r = Wire::Reader.new(bytes)
|
|
29
|
+
files = []
|
|
30
|
+
until r.eof?
|
|
31
|
+
number, wire_type = r.read_tag
|
|
32
|
+
if number == 1 && wire_type == Wire::LENGTH_DELIMITED
|
|
33
|
+
files << r.read_bytes
|
|
34
|
+
else
|
|
35
|
+
r.skip_field(number, wire_type)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
files
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Descriptors as descriptor.proto messages
|
|
42
|
+
# @api private
|
|
43
|
+
module Writer
|
|
44
|
+
module_function
|
|
45
|
+
|
|
46
|
+
def file(file, source_info:, json_names:)
|
|
47
|
+
attrs = {name: file.name}
|
|
48
|
+
attrs[:package] = file.package unless file.package.empty?
|
|
49
|
+
attrs[:dependency] = file.dependencies unless file.dependencies.empty?
|
|
50
|
+
attrs[:public_dependency] = file.public_dependencies unless file.public_dependencies.empty?
|
|
51
|
+
attrs[:weak_dependency] = file.weak_dependencies unless file.weak_dependencies.empty?
|
|
52
|
+
attrs[:option_dependency] = file.option_dependencies unless file.option_dependencies.empty?
|
|
53
|
+
list(attrs, :message_type, file.messages) { |m| message(m, json_names) }
|
|
54
|
+
list(attrs, :enum_type, file.enums) { |e| enum(e) }
|
|
55
|
+
list(attrs, :service, file.services) { |s| service(s) }
|
|
56
|
+
list(attrs, :extension, file.extensions) { |f| field(f, json_names) }
|
|
57
|
+
options(attrs, file)
|
|
58
|
+
attrs[:source_code_info] = source_code_info(file.source_info) if source_info && file.source_info
|
|
59
|
+
attrs[:syntax] = file.proto_syntax if file.proto_syntax
|
|
60
|
+
attrs[:edition] = file.edition if file.edition
|
|
61
|
+
Proto.types::FileDescriptorProto.new(**attrs)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def message(message, json_names)
|
|
65
|
+
t = Proto.types
|
|
66
|
+
attrs = {name: message.name}
|
|
67
|
+
list(attrs, :field, message.fields) { |f| field(f, json_names) }
|
|
68
|
+
list(attrs, :extension, message.extensions) { |f| field(f, json_names) }
|
|
69
|
+
list(attrs, :nested_type, message.nested_messages) { |m| message(m, json_names) }
|
|
70
|
+
list(attrs, :enum_type, message.nested_enums) { |e| enum(e) }
|
|
71
|
+
list(attrs, :extension_range, message.extension_range_descriptors) do |r|
|
|
72
|
+
range = {start: r.start, end: r.end + 1}
|
|
73
|
+
options(range, r)
|
|
74
|
+
t::DescriptorProto::ExtensionRange.new(**range)
|
|
75
|
+
end
|
|
76
|
+
list(attrs, :oneof_decl, message.oneofs) { |o| oneof(o) }
|
|
77
|
+
options(attrs, message)
|
|
78
|
+
list(attrs, :reserved_range, message.reserved_ranges) do |r|
|
|
79
|
+
t::DescriptorProto::ReservedRange.new(start: r.begin, end: r.end + 1)
|
|
80
|
+
end
|
|
81
|
+
attrs[:reserved_name] = message.reserved_names unless message.reserved_names.empty?
|
|
82
|
+
attrs[:visibility] = Proto.visibility(message.visibility) if message.visibility
|
|
83
|
+
t::DescriptorProto.new(**attrs)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def field(field, json_names)
|
|
87
|
+
attrs = {name: field.name, number: field.number, label: Field::PROTO_LABELS.fetch(field.declared_label),
|
|
88
|
+
type: Field::PROTO_TYPES.fetch(field.declared_type)}
|
|
89
|
+
attrs[:type_name] = ".#{field.type_name}" if field.type_name
|
|
90
|
+
attrs[:extendee] = ".#{field.extendee.full_name}" if field.extension?
|
|
91
|
+
attrs[:default_value] = field.raw_default if field.raw_default
|
|
92
|
+
attrs[:oneof_index] = field.oneof.index if field.oneof
|
|
93
|
+
attrs[:json_name] = field.json_name if json_names || field.json_name != Schema.json_name(field.name)
|
|
94
|
+
options(attrs, field)
|
|
95
|
+
attrs[:proto3_optional] = true if field.proto3_optional?
|
|
96
|
+
Proto.types::FieldDescriptorProto.new(**attrs)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def oneof(oneof)
|
|
100
|
+
attrs = {name: oneof.name}
|
|
101
|
+
options(attrs, oneof)
|
|
102
|
+
Proto.types::OneofDescriptorProto.new(**attrs)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def enum(enum)
|
|
106
|
+
t = Proto.types
|
|
107
|
+
attrs = {name: enum.name}
|
|
108
|
+
list(attrs, :value, enum.values) { |v| enum_value(v) }
|
|
109
|
+
options(attrs, enum)
|
|
110
|
+
list(attrs, :reserved_range, enum.reserved_ranges) do |r|
|
|
111
|
+
t::EnumDescriptorProto::EnumReservedRange.new(start: r.begin, end: r.end)
|
|
112
|
+
end
|
|
113
|
+
attrs[:reserved_name] = enum.reserved_names unless enum.reserved_names.empty?
|
|
114
|
+
attrs[:visibility] = Proto.visibility(enum.visibility) if enum.visibility
|
|
115
|
+
t::EnumDescriptorProto.new(**attrs)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def enum_value(value)
|
|
119
|
+
attrs = {name: value.name, number: value.number}
|
|
120
|
+
options(attrs, value)
|
|
121
|
+
Proto.types::EnumValueDescriptorProto.new(**attrs)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def service(service)
|
|
125
|
+
attrs = {name: service.name}
|
|
126
|
+
list(attrs, :method, service.rpcs) { |m| rpc(m) }
|
|
127
|
+
options(attrs, service)
|
|
128
|
+
Proto.types::ServiceDescriptorProto.new(**attrs)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def rpc(rpc)
|
|
132
|
+
attrs = {name: rpc.name, input_type: ".#{rpc.input_type.full_name}",
|
|
133
|
+
output_type: ".#{rpc.output_type.full_name}"}
|
|
134
|
+
options(attrs, rpc)
|
|
135
|
+
attrs[:client_streaming] = true if rpc.client_streaming?
|
|
136
|
+
attrs[:server_streaming] = true if rpc.server_streaming?
|
|
137
|
+
Proto.types::MethodDescriptorProto.new(**attrs)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def source_code_info(info)
|
|
141
|
+
location = Proto.types::SourceCodeInfo::Location
|
|
142
|
+
locations = info.locations.map do |loc|
|
|
143
|
+
attrs = {path: loc.path, span: loc.span}
|
|
144
|
+
attrs[:leading_comments] = loc.leading_comments if loc.leading_comments
|
|
145
|
+
attrs[:trailing_comments] = loc.trailing_comments if loc.trailing_comments
|
|
146
|
+
attrs[:leading_detached_comments] = loc.leading_detached_comments unless loc.leading_detached_comments.empty?
|
|
147
|
+
location.new(**attrs)
|
|
148
|
+
end
|
|
149
|
+
Proto.types::SourceCodeInfo.new(location: locations)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def options(attrs, descriptor)
|
|
153
|
+
options = descriptor.options
|
|
154
|
+
attrs[:options] = options.to_proto if options.declared?
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
FILE_SOURCE_CODE_INFO = 9 # FileDescriptorProto.source_code_info
|
|
158
|
+
|
|
159
|
+
# A serialized FileDescriptorProto with `source_code_info` put in its
|
|
160
|
+
# place among the fields, which are in number order. Thousands of
|
|
161
|
+
# locations are written straight to bytes rather than built as
|
|
162
|
+
# messages first: the same bytes, a quarter of the time.
|
|
163
|
+
def with_source_code_info(bytes, info)
|
|
164
|
+
r = Wire::Reader.new(bytes)
|
|
165
|
+
at = bytes.bytesize
|
|
166
|
+
until r.eof?
|
|
167
|
+
start = r.pos
|
|
168
|
+
number, wire_type = r.read_tag
|
|
169
|
+
if number > FILE_SOURCE_CODE_INFO
|
|
170
|
+
at = start
|
|
171
|
+
break
|
|
172
|
+
end
|
|
173
|
+
r.skip_field(number, wire_type)
|
|
174
|
+
end
|
|
175
|
+
out = bytes.byteslice(0, at)
|
|
176
|
+
w = Wire::Writer.new(out)
|
|
177
|
+
w.write_tag(FILE_SOURCE_CODE_INFO, Wire::LENGTH_DELIMITED)
|
|
178
|
+
w.write_bytes(source_code_info_bytes(info))
|
|
179
|
+
w.flush
|
|
180
|
+
out << bytes.byteslice(at, bytes.bytesize - at)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# SourceCodeInfo: location = 1; and in Location, path = 1 and span = 2
|
|
184
|
+
# (packed), leading_comments = 3, trailing_comments = 4,
|
|
185
|
+
# leading_detached_comments = 6
|
|
186
|
+
def source_code_info_bytes(info)
|
|
187
|
+
out = "".b
|
|
188
|
+
w = Wire::Writer.new(out)
|
|
189
|
+
loc_bytes = "".b
|
|
190
|
+
info.locations.each do |loc|
|
|
191
|
+
loc_bytes.clear
|
|
192
|
+
lw = Wire::Writer.new(loc_bytes)
|
|
193
|
+
packed(lw, 1, loc.path)
|
|
194
|
+
packed(lw, 2, loc.span)
|
|
195
|
+
string(lw, 3, loc.leading_comments) if loc.leading_comments
|
|
196
|
+
string(lw, 4, loc.trailing_comments) if loc.trailing_comments
|
|
197
|
+
loc.leading_detached_comments.each { |c| string(lw, 6, c) }
|
|
198
|
+
lw.flush
|
|
199
|
+
w.write_tag(1, Wire::LENGTH_DELIMITED)
|
|
200
|
+
w.write_bytes(loc_bytes)
|
|
201
|
+
end
|
|
202
|
+
w.flush
|
|
203
|
+
out
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def packed(w, number, values)
|
|
207
|
+
return if values.empty?
|
|
208
|
+
size = values.sum { |v| Wire.varint_size(v) }
|
|
209
|
+
w.write_tag(number, Wire::LENGTH_DELIMITED)
|
|
210
|
+
w.write_varint(size)
|
|
211
|
+
values.each { |v| w.write_varint(v) }
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def string(w, number, text)
|
|
215
|
+
w.write_tag(number, Wire::LENGTH_DELIMITED)
|
|
216
|
+
w.write_bytes(text.b)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def list(attrs, key, items, &block)
|
|
220
|
+
attrs[key] = items.map(&block) unless items.empty?
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# A FileDescriptorProto message read into a draft
|
|
225
|
+
# @api private
|
|
226
|
+
class Reader
|
|
227
|
+
def initialize(proto, pool)
|
|
228
|
+
@proto = proto
|
|
229
|
+
@pool = pool
|
|
230
|
+
@options = []
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def call
|
|
234
|
+
p = @proto
|
|
235
|
+
draft = File.draft(p.name, package: (p.package if p.has_package?), syntax: (p.syntax.to_sym if p.has_syntax?),
|
|
236
|
+
edition: (p.edition if p.has_edition?), dependencies: p.dependency.to_a,
|
|
237
|
+
public_dependencies: p.public_dependency.to_a, weak_dependencies: p.weak_dependency.to_a,
|
|
238
|
+
option_dependencies: p.option_dependency.to_a)
|
|
239
|
+
record(draft.descriptor, p)
|
|
240
|
+
draft.source_info = source_info(p.source_code_info) if p.has_source_code_info?
|
|
241
|
+
p.message_type.each { |m| message(draft, m) }
|
|
242
|
+
p.enum_type.each { |e| enum(draft, e) }
|
|
243
|
+
p.extension.each { |f| extension(draft, f) }
|
|
244
|
+
p.service.each do |s|
|
|
245
|
+
service = draft.service(s.name)
|
|
246
|
+
record(service.descriptor, s)
|
|
247
|
+
s["method"].each do |m|
|
|
248
|
+
record(service.rpc(m.name, m.input_type, m.output_type, client_streaming: m.client_streaming,
|
|
249
|
+
server_streaming: m.server_streaming), m)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
draft.link(@pool)
|
|
253
|
+
lookup = extension_lookup(draft.descriptor)
|
|
254
|
+
@options.each do |descriptor, options|
|
|
255
|
+
draft.set_options(descriptor, MessageValue.decode(options.class.descriptor, options.encode, lookup))
|
|
256
|
+
end
|
|
257
|
+
draft.resolve(@pool).freeze!
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
private
|
|
261
|
+
|
|
262
|
+
def record(descriptor, proto)
|
|
263
|
+
@options << [descriptor, proto.options] if proto.has_options?
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Extensions from the pool, then those the file defines itself
|
|
267
|
+
def extension_lookup(file)
|
|
268
|
+
own = file.all_extensions.to_h { |e| [[e.extendee.full_name, e.number], e] }
|
|
269
|
+
->(extendee, number) { @pool.find_extension(extendee, number) || own[[extendee, number]] }
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def message(scope, proto)
|
|
273
|
+
visibility = Proto.visibility_symbol(proto.visibility) if proto.has_visibility?
|
|
274
|
+
draft = scope.message(proto.name, visibility: visibility)
|
|
275
|
+
record(draft.descriptor, proto)
|
|
276
|
+
oneofs = proto.oneof_decl.map do |o|
|
|
277
|
+
oneof = draft.oneof(o.name)
|
|
278
|
+
record(oneof, o)
|
|
279
|
+
oneof
|
|
280
|
+
end
|
|
281
|
+
proto.field.each { |f| field(draft, f, oneofs) }
|
|
282
|
+
proto.nested_type.each { |m| message(draft, m) }
|
|
283
|
+
proto.enum_type.each { |e| enum(draft, e) }
|
|
284
|
+
proto.extension.each { |f| extension(draft, f) }
|
|
285
|
+
proto.extension_range.each { |r| record(draft.extension_range(r.start..(r.end - 1)), r) }
|
|
286
|
+
proto.reserved_range.each { |r| draft.reserved(r.start..(r.end - 1)) }
|
|
287
|
+
draft.reserved(*proto.reserved_name)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def field(scope, proto, oneofs)
|
|
291
|
+
raise Error, "#{@proto.name}: field #{proto.name} has an extendee" if proto.has_extendee?
|
|
292
|
+
oneof = oneofs[proto.oneof_index] if proto.has_oneof_index?
|
|
293
|
+
raise Error, "#{@proto.name}: #{proto.name} has an invalid oneof_index" if proto.has_oneof_index? && !oneof
|
|
294
|
+
type, type_name, kwargs = field_attributes(proto)
|
|
295
|
+
record(scope.field(proto.name, proto.number, type, type_name, oneof: oneof, **kwargs), proto)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def extension(scope, proto)
|
|
299
|
+
raise Error, "#{@proto.name}: extension #{proto.name} has no extendee" unless proto.has_extendee?
|
|
300
|
+
type, type_name, kwargs = field_attributes(proto)
|
|
301
|
+
record(scope.extension(proto.name, proto.number, type, type_name, extendee: proto.extendee, **kwargs), proto)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def field_attributes(proto)
|
|
305
|
+
type = Field::TYPES.fetch(proto.type) if proto.has_type?
|
|
306
|
+
type_name = proto.type_name if proto.has_type_name?
|
|
307
|
+
kwargs = {label: proto.has_label? ? Field::LABELS.fetch(proto.label) : :optional,
|
|
308
|
+
default: (proto.default_value if proto.has_default_value?),
|
|
309
|
+
json_name: (proto.json_name if proto.has_json_name?), optional: proto.proto3_optional}
|
|
310
|
+
[type, type_name, kwargs]
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def enum(scope, proto)
|
|
314
|
+
visibility = Proto.visibility_symbol(proto.visibility) if proto.has_visibility?
|
|
315
|
+
draft = scope.enum(proto.name, visibility: visibility)
|
|
316
|
+
record(draft.descriptor, proto)
|
|
317
|
+
proto.value.each { |v| record(draft.value(v.name, v.number), v) }
|
|
318
|
+
proto.reserved_range.each { |r| draft.reserved(r.start..r.end) }
|
|
319
|
+
draft.reserved(*proto.reserved_name)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def source_info(proto)
|
|
323
|
+
SourceInfo.new(proto.location.map do |loc|
|
|
324
|
+
SourceInfo::Location.new(path: loc.path.to_a, span: loc.span.to_a,
|
|
325
|
+
leading_comments: (loc.leading_comments if loc.has_leading_comments?),
|
|
326
|
+
trailing_comments: (loc.trailing_comments if loc.has_trailing_comments?),
|
|
327
|
+
leading_detached_comments: loc.leading_detached_comments.to_a)
|
|
328
|
+
end)
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# descriptor.proto's SymbolVisibility for {Message#visibility} and {Enum#visibility}
|
|
333
|
+
# @api private
|
|
334
|
+
def self.visibility(symbol)
|
|
335
|
+
(symbol == :local) ? :VISIBILITY_LOCAL : :VISIBILITY_EXPORT
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# @api private
|
|
339
|
+
def self.visibility_symbol(proto)
|
|
340
|
+
Draft.visibility(proto)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# Message values to and from wire bytes, driven by {Schema::Message}s:
|
|
344
|
+
# fields in number order with extensions among them, unknown fields
|
|
345
|
+
# last; packed repeated fields per features; MessageSet items for
|
|
346
|
+
# `message_set_wire_format` types; proto3 zero values left out.
|
|
347
|
+
# @api private
|
|
348
|
+
module Codec
|
|
349
|
+
module_function
|
|
350
|
+
|
|
351
|
+
def encode(type, value)
|
|
352
|
+
entries = []
|
|
353
|
+
value.fields.each do |name, v|
|
|
354
|
+
field = type.fields_by_name[name.to_s] || raise(Error, "#{type.full_name} has no field #{name}")
|
|
355
|
+
entries << [field, v]
|
|
356
|
+
end
|
|
357
|
+
value.extensions.each { |field, v| entries << [field, v] }
|
|
358
|
+
out = "".b
|
|
359
|
+
w = Wire::Writer.new(out)
|
|
360
|
+
entries.sort_by! { |f, _| f.number }
|
|
361
|
+
entries.each do |field, v|
|
|
362
|
+
if field.extension? && type.message_set_wire_format?
|
|
363
|
+
write_set_item(w, field, v)
|
|
364
|
+
else
|
|
365
|
+
write_field(w, field, v)
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
w.write_raw(value.unknown_fields) unless value.unknown_fields.empty?
|
|
369
|
+
w.flush
|
|
370
|
+
out
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# A MessageSet item: group 1 holding type_id 2 and message 3
|
|
374
|
+
def write_set_item(w, field, value)
|
|
375
|
+
w.write_tag(1, Wire::START_GROUP)
|
|
376
|
+
w.write_tag(2, Wire::VARINT)
|
|
377
|
+
w.write_varint(field.number)
|
|
378
|
+
w.write_tag(3, Wire::LENGTH_DELIMITED)
|
|
379
|
+
w.write_bytes(encode(field.message_type, value))
|
|
380
|
+
w.write_tag(1, Wire::END_GROUP)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def write_field(w, field, value)
|
|
384
|
+
if field.repeated?
|
|
385
|
+
return if value.empty?
|
|
386
|
+
if field.packed?
|
|
387
|
+
body = "".b
|
|
388
|
+
inner = Wire::Writer.new(body)
|
|
389
|
+
value.each { |v| write_value(inner, field, v) }
|
|
390
|
+
inner.flush
|
|
391
|
+
w.write_tag(field.number, Wire::LENGTH_DELIMITED)
|
|
392
|
+
w.write_bytes(body)
|
|
393
|
+
else
|
|
394
|
+
value.each { |v| write_one(w, field, v) }
|
|
395
|
+
end
|
|
396
|
+
else
|
|
397
|
+
return if !field.presence? && zero?(field, value)
|
|
398
|
+
write_one(w, field, value)
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def zero?(field, value)
|
|
403
|
+
case field.type
|
|
404
|
+
when :string, :bytes then value.empty?
|
|
405
|
+
when :bool then !value
|
|
406
|
+
when :enum then value == 0 || (value.is_a?(Symbol) && field.enum_type.value(value.to_s)&.number == 0)
|
|
407
|
+
when :double, :float then value == 0 && !(1.0 / value).negative?
|
|
408
|
+
else value == 0
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def write_one(w, field, value)
|
|
413
|
+
case field.type
|
|
414
|
+
when :message
|
|
415
|
+
w.write_tag(field.number, Wire::LENGTH_DELIMITED)
|
|
416
|
+
w.write_bytes(encode(field.message_type, value))
|
|
417
|
+
when :group
|
|
418
|
+
w.write_tag(field.number, Wire::START_GROUP)
|
|
419
|
+
w.write_raw(encode(field.message_type, value))
|
|
420
|
+
w.write_tag(field.number, Wire::END_GROUP)
|
|
421
|
+
else
|
|
422
|
+
w.write_tag(field.number, field.wire_type)
|
|
423
|
+
write_value(w, field, value)
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def write_value(w, field, v)
|
|
428
|
+
case field.type
|
|
429
|
+
when :int32, :int64, :uint32, :uint64 then w.write_varint(v)
|
|
430
|
+
when :sint32, :sint64 then w.write_zigzag(v)
|
|
431
|
+
when :bool then w.write_varint(v ? 1 : 0)
|
|
432
|
+
when :enum
|
|
433
|
+
number = v.is_a?(Integer) ? v : field.enum_type.value(v.to_s)&.number
|
|
434
|
+
raise Error, "#{v.inspect} is not a value of #{field.enum_type.full_name}" unless number
|
|
435
|
+
w.write_varint(number)
|
|
436
|
+
when :fixed32 then w.write_fixed32(v)
|
|
437
|
+
when :fixed64 then w.write_fixed64(v)
|
|
438
|
+
when :sfixed32 then w.write_sfixed32(v)
|
|
439
|
+
when :sfixed64 then w.write_sfixed64(v)
|
|
440
|
+
when :float then w.write_float(v)
|
|
441
|
+
when :double then w.write_double(v)
|
|
442
|
+
when :string, :bytes then w.write_bytes(v.b)
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def decode(type, bytes, lookup)
|
|
447
|
+
read(Wire::Reader.new(bytes), type, lookup)
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def read(r, type, lookup)
|
|
451
|
+
fields = {}
|
|
452
|
+
extensions = {}
|
|
453
|
+
unknown = nil
|
|
454
|
+
chunks = nil
|
|
455
|
+
until r.eof?
|
|
456
|
+
number, wire_type = r.read_tag
|
|
457
|
+
if number == 1 && wire_type == Wire::START_GROUP && type.message_set_wire_format?
|
|
458
|
+
unknown = read_set_item(r, extensions, type, lookup, unknown)
|
|
459
|
+
next
|
|
460
|
+
end
|
|
461
|
+
field = type.fields_by_number[number]
|
|
462
|
+
field ||= lookup&.call(type.full_name, number) if type.extension_number?(number)
|
|
463
|
+
if field.nil? || !compatible?(field, wire_type)
|
|
464
|
+
start = r.pos
|
|
465
|
+
r.skip_field(number, wire_type)
|
|
466
|
+
unknown = (unknown || "".b) << tag_bytes(number, wire_type) << raw(r, start)
|
|
467
|
+
next
|
|
468
|
+
end
|
|
469
|
+
target = field.extension? ? extensions : fields
|
|
470
|
+
key = field.extension? ? field : field.name.to_sym
|
|
471
|
+
if field.type == :message || field.type == :group
|
|
472
|
+
body = if field.type == :group
|
|
473
|
+
start = r.pos
|
|
474
|
+
r.skip_group(number)
|
|
475
|
+
group_body(r, start, number)
|
|
476
|
+
else
|
|
477
|
+
r.read_bytes
|
|
478
|
+
end
|
|
479
|
+
if field.repeated?
|
|
480
|
+
(target[key] ||= []) << decode(field.message_type, body, lookup)
|
|
481
|
+
else
|
|
482
|
+
chunks ||= {}
|
|
483
|
+
(chunks[[target, key, field]] ||= +"".b) << body
|
|
484
|
+
end
|
|
485
|
+
elsif field.repeated?
|
|
486
|
+
list = (target[key] ||= [])
|
|
487
|
+
if wire_type == Wire::LENGTH_DELIMITED && field.packable?
|
|
488
|
+
packed = Wire::Reader.new(r.read_bytes)
|
|
489
|
+
list << read_value(packed, field) until packed.eof?
|
|
490
|
+
else
|
|
491
|
+
list << read_value(r, field)
|
|
492
|
+
end
|
|
493
|
+
else
|
|
494
|
+
target[key] = read_value(r, field)
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
chunks&.each do |(target, key, field), body|
|
|
498
|
+
target[key] = decode(field.message_type, body, lookup)
|
|
499
|
+
end
|
|
500
|
+
MessageValue.new(fields, extensions, unknown || MessageValue::EMPTY_BYTES)
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
# One MessageSet item; kept whole as an unknown group when its
|
|
504
|
+
# extension is not known
|
|
505
|
+
def read_set_item(r, extensions, type, lookup, unknown)
|
|
506
|
+
body_start = r.pos
|
|
507
|
+
r.skip_group(1)
|
|
508
|
+
item = Wire::Reader.new(group_body(r, body_start, 1))
|
|
509
|
+
type_id = message = nil
|
|
510
|
+
until item.eof?
|
|
511
|
+
n, wt = item.read_tag
|
|
512
|
+
if n == 2 && wt == Wire::VARINT
|
|
513
|
+
type_id = item.read_varint
|
|
514
|
+
elsif n == 3 && wt == Wire::LENGTH_DELIMITED
|
|
515
|
+
message = item.read_bytes
|
|
516
|
+
else
|
|
517
|
+
item.skip_field(n, wt)
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
field = type_id && message && lookup&.call(type.full_name, type_id)
|
|
521
|
+
if field && field.type == :message
|
|
522
|
+
extensions[field] = decode(field.message_type, message, lookup)
|
|
523
|
+
unknown
|
|
524
|
+
else
|
|
525
|
+
(unknown || "".b) << tag_bytes(1, Wire::START_GROUP) << raw(r, body_start)
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
def compatible?(field, wire_type)
|
|
530
|
+
return true if wire_type == field.wire_type
|
|
531
|
+
wire_type == Wire::LENGTH_DELIMITED && field.packable?
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
def tag_bytes(number, wire_type)
|
|
535
|
+
out = "".b
|
|
536
|
+
w = Wire::Writer.new(out)
|
|
537
|
+
w.write_tag(number, wire_type)
|
|
538
|
+
w.flush
|
|
539
|
+
out
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def raw(r, start)
|
|
543
|
+
r.buffer.byteslice(start, r.pos - start).b
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
def group_body(r, start, number)
|
|
547
|
+
all = raw(r, start)
|
|
548
|
+
all.byteslice(0, all.bytesize - Wire.varint_size(Wire.tag(number, Wire::END_GROUP)))
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
def read_value(r, field)
|
|
552
|
+
case field.type
|
|
553
|
+
when :int32 then r.read_int32
|
|
554
|
+
when :int64 then r.read_int64
|
|
555
|
+
when :uint32 then r.read_uint32
|
|
556
|
+
when :uint64 then r.read_varint
|
|
557
|
+
when :sint32 then r.read_sint32
|
|
558
|
+
when :sint64 then r.read_sint64
|
|
559
|
+
when :bool then r.read_varint != 0
|
|
560
|
+
when :enum
|
|
561
|
+
number = r.read_int32
|
|
562
|
+
field.enum_type.values_by_number[number]&.name&.to_sym || number
|
|
563
|
+
when :fixed32 then r.read_fixed32
|
|
564
|
+
when :fixed64 then r.read_fixed64
|
|
565
|
+
when :sfixed32 then r.read_sfixed32
|
|
566
|
+
when :sfixed64 then r.read_sfixed64
|
|
567
|
+
when :float then r.read_float
|
|
568
|
+
when :double then r.read_double
|
|
569
|
+
when :string then r.read_bytes.force_encoding(Encoding::UTF_8)
|
|
570
|
+
when :bytes then r.read_bytes.b
|
|
571
|
+
end
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
module Schema
|
|
5
|
+
# protoc's C++-style scoped name lookup (`DescriptorBuilder::LookupSymbol`),
|
|
6
|
+
# as `FieldDescriptorProto.type_name` in descriptor.proto describes it.
|
|
7
|
+
#
|
|
8
|
+
# A name with a leading dot is fully qualified. Otherwise the *first component*
|
|
9
|
+
# is looked up in the innermost scope, then each enclosing scope out to the
|
|
10
|
+
# root; once it is found (as something that can contain names), the rest of
|
|
11
|
+
# the name must resolve beneath it or the lookup fails without searching
|
|
12
|
+
# further out. Only symbols of the file itself, its direct imports and
|
|
13
|
+
# whatever those re-export through `import public` are visible.
|
|
14
|
+
module Resolver
|
|
15
|
+
# Stands for "this name is a package" during lookup
|
|
16
|
+
PACKAGE = Object.new.freeze
|
|
17
|
+
|
|
18
|
+
# @param name [String] the name as written, e.g. "Foo.Bar" or ".pkg.Foo.Bar"
|
|
19
|
+
# @param scope [String] full name of the enclosing message, or the package ("" for none)
|
|
20
|
+
# @param file [File] the file doing the lookup; it may still be under construction
|
|
21
|
+
# @param pool [Pool, nil] consulted only to explain a failure ("not imported")
|
|
22
|
+
# @param types_only [Boolean] when a simple (one-component) name finds something
|
|
23
|
+
# that is not a message or enum, keep searching outward, as protoc does for
|
|
24
|
+
# type names. Compound names return whatever they reach.
|
|
25
|
+
# @return [Descriptor]
|
|
26
|
+
# @raise [ResolveError]
|
|
27
|
+
def self.resolve(name, scope:, file:, pool: nil, types_only: true)
|
|
28
|
+
if !name.is_a?(String) || name.empty? || name == "."
|
|
29
|
+
raise ResolveError.new("#{name.inspect} is not a valid name", name: name, scope: scope)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if name.start_with?(".")
|
|
33
|
+
found = find(name[1..], file)
|
|
34
|
+
return found if found && !found.equal?(PACKAGE)
|
|
35
|
+
raise not_defined(name, scope, file, pool)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
dot = name.index(".")
|
|
39
|
+
first = dot ? name[0, dot] : name
|
|
40
|
+
prefix = scope
|
|
41
|
+
loop do
|
|
42
|
+
candidate = prefix.empty? ? first : "#{prefix}.#{first}"
|
|
43
|
+
found = find(candidate, file)
|
|
44
|
+
if found
|
|
45
|
+
if dot
|
|
46
|
+
if aggregate?(found)
|
|
47
|
+
full = prefix.empty? ? name : "#{prefix}.#{name}"
|
|
48
|
+
inner = find(full, file)
|
|
49
|
+
return inner if inner && !inner.equal?(PACKAGE)
|
|
50
|
+
raise ResolveError.new(
|
|
51
|
+
"\"#{name}\" is resolved to \"#{full}\", which is not defined. The innermost scope is " \
|
|
52
|
+
"searched first in name resolution. Consider using a leading '.'(i.e., \".#{name}\") " \
|
|
53
|
+
"to start from the outermost scope (scope \"#{scope}\" in #{file.name})",
|
|
54
|
+
name: name, scope: scope
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
elsif !found.equal?(PACKAGE) && (!types_only || type?(found))
|
|
58
|
+
return found
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
break if prefix.empty?
|
|
62
|
+
cut = prefix.rindex(".")
|
|
63
|
+
prefix = cut ? prefix[0, cut] : ""
|
|
64
|
+
end
|
|
65
|
+
raise not_defined(name, scope, file, pool)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# A visible symbol by full name, {PACKAGE} for a visible package, or nil
|
|
69
|
+
# @api private
|
|
70
|
+
def self.find(full_name, file)
|
|
71
|
+
visible = file.visible_files
|
|
72
|
+
visible.each do |f|
|
|
73
|
+
found = f.symbols[full_name]
|
|
74
|
+
return found if found
|
|
75
|
+
end
|
|
76
|
+
visible.each do |f|
|
|
77
|
+
return PACKAGE if in_package?(f.package, full_name)
|
|
78
|
+
end
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @api private
|
|
83
|
+
def self.in_package?(package, name)
|
|
84
|
+
package == name || (package.start_with?(name) && package.getbyte(name.bytesize) == 46) # "."
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @api private
|
|
88
|
+
def self.type?(found)
|
|
89
|
+
found.is_a?(Message) || found.is_a?(Enum)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @api private
|
|
93
|
+
def self.aggregate?(found)
|
|
94
|
+
found.equal?(PACKAGE) || found.is_a?(Message) || found.is_a?(Enum) || found.is_a?(Service)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.not_defined(name, scope, file, pool)
|
|
98
|
+
message = "\"#{name}\" is not defined (scope \"#{scope}\" in #{file.name})"
|
|
99
|
+
if pool
|
|
100
|
+
elsewhere = candidates(name, scope).lazy.map { |c| pool.find_symbol(c) }.find(&:itself)
|
|
101
|
+
if elsewhere
|
|
102
|
+
message = "\"#{elsewhere.full_name}\" seems to be defined in \"#{elsewhere.file.name}\", which is " \
|
|
103
|
+
"not imported by \"#{file.name}\". To use it here, please add the necessary import."
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
ResolveError.new(message, name: name, scope: scope)
|
|
107
|
+
end
|
|
108
|
+
private_class_method :not_defined
|
|
109
|
+
|
|
110
|
+
def self.candidates(name, scope)
|
|
111
|
+
return [name[1..]] if name.start_with?(".")
|
|
112
|
+
parts = scope.empty? ? [] : scope.split(".")
|
|
113
|
+
parts.size.downto(0).map { |i| (parts.first(i) + [name]).join(".") }
|
|
114
|
+
end
|
|
115
|
+
private_class_method :candidates
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|