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.
Files changed (223) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +60 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +647 -0
  5. data/THIRD_PARTY_NOTICES.md +61 -0
  6. data/exe/protoc-gen-protostar +7 -0
  7. data/exe/protostar +7 -0
  8. data/lib/protostar/builder/generator.rb +71 -0
  9. data/lib/protostar/builder/sink.rb +35 -0
  10. data/lib/protostar/builder.rb +965 -0
  11. data/lib/protostar/cli.rb +267 -0
  12. data/lib/protostar/codec/decode_generator.rb +199 -0
  13. data/lib/protostar/codec/decoder.rb +296 -0
  14. data/lib/protostar/codec/encode_generator.rb +224 -0
  15. data/lib/protostar/codec/encoder.rb +121 -0
  16. data/lib/protostar/codec/sizer.rb +91 -0
  17. data/lib/protostar/codec.rb +15 -0
  18. data/lib/protostar/compiler/ast.rb +193 -0
  19. data/lib/protostar/compiler/emitters/descriptor_set.rb +57 -0
  20. data/lib/protostar/compiler/emitters/google_ruby.rb +103 -0
  21. data/lib/protostar/compiler/emitters/grpc.rb +175 -0
  22. data/lib/protostar/compiler/emitters/protostar_rbs.rb +252 -0
  23. data/lib/protostar/compiler/emitters/rbs.rb +394 -0
  24. data/lib/protostar/compiler/emitters/ruby.rb +503 -0
  25. data/lib/protostar/compiler/emitters.rb +268 -0
  26. data/lib/protostar/compiler/include/LICENSE +32 -0
  27. data/lib/protostar/compiler/include/google/protobuf/any.proto +106 -0
  28. data/lib/protostar/compiler/include/google/protobuf/api.proto +229 -0
  29. data/lib/protostar/compiler/include/google/protobuf/compiler/plugin.proto +180 -0
  30. data/lib/protostar/compiler/include/google/protobuf/cpp_features.proto +87 -0
  31. data/lib/protostar/compiler/include/google/protobuf/descriptor.proto +1532 -0
  32. data/lib/protostar/compiler/include/google/protobuf/duration.proto +115 -0
  33. data/lib/protostar/compiler/include/google/protobuf/empty.proto +51 -0
  34. data/lib/protostar/compiler/include/google/protobuf/field_mask.proto +243 -0
  35. data/lib/protostar/compiler/include/google/protobuf/java_features.proto +132 -0
  36. data/lib/protostar/compiler/include/google/protobuf/source_context.proto +48 -0
  37. data/lib/protostar/compiler/include/google/protobuf/struct.proto +111 -0
  38. data/lib/protostar/compiler/include/google/protobuf/timestamp.proto +145 -0
  39. data/lib/protostar/compiler/include/google/protobuf/type.proto +217 -0
  40. data/lib/protostar/compiler/include/google/protobuf/wrappers.proto +157 -0
  41. data/lib/protostar/compiler/lexer.rb +659 -0
  42. data/lib/protostar/compiler/linker.rb +894 -0
  43. data/lib/protostar/compiler/option_interpreter.rb +363 -0
  44. data/lib/protostar/compiler/parser.rb +1696 -0
  45. data/lib/protostar/compiler/retention.rb +104 -0
  46. data/lib/protostar/compiler/run.rb +184 -0
  47. data/lib/protostar/compiler/source_info.rb +161 -0
  48. data/lib/protostar/compiler/symbol_table.rb +223 -0
  49. data/lib/protostar/compiler/text_format.rb +340 -0
  50. data/lib/protostar/compiler/validator.rb +944 -0
  51. data/lib/protostar/compiler.rb +304 -0
  52. data/lib/protostar/diagnostics/codes.rb +1307 -0
  53. data/lib/protostar/diagnostics/formats.rb +64 -0
  54. data/lib/protostar/diagnostics/renderer.rb +240 -0
  55. data/lib/protostar/diagnostics/source.rb +153 -0
  56. data/lib/protostar/diagnostics.rb +117 -0
  57. data/lib/protostar/dsl.rb +367 -0
  58. data/lib/protostar/enum.rb +70 -0
  59. data/lib/protostar/errors.rb +23 -0
  60. data/lib/protostar/google/protobuf/any_pb.rb +90 -0
  61. data/lib/protostar/google/protobuf/api_pb.rb +199 -0
  62. data/lib/protostar/google/protobuf/compiler/plugin_pb.rb +168 -0
  63. data/lib/protostar/google/protobuf/descriptor_pb.rb +1239 -0
  64. data/lib/protostar/google/protobuf/duration_pb.rb +99 -0
  65. data/lib/protostar/google/protobuf/empty_pb.rb +36 -0
  66. data/lib/protostar/google/protobuf/field_mask_pb.rb +228 -0
  67. data/lib/protostar/google/protobuf/source_context_pb.rb +33 -0
  68. data/lib/protostar/google/protobuf/struct_pb.rb +91 -0
  69. data/lib/protostar/google/protobuf/timestamp_pb.rb +129 -0
  70. data/lib/protostar/google/protobuf/type_pb.rb +202 -0
  71. data/lib/protostar/google/protobuf/wrappers_pb.rb +125 -0
  72. data/lib/protostar/google_protobuf_shim.rb +24 -0
  73. data/lib/protostar/json/encoder.rb +340 -0
  74. data/lib/protostar/json/errors.rb +34 -0
  75. data/lib/protostar/json/parser.rb +437 -0
  76. data/lib/protostar/json/path.rb +39 -0
  77. data/lib/protostar/json.rb +95 -0
  78. data/lib/protostar/lint/command.rb +197 -0
  79. data/lib/protostar/lint/config.rb +98 -0
  80. data/lib/protostar/lint/context.rb +126 -0
  81. data/lib/protostar/lint/directives.rb +56 -0
  82. data/lib/protostar/lint/fixer.rb +165 -0
  83. data/lib/protostar/lint/naming.rb +54 -0
  84. data/lib/protostar/lint/rule.rb +120 -0
  85. data/lib/protostar/lint/rules/comment_enum.rb +31 -0
  86. data/lib/protostar/lint/rules/comment_enum_value.rb +32 -0
  87. data/lib/protostar/lint/rules/comment_field.rb +33 -0
  88. data/lib/protostar/lint/rules/comment_message.rb +31 -0
  89. data/lib/protostar/lint/rules/comment_rpc.rb +31 -0
  90. data/lib/protostar/lint/rules/comment_service.rb +31 -0
  91. data/lib/protostar/lint/rules/edition_recommended.rb +31 -0
  92. data/lib/protostar/lint/rules/enum_first_value_zero.rb +34 -0
  93. data/lib/protostar/lint/rules/enum_pascal_case.rb +25 -0
  94. data/lib/protostar/lint/rules/enum_value_prefix.rb +38 -0
  95. data/lib/protostar/lint/rules/enum_value_upper_snake_case.rb +28 -0
  96. data/lib/protostar/lint/rules/enum_zero_value_suffix.rb +41 -0
  97. data/lib/protostar/lint/rules/field_lower_snake_case.rb +34 -0
  98. data/lib/protostar/lint/rules/field_not_required.rb +39 -0
  99. data/lib/protostar/lint/rules/field_number_reuse_risk.rb +45 -0
  100. data/lib/protostar/lint/rules/field_numbers_hot_range.rb +44 -0
  101. data/lib/protostar/lint/rules/file_lower_snake_case.rb +27 -0
  102. data/lib/protostar/lint/rules/import_no_public.rb +29 -0
  103. data/lib/protostar/lint/rules/import_no_weak.rb +28 -0
  104. data/lib/protostar/lint/rules/import_used.rb +33 -0
  105. data/lib/protostar/lint/rules/message_pascal_case.rb +27 -0
  106. data/lib/protostar/lint/rules/no_deprecated_features.rb +50 -0
  107. data/lib/protostar/lint/rules/no_groups.rb +34 -0
  108. data/lib/protostar/lint/rules/oneof_lower_snake_case.rb +27 -0
  109. data/lib/protostar/lint/rules/package_defined.rb +28 -0
  110. data/lib/protostar/lint/rules/package_directory_match.rb +33 -0
  111. data/lib/protostar/lint/rules/package_lower_snake_case.rb +28 -0
  112. data/lib/protostar/lint/rules/package_same_directory.rb +39 -0
  113. data/lib/protostar/lint/rules/package_version_suffix.rb +31 -0
  114. data/lib/protostar/lint/rules/reserved_names_with_numbers.rb +44 -0
  115. data/lib/protostar/lint/rules/rpc_pascal_case.rb +25 -0
  116. data/lib/protostar/lint/rules/rpc_request_response_unique.rb +40 -0
  117. data/lib/protostar/lint/rules/rpc_request_standard_name.rb +32 -0
  118. data/lib/protostar/lint/rules/rpc_response_standard_name.rb +31 -0
  119. data/lib/protostar/lint/rules/service_pascal_case.rb +25 -0
  120. data/lib/protostar/lint/rules/service_suffix.rb +28 -0
  121. data/lib/protostar/lint/rules/syntax_specified.rb +35 -0
  122. data/lib/protostar/lint/runner.rb +135 -0
  123. data/lib/protostar/lint.rb +122 -0
  124. data/lib/protostar/message/accessors.rb +86 -0
  125. data/lib/protostar/message/coercion.rb +171 -0
  126. data/lib/protostar/message/field_info.rb +145 -0
  127. data/lib/protostar/message/reflection.rb +149 -0
  128. data/lib/protostar/message.rb +572 -0
  129. data/lib/protostar/plugin.rb +94 -0
  130. data/lib/protostar/schema/default_value.rb +113 -0
  131. data/lib/protostar/schema/descriptor.rb +41 -0
  132. data/lib/protostar/schema/draft/copy.rb +88 -0
  133. data/lib/protostar/schema/draft/resolution.rb +177 -0
  134. data/lib/protostar/schema/draft.rb +779 -0
  135. data/lib/protostar/schema/enum.rb +106 -0
  136. data/lib/protostar/schema/extension_range.rb +37 -0
  137. data/lib/protostar/schema/features.rb +153 -0
  138. data/lib/protostar/schema/field.rb +201 -0
  139. data/lib/protostar/schema/file.rb +217 -0
  140. data/lib/protostar/schema/freezer.rb +61 -0
  141. data/lib/protostar/schema/message.rb +104 -0
  142. data/lib/protostar/schema/oneof.rb +38 -0
  143. data/lib/protostar/schema/options.rb +367 -0
  144. data/lib/protostar/schema/pool.rb +179 -0
  145. data/lib/protostar/schema/proto.rb +576 -0
  146. data/lib/protostar/schema/resolver.rb +118 -0
  147. data/lib/protostar/schema/service.rb +89 -0
  148. data/lib/protostar/schema/source_info.rb +53 -0
  149. data/lib/protostar/schema.rb +65 -0
  150. data/lib/protostar/short_names/any.rb +7 -0
  151. data/lib/protostar/short_names/duration.rb +7 -0
  152. data/lib/protostar/short_names/empty.rb +7 -0
  153. data/lib/protostar/short_names/field_mask.rb +7 -0
  154. data/lib/protostar/short_names/struct.rb +9 -0
  155. data/lib/protostar/short_names/timestamp.rb +7 -0
  156. data/lib/protostar/short_names/wrappers.rb +15 -0
  157. data/lib/protostar/text/extensions.rb +114 -0
  158. data/lib/protostar/text/parser.rb +439 -0
  159. data/lib/protostar/text/printer.rb +304 -0
  160. data/lib/protostar/text/tokenizer.rb +116 -0
  161. data/lib/protostar/text.rb +67 -0
  162. data/lib/protostar/types.rb +152 -0
  163. data/lib/protostar/unknown_field.rb +58 -0
  164. data/lib/protostar/version.rb +5 -0
  165. data/lib/protostar/view/generator.rb +298 -0
  166. data/lib/protostar/view/map.rb +194 -0
  167. data/lib/protostar/view/repeated.rb +143 -0
  168. data/lib/protostar/view/scan.rb +183 -0
  169. data/lib/protostar/view.rb +283 -0
  170. data/lib/protostar/well_known/any.rb +52 -0
  171. data/lib/protostar/well_known/duration.rb +53 -0
  172. data/lib/protostar/well_known/list_value.rb +63 -0
  173. data/lib/protostar/well_known/struct.rb +55 -0
  174. data/lib/protostar/well_known/timestamp.rb +64 -0
  175. data/lib/protostar/well_known/value.rb +60 -0
  176. data/lib/protostar/well_known.rb +38 -0
  177. data/lib/protostar/wire/io_source.rb +40 -0
  178. data/lib/protostar/wire/reader.rb +254 -0
  179. data/lib/protostar/wire/writer.rb +201 -0
  180. data/lib/protostar/wire.rb +96 -0
  181. data/lib/protostar.rb +95 -0
  182. data/shims/google-protobuf/lib/google/protobuf/any_pb.rb +17 -0
  183. data/shims/google-protobuf/lib/google/protobuf/api_pb.rb +22 -0
  184. data/shims/google-protobuf/lib/google/protobuf/compiler/plugin_pb.rb +25 -0
  185. data/shims/google-protobuf/lib/google/protobuf/descriptor_pb.rb +72 -0
  186. data/shims/google-protobuf/lib/google/protobuf/descriptor_pool.rb +164 -0
  187. data/shims/google-protobuf/lib/google/protobuf/descriptors.rb +553 -0
  188. data/shims/google-protobuf/lib/google/protobuf/duration_pb.rb +17 -0
  189. data/shims/google-protobuf/lib/google/protobuf/empty_pb.rb +17 -0
  190. data/shims/google-protobuf/lib/google/protobuf/field_mask_pb.rb +17 -0
  191. data/shims/google-protobuf/lib/google/protobuf/internal/convert.rb +180 -0
  192. data/shims/google-protobuf/lib/google/protobuf/internal/extensions.rb +154 -0
  193. data/shims/google-protobuf/lib/google/protobuf/internal/json.rb +104 -0
  194. data/shims/google-protobuf/lib/google/protobuf/internal/well_known.rb +140 -0
  195. data/shims/google-protobuf/lib/google/protobuf/map.rb +216 -0
  196. data/shims/google-protobuf/lib/google/protobuf/message.rb +544 -0
  197. data/shims/google-protobuf/lib/google/protobuf/message_exts.rb +41 -0
  198. data/shims/google-protobuf/lib/google/protobuf/plugin_pb.rb +4 -0
  199. data/shims/google-protobuf/lib/google/protobuf/repeated_field.rb +366 -0
  200. data/shims/google-protobuf/lib/google/protobuf/source_context_pb.rb +17 -0
  201. data/shims/google-protobuf/lib/google/protobuf/struct_pb.rb +20 -0
  202. data/shims/google-protobuf/lib/google/protobuf/timestamp_pb.rb +17 -0
  203. data/shims/google-protobuf/lib/google/protobuf/type_pb.rb +27 -0
  204. data/shims/google-protobuf/lib/google/protobuf/well_known_types.rb +37 -0
  205. data/shims/google-protobuf/lib/google/protobuf/wrappers_pb.rb +25 -0
  206. data/shims/google-protobuf/lib/google/protobuf.rb +107 -0
  207. data/shims/google-protobuf/lib/google/protobuf_ffi.rb +5 -0
  208. data/shims/google-protobuf/lib/google/protobuf_native.rb +5 -0
  209. data/sig/protostar/google/protobuf/any_pb.rbs +77 -0
  210. data/sig/protostar/google/protobuf/api_pb.rbs +242 -0
  211. data/sig/protostar/google/protobuf/compiler/plugin_pb.rbs +264 -0
  212. data/sig/protostar/google/protobuf/descriptor_pb.rbs +2220 -0
  213. data/sig/protostar/google/protobuf/duration_pb.rbs +82 -0
  214. data/sig/protostar/google/protobuf/empty_pb.rbs +18 -0
  215. data/sig/protostar/google/protobuf/field_mask_pb.rbs +170 -0
  216. data/sig/protostar/google/protobuf/source_context_pb.rbs +24 -0
  217. data/sig/protostar/google/protobuf/struct_pb.rbs +135 -0
  218. data/sig/protostar/google/protobuf/timestamp_pb.rbs +102 -0
  219. data/sig/protostar/google/protobuf/type_pb.rbs +304 -0
  220. data/sig/protostar/google/protobuf/wrappers_pb.rbs +169 -0
  221. data/sig/protostar/lint.rbs +216 -0
  222. data/sig/protostar.rbs +326 -0
  223. 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