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,367 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
module Schema
|
|
5
|
+
# A message-typed value in options: an options message itself, or a
|
|
6
|
+
# message inside one (a `FeatureSet`, a custom option of a message type).
|
|
7
|
+
# Fields are kept by name, extensions by their {Field}, and fields nobody
|
|
8
|
+
# could interpret as their wire bytes.
|
|
9
|
+
#
|
|
10
|
+
# Values are Ruby values: Integers, Floats, Strings (binary for bytes),
|
|
11
|
+
# true and false, enum values as Symbols (an Integer for a number the enum
|
|
12
|
+
# does not name), repeated fields as frozen Arrays, messages as
|
|
13
|
+
# MessageValues. Map fields are repeated entry messages.
|
|
14
|
+
class MessageValue
|
|
15
|
+
# @return [Hash{Field => Object}] extension values by extension descriptor
|
|
16
|
+
attr_reader :extensions
|
|
17
|
+
# @return [String] binary, wire-format bytes of fields that could not be interpreted
|
|
18
|
+
attr_reader :unknown_fields
|
|
19
|
+
|
|
20
|
+
# @param fields [Hash{Symbol => Object}]
|
|
21
|
+
# @param extensions [Hash{Field => Object}]
|
|
22
|
+
# @param unknown_fields [String] binary
|
|
23
|
+
def initialize(fields = Freezer::EMPTY_HASH, extensions = Freezer::EMPTY_HASH, unknown_fields = EMPTY_BYTES)
|
|
24
|
+
@fields = frozen(fields)
|
|
25
|
+
@extensions = frozen(extensions)
|
|
26
|
+
@unknown_fields = if unknown_fields.empty?
|
|
27
|
+
EMPTY_BYTES
|
|
28
|
+
else
|
|
29
|
+
unknown_fields.frozen? ? unknown_fields : unknown_fields.b.freeze
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @param name [Symbol, String] a field of the message
|
|
34
|
+
# @return [Object, nil] its value, nil when not set
|
|
35
|
+
def [](name)
|
|
36
|
+
@fields[name.to_sym]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [Boolean] whether the field is set
|
|
40
|
+
def key?(name)
|
|
41
|
+
@fields.key?(name.to_sym)
|
|
42
|
+
end
|
|
43
|
+
alias_method :set?, :key?
|
|
44
|
+
|
|
45
|
+
# @return [Hash{Symbol => Object}] the fields that are set, by name, in the order they were given
|
|
46
|
+
attr_reader :fields
|
|
47
|
+
|
|
48
|
+
# @param key [Field, String] an extension descriptor or its full name
|
|
49
|
+
# @return [Object, nil] the extension's value
|
|
50
|
+
def extension(key)
|
|
51
|
+
return @extensions[key] if key.is_a?(Field)
|
|
52
|
+
name = key.to_s.delete_prefix(".")
|
|
53
|
+
@extensions.each { |field, value| return value if field.full_name == name }
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The same value with every extension, here and in nested values, looked
|
|
58
|
+
# up again by full name; one that is not found stays as it was. A copy
|
|
59
|
+
# of a file in another pool keys its options by that pool's extensions.
|
|
60
|
+
# @api private
|
|
61
|
+
# @param extension [#call] full name to extension {Field}
|
|
62
|
+
# @return [MessageValue]
|
|
63
|
+
def rekey(extension)
|
|
64
|
+
changed = false
|
|
65
|
+
again = lambda do |v|
|
|
66
|
+
case v
|
|
67
|
+
when MessageValue
|
|
68
|
+
found = v.rekey(extension)
|
|
69
|
+
changed ||= !found.equal?(v)
|
|
70
|
+
found
|
|
71
|
+
when Array then v.map(&again)
|
|
72
|
+
else v
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
fields = @fields.transform_values(&again)
|
|
76
|
+
extensions = @extensions.to_h do |field, v|
|
|
77
|
+
key = extension.call(field.full_name) || field
|
|
78
|
+
changed ||= !key.equal?(field)
|
|
79
|
+
[key, again.call(v)]
|
|
80
|
+
end
|
|
81
|
+
changed ? self.class.new(fields, extensions, @unknown_fields) : self
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @return [Boolean] nothing set, not even unknown fields
|
|
85
|
+
def empty?
|
|
86
|
+
@fields.empty? && @extensions.empty? && @unknown_fields.empty?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Protobuf merge semantics: `other`'s fields win, nested messages merge,
|
|
90
|
+
# repeated fields concatenate
|
|
91
|
+
# @param other [MessageValue, nil]
|
|
92
|
+
# @return [MessageValue]
|
|
93
|
+
def merge(other)
|
|
94
|
+
return self if other.nil? || other.empty?
|
|
95
|
+
return other if empty? && other.instance_of?(self.class)
|
|
96
|
+
fields = MessageValue.merge_values(@fields, other.fields)
|
|
97
|
+
extensions = MessageValue.merge_values(@extensions, other.extensions)
|
|
98
|
+
unknown = @unknown_fields.empty? ? other.unknown_fields : (@unknown_fields + other.unknown_fields).freeze
|
|
99
|
+
self.class.new(fields, extensions, unknown)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @api private
|
|
103
|
+
def self.merge_values(base, overlay)
|
|
104
|
+
return base if overlay.empty?
|
|
105
|
+
return overlay if base.empty?
|
|
106
|
+
base.merge(overlay) do |_key, old, new|
|
|
107
|
+
if old.is_a?(MessageValue) && new.is_a?(MessageValue)
|
|
108
|
+
old.merge(new)
|
|
109
|
+
elsif old.is_a?(Array) && new.is_a?(Array)
|
|
110
|
+
(old + new).freeze
|
|
111
|
+
else
|
|
112
|
+
new
|
|
113
|
+
end
|
|
114
|
+
end.freeze
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def ==(other)
|
|
118
|
+
other.instance_of?(self.class) && other.fields == @fields && other.extensions == @extensions &&
|
|
119
|
+
other.unknown_fields == @unknown_fields
|
|
120
|
+
end
|
|
121
|
+
alias_method :eql?, :==
|
|
122
|
+
|
|
123
|
+
def hash
|
|
124
|
+
[self.class, @fields, @extensions, @unknown_fields].hash
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# A plain Hash for reading: fields by name, extensions under their full
|
|
128
|
+
# name in parentheses, nested messages as Hashes
|
|
129
|
+
# @return [Hash]
|
|
130
|
+
def to_h
|
|
131
|
+
h = @fields.transform_values { |v| MessageValue.plain(v) }
|
|
132
|
+
@extensions.each { |field, v| h[:"(#{field.full_name})"] = MessageValue.plain(v) }
|
|
133
|
+
h[:unknown_fields] = @unknown_fields unless @unknown_fields.empty?
|
|
134
|
+
h
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# @api private
|
|
138
|
+
def self.plain(value)
|
|
139
|
+
case value
|
|
140
|
+
when MessageValue then value.to_h
|
|
141
|
+
when Array then value.map { |v| plain(v) }
|
|
142
|
+
else value
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def inspect
|
|
147
|
+
parts = @fields.map { |k, v| "#{k}: #{v.inspect}" }
|
|
148
|
+
@extensions.each { |f, v| parts << "(#{f.full_name}): #{v.inspect}" }
|
|
149
|
+
parts << "unknown_fields: #{@unknown_fields.bytesize} bytes" unless @unknown_fields.empty?
|
|
150
|
+
parts.empty? ? "#<#{self.class.name}>" : "#<#{self.class.name} #{parts.join(", ")}>"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
def frozen(hash)
|
|
156
|
+
return Freezer::EMPTY_HASH if hash.empty?
|
|
157
|
+
hash.frozen? ? hash : hash.dup.freeze
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
public
|
|
161
|
+
|
|
162
|
+
EMPTY_BYTES = "".b.freeze
|
|
163
|
+
EMPTY = new.freeze
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# The declared options of a descriptor: a message of descriptor.proto's
|
|
167
|
+
# `FileOptions`, `FieldOptions` and so on, one subclass each. Every field
|
|
168
|
+
# of that message has a reader (booleans also a `?` form); custom options
|
|
169
|
+
# are {#extension} values; {#features} is what the descriptor resolves to.
|
|
170
|
+
#
|
|
171
|
+
# field.options.deprecated? # => true
|
|
172
|
+
# field.options.packed # => nil when not set
|
|
173
|
+
# file.options.java_package # => "com.acme"
|
|
174
|
+
# message.options.extension("acme.limits") # => #<Protostar::Schema::MessageValue max: 10>
|
|
175
|
+
# field.options[:features] # => the FeatureSet as declared
|
|
176
|
+
# field.options.features # => #<Protostar::Schema::Features ...> resolved
|
|
177
|
+
#
|
|
178
|
+
# A reader answers what was declared: nil (or an empty Array) when the
|
|
179
|
+
# option was not set, not descriptor.proto's default.
|
|
180
|
+
class Options < MessageValue
|
|
181
|
+
TYPE_NAME = nil
|
|
182
|
+
FIELDS = Freezer::EMPTY_ARRAY
|
|
183
|
+
|
|
184
|
+
class << self
|
|
185
|
+
# @return [String] full name of the options message, e.g. "google.protobuf.FieldOptions"
|
|
186
|
+
def type_name
|
|
187
|
+
self::TYPE_NAME
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# @api private
|
|
191
|
+
def define(type_name, *names, booleans: [], repeated: [])
|
|
192
|
+
klass = Class.new(self)
|
|
193
|
+
klass.const_set(:TYPE_NAME, -"google.protobuf.#{type_name}")
|
|
194
|
+
names.each do |name|
|
|
195
|
+
if repeated.include?(name)
|
|
196
|
+
klass.define_method(name) { @fields.fetch(name, Freezer::EMPTY_ARRAY) }
|
|
197
|
+
else
|
|
198
|
+
klass.define_method(name) { @fields[name] }
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
booleans.each do |name|
|
|
202
|
+
klass.define_method(name) { @fields[name] }
|
|
203
|
+
klass.define_method(:"#{name}?") { @fields[name] == true }
|
|
204
|
+
end
|
|
205
|
+
klass.const_set(:FIELDS, (names + booleans + [:features]).freeze)
|
|
206
|
+
klass
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Options built from what a draft was given: nil, an Options or
|
|
210
|
+
# MessageValue, or a Hash of field values by name with custom options
|
|
211
|
+
# under `extensions:` (by full name or {Field}) and wire bytes under
|
|
212
|
+
# `unknown_fields:`. Message values may be Hashes in the same spelling.
|
|
213
|
+
# @api private
|
|
214
|
+
# @param spec [Hash, MessageValue, nil]
|
|
215
|
+
# @param extension [#call] full name to extension {Field}
|
|
216
|
+
# @return [Options, nil] unresolved (no {#features})
|
|
217
|
+
def build(spec, extension, owner)
|
|
218
|
+
case spec
|
|
219
|
+
when nil then nil
|
|
220
|
+
when MessageValue
|
|
221
|
+
value = spec.rekey(extension)
|
|
222
|
+
new(value.fields, value.extensions, value.unknown_fields, declared: true)
|
|
223
|
+
when Hash
|
|
224
|
+
value = MessageValue.from_hash(spec, extension, owner)
|
|
225
|
+
new(value.fields, value.extensions, value.unknown_fields, declared: true)
|
|
226
|
+
else raise Error, "#{owner}: options must be a Hash, got #{spec.class}"
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# @param features [Features, nil] resolved features of the descriptor
|
|
232
|
+
# @param declared [Boolean] whether the descriptor carries an options message, even an empty one
|
|
233
|
+
def initialize(fields = Freezer::EMPTY_HASH, extensions = Freezer::EMPTY_HASH, unknown_fields = EMPTY_BYTES,
|
|
234
|
+
features: nil, declared: false)
|
|
235
|
+
super(fields, extensions, unknown_fields)
|
|
236
|
+
@features = features
|
|
237
|
+
@declared = declared
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# @return [Features, nil] the features the descriptor resolves to, nil before resolution
|
|
241
|
+
attr_reader :features
|
|
242
|
+
|
|
243
|
+
# @api private
|
|
244
|
+
# @return [Boolean] whether the descriptor carries an options message (protoc writes `options {}`
|
|
245
|
+
# for one declared empty)
|
|
246
|
+
def declared?
|
|
247
|
+
@declared
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# @api private
|
|
251
|
+
# @return [Options] the same options, bound to resolved features
|
|
252
|
+
def with_features(features)
|
|
253
|
+
self.class.new(@fields, @extensions, @unknown_fields, features: features, declared: @declared)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# @return [String] e.g. "google.protobuf.FieldOptions"
|
|
257
|
+
def type_name
|
|
258
|
+
self.class.type_name
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def inspect
|
|
262
|
+
@declared ? super : "#<#{self.class.name} (none declared)>"
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Returned by descriptors before they are resolved
|
|
266
|
+
EMPTY = new.freeze
|
|
267
|
+
|
|
268
|
+
# These options as the descriptor.proto message they are (a
|
|
269
|
+
# `Protostar::Google::Protobuf::FieldOptions`, say), custom options as
|
|
270
|
+
# extension bytes the message keeps as unknown fields
|
|
271
|
+
# @return [Protostar::Message]
|
|
272
|
+
def to_proto
|
|
273
|
+
klass = Proto.types.const_get(type_name.delete_prefix("google.protobuf."))
|
|
274
|
+
klass.decode(encode(klass.descriptor))
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
FileOptions = Options.define("FileOptions", :java_package, :java_outer_classname, :optimize_for, :go_package,
|
|
279
|
+
:objc_class_prefix, :csharp_namespace, :swift_prefix, :php_class_prefix, :php_namespace,
|
|
280
|
+
:php_metadata_namespace, :ruby_package,
|
|
281
|
+
booleans: %i[java_multiple_files java_generate_equals_and_hash java_string_check_utf8 cc_generic_services
|
|
282
|
+
java_generic_services py_generic_services deprecated cc_enable_arenas])
|
|
283
|
+
MessageOptions = Options.define("MessageOptions",
|
|
284
|
+
booleans: %i[message_set_wire_format no_standard_descriptor_accessor deprecated map_entry
|
|
285
|
+
deprecated_legacy_json_field_conflicts])
|
|
286
|
+
FieldOptions = Options.define("FieldOptions", :ctype, :jstype, :retention, :targets, :edition_defaults,
|
|
287
|
+
:feature_support, booleans: %i[packed lazy unverified_lazy deprecated weak debug_redact],
|
|
288
|
+
repeated: %i[targets edition_defaults])
|
|
289
|
+
OneofOptions = Options.define("OneofOptions")
|
|
290
|
+
EnumOptions = Options.define("EnumOptions",
|
|
291
|
+
booleans: %i[allow_alias deprecated deprecated_legacy_json_field_conflicts])
|
|
292
|
+
EnumValueOptions = Options.define("EnumValueOptions", :feature_support, booleans: %i[deprecated debug_redact])
|
|
293
|
+
ServiceOptions = Options.define("ServiceOptions", booleans: %i[deprecated])
|
|
294
|
+
MethodOptions = Options.define("MethodOptions", :idempotency_level, booleans: %i[deprecated])
|
|
295
|
+
ExtensionRangeOptions = Options.define("ExtensionRangeOptions", :declaration, :verification,
|
|
296
|
+
repeated: %i[declaration])
|
|
297
|
+
|
|
298
|
+
class MessageValue
|
|
299
|
+
# A value from a Hash: fields by name, `extensions:` by full name or
|
|
300
|
+
# {Field}, `unknown_fields:` as bytes. A field named `extensions` or
|
|
301
|
+
# `unknown_fields` takes a String key. `__extensions:` and
|
|
302
|
+
# `__unknown_fields:` (as `[number, wire_type, bytes]` triples) are the
|
|
303
|
+
# deprecated spellings.
|
|
304
|
+
# @api private
|
|
305
|
+
def self.from_hash(hash, extension, owner)
|
|
306
|
+
fields = {}
|
|
307
|
+
extensions = {}
|
|
308
|
+
unknown = EMPTY_BYTES
|
|
309
|
+
hash.each do |key, value|
|
|
310
|
+
case key
|
|
311
|
+
when :extensions, :__extensions
|
|
312
|
+
Protostar.deprecated("__extensions:", "extensions:") if key == :__extensions
|
|
313
|
+
value.each do |name, v|
|
|
314
|
+
field = name.is_a?(Field) ? name : extension.call(name.to_s.delete_prefix("."))
|
|
315
|
+
raise Error, "#{owner}: option extension #{name} is not defined here or in an import" unless field
|
|
316
|
+
extensions[field] = from_value(v, extension, owner)
|
|
317
|
+
end
|
|
318
|
+
when :unknown_fields then unknown = value.b.freeze
|
|
319
|
+
when :__unknown_fields
|
|
320
|
+
Protostar.deprecated("__unknown_fields: [[number, wire_type, bytes]]", "unknown_fields: \"bytes\".b")
|
|
321
|
+
unknown = unknown_bytes(value)
|
|
322
|
+
else fields[key.to_sym] = from_value(value, extension, owner)
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
new(fields.freeze, extensions.freeze, unknown)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# @api private
|
|
329
|
+
def self.from_value(value, extension, owner)
|
|
330
|
+
case value
|
|
331
|
+
when MessageValue then value
|
|
332
|
+
when Hash then from_hash(value, extension, owner)
|
|
333
|
+
when Array then value.map { |v| from_value(v, extension, owner) }.freeze
|
|
334
|
+
else Freezer.call(value)
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# @api private
|
|
339
|
+
def self.unknown_bytes(triples)
|
|
340
|
+
out = "".b
|
|
341
|
+
triples.each do |number, wire_type, bytes|
|
|
342
|
+
w = Wire::Writer.new(out)
|
|
343
|
+
w.write_tag(number, wire_type)
|
|
344
|
+
w.flush
|
|
345
|
+
out << bytes.b
|
|
346
|
+
end
|
|
347
|
+
out.freeze
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# This value in wire format
|
|
351
|
+
# @param type [Message] the message type it is a value of
|
|
352
|
+
# @return [String] binary
|
|
353
|
+
def encode(type)
|
|
354
|
+
Proto::Codec.encode(type, self)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# @param type [Message]
|
|
358
|
+
# @param bytes [String]
|
|
359
|
+
# @param extensions [#call, nil] (extendee full name, number) to an extension {Field};
|
|
360
|
+
# fields it does not know stay in {#unknown_fields}
|
|
361
|
+
# @return [MessageValue]
|
|
362
|
+
def self.decode(type, bytes, extensions = nil)
|
|
363
|
+
Proto::Codec.decode(type, bytes, extensions)
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
module Schema
|
|
5
|
+
# A registry of built files, safe to use from several threads. The pool
|
|
6
|
+
# itself is mutable and not shareable between Ractors; the files it holds are.
|
|
7
|
+
class Pool
|
|
8
|
+
def initialize
|
|
9
|
+
@mutex = Mutex.new
|
|
10
|
+
@files = {}
|
|
11
|
+
@symbols = {}
|
|
12
|
+
@packages = {}
|
|
13
|
+
@extensions = {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Registers a built file and everything it defines.
|
|
17
|
+
#
|
|
18
|
+
# An extension whose extendee and number another file already uses is
|
|
19
|
+
# accepted with a warning, as protoc does; {#find_extension} keeps
|
|
20
|
+
# returning the first one.
|
|
21
|
+
# @param file [File]
|
|
22
|
+
# @param warn [Boolean] write that warning with `Kernel#warn`; the
|
|
23
|
+
# compiler passes false and reports it as a diagnostic instead
|
|
24
|
+
# @return [File]
|
|
25
|
+
# @raise [Error] when the file name is taken, a symbol or package it
|
|
26
|
+
# defines clashes with one already in the pool, or the file itself uses
|
|
27
|
+
# an extension number twice
|
|
28
|
+
def add(file, warn: true)
|
|
29
|
+
raise Error, "expected a #{File.name}, got #{file.class}" unless file.is_a?(File)
|
|
30
|
+
raise Error, "#{file.name} is not frozen; finish its draft with freeze!" unless file.frozen?
|
|
31
|
+
taken = @mutex.synchronize do
|
|
32
|
+
check_clashes(file)
|
|
33
|
+
@files[file.name] = file
|
|
34
|
+
file.symbols.each { |name, symbol| @symbols[name] = symbol }
|
|
35
|
+
package_prefixes(file.package).each { |pkg| @packages[pkg] ||= file }
|
|
36
|
+
file.all_extensions.filter_map do |ext|
|
|
37
|
+
by_number = (@extensions[ext.extendee.full_name] ||= {})
|
|
38
|
+
existing = by_number[ext.number]
|
|
39
|
+
by_number[ext.number] = ext unless existing
|
|
40
|
+
[ext, existing] if existing
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
if warn
|
|
44
|
+
taken.each do |ext, existing|
|
|
45
|
+
Kernel.warn("#{file.name}: Extension number #{ext.number} has already been used in " \
|
|
46
|
+
"\"#{ext.extendee.full_name}\" by extension \"#{existing.full_name}\" defined in #{existing.file.name}.")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
file
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [File, nil]
|
|
53
|
+
def file(name)
|
|
54
|
+
@mutex.synchronize { @files[name] }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [Boolean] whether a file of that name is registered
|
|
58
|
+
def include?(file_name)
|
|
59
|
+
@mutex.synchronize { @files.key?(file_name) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @return [Array<File>] in the order they were added
|
|
63
|
+
def files
|
|
64
|
+
@mutex.synchronize { @files.values }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @param full_name [String] without a leading dot
|
|
68
|
+
# @return [Descriptor, nil] any symbol: message, field, oneof, enum, enum value, service or method
|
|
69
|
+
def find_symbol(full_name)
|
|
70
|
+
@mutex.synchronize { @symbols[full_name] }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @return [Message, nil]
|
|
74
|
+
def find_message(full_name)
|
|
75
|
+
found = find_symbol(full_name)
|
|
76
|
+
found if found.is_a?(Message)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @return [Enum, nil]
|
|
80
|
+
def find_enum(full_name)
|
|
81
|
+
found = find_symbol(full_name)
|
|
82
|
+
found if found.is_a?(Enum)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @return [Service, nil]
|
|
86
|
+
def find_service(full_name)
|
|
87
|
+
found = find_symbol(full_name)
|
|
88
|
+
found if found.is_a?(Service)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @param extendee [String, Message] the extended message or its full name
|
|
92
|
+
# @param number [Integer]
|
|
93
|
+
# @return [Field, nil]
|
|
94
|
+
def find_extension(extendee, number)
|
|
95
|
+
extendee = extendee.full_name if extendee.is_a?(Message)
|
|
96
|
+
@mutex.synchronize { @extensions[extendee]&.[](number) }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @param extendee [String, Message]
|
|
100
|
+
# @return [Array<Field>] every registered extension of the message, by number
|
|
101
|
+
def extensions_of(extendee)
|
|
102
|
+
extendee = extendee.full_name if extendee.is_a?(Message)
|
|
103
|
+
@mutex.synchronize { (@extensions[extendee] || {}).values.sort_by(&:number) }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def inspect
|
|
107
|
+
"#<#{self.class.name} #{@files.size} files>"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Builds and adds a serialized FileDescriptorProto, unless a file of that name is here
|
|
111
|
+
# @return [File]
|
|
112
|
+
def add_serialized_file(bytes)
|
|
113
|
+
proto = Proto.types::FileDescriptorProto.decode(bytes)
|
|
114
|
+
file(proto.name) || add(File.from_proto(proto, pool: self))
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# @see Protostar.load_descriptor_set
|
|
118
|
+
def load_descriptor_set(bytes)
|
|
119
|
+
Protostar.load_descriptor_set(bytes, pool: self)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def check_clashes(file)
|
|
125
|
+
raise Error, "file \"#{file.name}\" is already in the pool" if @files.key?(file.name)
|
|
126
|
+
file.symbols.each do |name, symbol|
|
|
127
|
+
if (other = @symbols[name])
|
|
128
|
+
raise Error, "\"#{name}\" is already defined in file \"#{other.file.name}\" (redefined in \"#{file.name}\")"
|
|
129
|
+
end
|
|
130
|
+
if (pkg_file = @packages[name])
|
|
131
|
+
raise Error, "\"#{name}\" is already defined as a package in file \"#{pkg_file.name}\" " \
|
|
132
|
+
"(redefined as a #{symbol.class.name.split("::").last} in \"#{file.name}\")"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
package_prefixes(file.package).each do |pkg|
|
|
136
|
+
other = @symbols[pkg] || file.symbols[pkg]
|
|
137
|
+
next unless other
|
|
138
|
+
raise Error, "\"#{pkg}\" is already defined (as something other than a package) in file \"#{other.file.name}\""
|
|
139
|
+
end
|
|
140
|
+
seen = {}
|
|
141
|
+
file.all_extensions.each do |ext|
|
|
142
|
+
key = [ext.extendee.full_name, ext.number]
|
|
143
|
+
if (existing = seen[key])
|
|
144
|
+
raise Error, "extension number #{ext.number} of \"#{ext.extendee.full_name}\" is already used by " \
|
|
145
|
+
"\"#{existing.full_name}\" (redefined by \"#{ext.full_name}\")"
|
|
146
|
+
end
|
|
147
|
+
seen[key] = ext
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def package_prefixes(package)
|
|
152
|
+
return [] if package.empty?
|
|
153
|
+
parts = package.split(".")
|
|
154
|
+
(1..parts.size).map { |n| parts.first(n).join(".") }
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
module Protostar
|
|
161
|
+
@generated_pool = Schema::Pool.new
|
|
162
|
+
|
|
163
|
+
class << self
|
|
164
|
+
# @return [Schema::Pool] the process-wide pool generated `_pb.rb` files build into
|
|
165
|
+
attr_reader :generated_pool
|
|
166
|
+
|
|
167
|
+
# Builds every file of a serialized `FileDescriptorSet` into a pool, in
|
|
168
|
+
# order. Custom options are decoded against the extensions the pool (and
|
|
169
|
+
# the file itself) defines; files already in the pool are taken from it.
|
|
170
|
+
# @param bytes [String] a serialized google.protobuf.FileDescriptorSet
|
|
171
|
+
# @param pool [Schema::Pool]
|
|
172
|
+
# @return [Array<Schema::File>]
|
|
173
|
+
def load_descriptor_set(bytes, pool:)
|
|
174
|
+
Schema::Proto.each_file(bytes).map { |file_bytes| pool.add_serialized_file(file_bytes) }
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
Pool = Schema::Pool
|
|
179
|
+
end
|