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,572 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ # The base class of every message class. Subclasses are made by
5
+ # {Types.define} from a {Schema::Message}; they get one reader and writer
6
+ # per field, `has_` and `clear_` for fields with presence, and `which_` for
7
+ # each oneof.
8
+ #
9
+ # person = Person.new(name: "Ada", ids: [1, 2])
10
+ # bytes = person.encode
11
+ # Person.decode(bytes) == person # => true
12
+ #
13
+ # Values are kept in one Array per message, a slot per field. Singular
14
+ # fields with presence hold nil when unset; fields without presence hold
15
+ # their value, the default included. Repeated fields hold an Array and maps
16
+ # a Hash, both allocated on first access.
17
+ class Message
18
+ EMPTY_ARRAY = [].freeze
19
+ EMPTY_HASH = {}.freeze
20
+
21
+ # Needed while a class is defined, never after
22
+ autoload :Accessors, ::File.expand_path("message/accessors", __dir__)
23
+ autoload :Reflection, ::File.expand_path("message/reflection", __dir__)
24
+
25
+ class << self
26
+ # @return [Schema::Message]
27
+ attr_reader :descriptor
28
+
29
+ # @return [Reflection] the class's fields by descriptor, name or number
30
+ attr_reader :reflection
31
+
32
+ # @api private
33
+ attr_reader :__fields, :__by_number, :__by_name, :__template, :__oneofs, :__required
34
+
35
+ # @param bytes [String, IO::Buffer] never mutated
36
+ # @param recursion_limit [Integer] how deep messages may nest
37
+ # @param frozen [Boolean] return a deep-frozen, Ractor-shareable message
38
+ # @return [Message]
39
+ # @raise [DecodeError]
40
+ def decode(bytes, recursion_limit: Codec::Decoder::RECURSION_LIMIT, frozen: false)
41
+ Codec::Decoder.decode(self, bytes, recursion_limit: recursion_limit, frozen: frozen)
42
+ end
43
+
44
+ # Reads one message prefixed by its varint length
45
+ # @param source [String, IO::Buffer, #getbyte and #read] a String is read
46
+ # from its start; an IO from its current position, leaving it after the message
47
+ # @return [Message, nil] nil when an IO is at its end
48
+ # @raise [DecodeError]
49
+ def decode_delimited(source, **options)
50
+ Codec::Decoder.decode_delimited(self, source, **options)
51
+ end
52
+
53
+ # @param message [Message] an instance of this class
54
+ # @return [String] binary
55
+ def encode(message)
56
+ unless message.instance_of?(self)
57
+ raise Protostar::TypeError, "#{descriptor.full_name}.encode expects a #{descriptor.full_name}, got #{message.class}"
58
+ end
59
+ message.encode
60
+ end
61
+
62
+ # @param json [String]
63
+ # @param options [Hash] see {JSON.decode}
64
+ # @return [Message]
65
+ # @raise [JSON::ParseError]
66
+ def decode_json(json, **options)
67
+ JSON.decode(self, json, **options)
68
+ end
69
+
70
+ # @param message [Message] an instance of this class
71
+ # @param options [Hash] see {JSON.encode}
72
+ # @return [String]
73
+ def encode_json(message, **options)
74
+ unless message.instance_of?(self)
75
+ raise Protostar::TypeError, "#{descriptor.full_name}.encode_json expects a #{descriptor.full_name}, got #{message.class}"
76
+ end
77
+ JSON.encode(message, **options)
78
+ end
79
+
80
+ # @api private
81
+ attr_reader :__view_class
82
+
83
+ # @return [Class] the {View} subclass whose instances read this class's messages
84
+ def view_class
85
+ @__view_class || raise(NotImplementedError, "#{self} is abstract; define message classes with Protostar::Types")
86
+ end
87
+
88
+ # A read-only view of an encoded message that decodes fields only when read
89
+ # @param bytes [String, IO::Buffer] not copied; must not change while the view is used
90
+ # @param offset [Integer] where the message starts in `bytes`
91
+ # @param length [Integer, nil] its length, by default up to the end of `bytes`
92
+ # @param recursion_limit [Integer] how deep messages may nest
93
+ # @return [View]
94
+ # @raise [DecodeError] when the top level of the message is malformed
95
+ def view(bytes, offset: 0, length: nil, recursion_limit: Codec::Decoder::RECURSION_LIMIT)
96
+ view_class.__open(bytes, offset, length, recursion_limit)
97
+ end
98
+
99
+ # A message made with a {Builder} block, or with `new` when there is no
100
+ # block. `fields` are written first; the block may set them again (the
101
+ # bytes never leave the process, so overwrites are allowed).
102
+ #
103
+ # person = Acme::Person.build(name: "Jane") do |pb|
104
+ # pb.address { pb.street "x" }
105
+ # end
106
+ #
107
+ # @param fields [Hash, nil] field names to values, as {#initialize} takes them
108
+ # @yieldparam pb [Builder]
109
+ # @return [Message]
110
+ def build(fields = nil, **more, &block)
111
+ return new(fields, **more) unless block
112
+ Builder.message(self, overwrites: true) do |pb|
113
+ pb.merge!(fields) if fields
114
+ pb.merge!(more) unless more.empty?
115
+ block.call(pb)
116
+ end
117
+ end
118
+
119
+ # @return [Message] a new message with `b` merged into a copy of `a`
120
+ def merge(a, b)
121
+ a.dup.merge!(b)
122
+ end
123
+
124
+ # What a field of this type makes of a value that is neither an
125
+ # instance nor a Hash. The well-known types' mixins override it, so that
126
+ # a Time can be assigned to a Timestamp field.
127
+ # @return [Message, nil] nil when the value cannot become one
128
+ # @api private
129
+ def __coerce(_value)
130
+ nil
131
+ end
132
+
133
+ # @return [Message] a message with every field unset, bypassing argument parsing
134
+ # @api private
135
+ def __empty
136
+ m = allocate
137
+ m.__reset
138
+ m
139
+ end
140
+
141
+ # @api private
142
+ def __define(descriptor:, fields:, oneofs:)
143
+ @descriptor = descriptor
144
+ @__fields = fields
145
+ @__by_number = fields.to_h { |f| [f.number, f] }
146
+ @__by_name = fields.each_with_object({}) { |f, h| h[f.name] = h[f.symbol] = f }
147
+ @__template = fields.map { |f| (f.presence || f.repeated || f.map) ? nil : f.default }
148
+ @__oneofs = oneofs
149
+ @__required = fields.select(&:required)
150
+ [@__fields, @__by_number, @__by_name, @__template, @__oneofs, @__required].each do |o|
151
+ Ractor.make_shareable(o)
152
+ end
153
+ @reflection = Ractor.make_shareable(Reflection.new(self, descriptor))
154
+ self
155
+ end
156
+
157
+ def inspect
158
+ name || "#<Class #{descriptor ? descriptor.full_name : "Protostar::Message"}>"
159
+ end
160
+ end
161
+
162
+ # @param hash [Hash, nil] field names (Symbols or Strings) to values
163
+ # @param fields [Hash] the same, as keywords
164
+ # @raise [ArgumentError] for a name that is not a field
165
+ # @raise [Protostar::TypeError] for a value the field cannot hold
166
+ def initialize(hash = nil, **fields)
167
+ klass = self.class
168
+ raise NotImplementedError, "#{klass} is abstract; define message classes with Protostar::Types" unless klass.__fields
169
+ @values = klass.__template.dup
170
+ @unknown = nil
171
+ __assign(hash) if hash
172
+ __assign(fields) unless fields.empty?
173
+ end
174
+
175
+ # @param name [String, Symbol]
176
+ # @return [Object] what the field's reader returns
177
+ # @raise [ArgumentError] when there is no such field
178
+ def [](name)
179
+ __get(__info(name))
180
+ end
181
+
182
+ # @param name [String, Symbol]
183
+ # @raise [ArgumentError] when there is no such field
184
+ def []=(name, value)
185
+ __set(__info(name).index, value)
186
+ end
187
+
188
+ # @return [Array<UnknownField>] frozen, in the order they were read
189
+ def unknown_fields
190
+ @unknown || EMPTY_ARRAY
191
+ end
192
+
193
+ # @return [String] binary
194
+ def encode
195
+ Codec::Encoder.encode(self)
196
+ end
197
+ alias_method :to_proto, :encode
198
+
199
+ # Writes the message to an IO without building the whole encoding in memory
200
+ # @return [IO] the IO given
201
+ def encode_to(io)
202
+ Codec::Encoder.encode_to(self, io)
203
+ io
204
+ end
205
+
206
+ # @param state [::JSON::State, nil] what the stdlib generator passes; its
207
+ # indentation is honoured, so `JSON.pretty_generate(message)` is indented
208
+ # @param options [Hash] see {JSON.encode}
209
+ # @return [String] the message as proto3 JSON
210
+ def to_json(state = nil, **options)
211
+ json = JSON.encode(self, **options)
212
+ state.respond_to?(:object_nl) ? JSON.layout(json, state) : json
213
+ end
214
+
215
+ # @param options [Hash] see {Text.print}
216
+ # @return [String] the message in protoc's text format
217
+ def to_text(**options)
218
+ Text.print(self, **options)
219
+ end
220
+
221
+ # The message prefixed by its varint length
222
+ # @param io [IO, nil] where to write it
223
+ # @return [String, IO] the bytes when no IO is given, otherwise the IO
224
+ def encode_delimited(io = nil)
225
+ Codec::Encoder.encode_delimited(self, io)
226
+ end
227
+
228
+ # @return [Hash] Symbol keys, every field that is set or not at its
229
+ # default; nested messages as Hashes, enums as Symbols
230
+ def to_h
231
+ __to_h
232
+ end
233
+
234
+ # {#to_h} as Message defines it; well-known types replace to_h (a Struct
235
+ # gives a plain Hash), but nested messages always convert field by field
236
+ # @api private
237
+ def __to_h
238
+ h = {}
239
+ values = @values
240
+ self.class.__fields.each do |info|
241
+ v = values[info.index]
242
+ next if v.nil?
243
+ if info.map
244
+ next if v.empty?
245
+ h[info.symbol] = info.value.message? ? v.transform_values(&:__to_h) : v.dup
246
+ elsif info.repeated
247
+ next if v.empty?
248
+ h[info.symbol] = info.message? ? v.map(&:__to_h) : v.dup
249
+ elsif info.message?
250
+ h[info.symbol] = v.__to_h
251
+ elsif info.presence || !info.zero?(v)
252
+ h[info.symbol] = v
253
+ end
254
+ end
255
+ h
256
+ end
257
+
258
+ # Same class and same field values. Unknown fields are ignored, and NaN
259
+ # equals NaN in float and double fields, as protoc's MessageDifferencer has it.
260
+ def ==(other)
261
+ return other == self if other.is_a?(View)
262
+ return true if equal?(other)
263
+ return false unless other.instance_of?(self.class)
264
+ mine = @values
265
+ theirs = other.__values
266
+ self.class.__fields.each do |info|
267
+ i = info.index
268
+ return false unless Message.__equal_values?(info, mine[i], theirs[i])
269
+ end
270
+ true
271
+ end
272
+ alias_method :eql?, :==
273
+
274
+ def hash
275
+ h = [self.class]
276
+ self.class.__fields.each { |info| h << Message.__hash_value(info, @values[info.index]) }
277
+ h.hash
278
+ end
279
+
280
+ # Deep copy; the copy is not frozen
281
+ def initialize_copy(source)
282
+ super
283
+ @values = @values.map { |v| Message.__copy_value(v) }
284
+ @unknown = @unknown&.dup&.freeze
285
+ end
286
+
287
+ # Deep copy, frozen when the original is (or when asked)
288
+ def clone(freeze: nil)
289
+ copy = dup
290
+ copy.freeze if freeze || (freeze.nil? && frozen?)
291
+ copy
292
+ end
293
+
294
+ # Freezes the message and everything it holds, after which it is Ractor.shareable?
295
+ # @return [self]
296
+ def freeze
297
+ return self if frozen?
298
+ @values.each do |v|
299
+ case v
300
+ when Message then v.freeze
301
+ when Array then v.each(&:freeze).freeze
302
+ when Hash then v.each_value(&:freeze).freeze
303
+ end
304
+ end
305
+ @values.freeze
306
+ @unknown&.freeze
307
+ super
308
+ end
309
+
310
+ # Merges `other` into this message: singular fields set in `other`
311
+ # replace ours, messages merge recursively, repeated fields append, maps
312
+ # overwrite by key, unknown fields append.
313
+ # @return [self]
314
+ def merge!(other)
315
+ unless other.instance_of?(self.class)
316
+ raise Protostar::TypeError, "cannot merge a #{other.class} into a #{self.class.descriptor.full_name}"
317
+ end
318
+ __check_frozen
319
+ theirs = other.__values
320
+ self.class.__fields.each do |info|
321
+ i = info.index
322
+ v = theirs[i]
323
+ next if v.nil?
324
+ if info.map
325
+ next if v.empty?
326
+ (@values[i] ||= {}).merge!(info.value.message? ? v.transform_values(&:dup) : v)
327
+ elsif info.repeated
328
+ next if v.empty?
329
+ (@values[i] ||= []).concat(info.message? ? v.map(&:dup) : v)
330
+ elsif info.message?
331
+ mine = @values[i]
332
+ if mine.nil?
333
+ __clear_siblings(info)
334
+ @values[i] = v.dup
335
+ else
336
+ mine = @values[i] = mine.dup if mine.frozen?
337
+ mine.merge!(v)
338
+ end
339
+ elsif info.presence || !info.zero?(v)
340
+ __clear_siblings(info)
341
+ @values[i] = v
342
+ end
343
+ end
344
+ @unknown = ((@unknown || EMPTY_ARRAY) + other.unknown_fields).freeze unless other.unknown_fields.empty?
345
+ self
346
+ end
347
+
348
+ # Unsets every field and drops unknown fields
349
+ # @return [self]
350
+ def clear!
351
+ __check_frozen
352
+ __reset
353
+ self
354
+ end
355
+
356
+ # @return [Boolean] whether every required field is set, here and in every nested message
357
+ def initialized?
358
+ self.class.__required.each { |info| return false if @values[info.index].nil? }
359
+ self.class.__fields.each do |info|
360
+ v = @values[info.index]
361
+ next if v.nil?
362
+ if info.map
363
+ next unless info.value.message?
364
+ v.each_value { |m| return false unless m.initialized? }
365
+ elsif info.message?
366
+ if info.repeated
367
+ v.each { |m| return false unless m.initialized? }
368
+ else
369
+ return false unless v.initialized?
370
+ end
371
+ end
372
+ end
373
+ true
374
+ end
375
+
376
+ def inspect
377
+ parts = __shown.map { |name, v| "#{name}: #{v.inspect}" }
378
+ name = self.class.descriptor.full_name
379
+ parts.empty? ? "#<#{name}>" : "#<#{name} #{parts.join(", ")}>"
380
+ end
381
+ alias_method :to_s, :inspect
382
+
383
+ # For `pp`: the fields `inspect` shows, one per line with nested
384
+ # messages indented when they do not fit on one
385
+ def pretty_print(q)
386
+ Message.__pretty_print(q, self.class.descriptor.full_name, __shown)
387
+ end
388
+
389
+ # @api private
390
+ def self.__pretty_print(q, name, fields)
391
+ return q.text("#<#{name}>") if fields.empty?
392
+ q.group(2, "#<#{name}", ">") do
393
+ fields.each_with_index do |(field, v), i|
394
+ q.text(",") if i > 0
395
+ q.breakable
396
+ q.text("#{field}: ")
397
+ q.pp(v)
398
+ end
399
+ end
400
+ end
401
+
402
+ # The fields `inspect` shows: set, and not at their zero value
403
+ # @return [Array<Array(String, Object)>]
404
+ # @api private
405
+ def __shown
406
+ shown = []
407
+ self.class.__fields.each do |info|
408
+ v = @values[info.index]
409
+ next if v.nil?
410
+ next if (info.repeated || info.map) && v.empty?
411
+ next if !info.presence && !info.repeated && !info.map && info.zero?(v)
412
+ shown << [info.name, v]
413
+ end
414
+ shown
415
+ end
416
+
417
+ # @api private
418
+ def __values
419
+ @values
420
+ end
421
+
422
+ # @api private
423
+ def __reset
424
+ @values = self.class.__template.dup
425
+ @unknown = nil
426
+ end
427
+
428
+ # Appends an unknown field; the decoder's way in
429
+ # @api private
430
+ def __add_unknown(field)
431
+ u = @unknown
432
+ if u.nil?
433
+ @unknown = [field]
434
+ elsif u.frozen?
435
+ @unknown = [*u, field]
436
+ else
437
+ u << field
438
+ end
439
+ end
440
+
441
+ # @api private
442
+ def __seal_unknown
443
+ @unknown&.freeze
444
+ end
445
+
446
+ # @api private
447
+ def __set(index, value)
448
+ __check_frozen
449
+ info = self.class.__fields[index]
450
+ if value.nil?
451
+ @values[index] = (info.presence || info.repeated || info.map) ? nil : info.default
452
+ else
453
+ value = Coercion.field(info, value)
454
+ __clear_siblings(info)
455
+ @values[index] = value
456
+ end
457
+ end
458
+
459
+ # @api private
460
+ def __clear(index)
461
+ __check_frozen
462
+ @values[index] = nil
463
+ end
464
+
465
+ # The stored Array or Hash of a repeated or map field, made on first use
466
+ # @api private
467
+ def __container(index)
468
+ info = self.class.__fields[index]
469
+ if frozen?
470
+ info.map ? EMPTY_HASH : EMPTY_ARRAY
471
+ else
472
+ @values[index] = info.map ? {} : []
473
+ end
474
+ end
475
+
476
+ # @api private
477
+ def __get(info)
478
+ v = @values[info.index]
479
+ if info.repeated || info.map
480
+ v || __container(info.index)
481
+ elsif v.nil?
482
+ info.default
483
+ else
484
+ v
485
+ end
486
+ end
487
+
488
+ # @api private
489
+ def self.__equal_values?(info, a, b)
490
+ if info.map
491
+ a ||= EMPTY_HASH
492
+ b ||= EMPTY_HASH
493
+ return a == b unless float_kind?(info.value.kind)
494
+ a.size == b.size && a.all? { |k, v| b.key?(k) && float_equal?(v, b[k]) }
495
+ elsif info.repeated
496
+ a ||= EMPTY_ARRAY
497
+ b ||= EMPTY_ARRAY
498
+ return a == b unless float_kind?(info.kind)
499
+ a.size == b.size && a.each_index.all? { |i| float_equal?(a[i], b[i]) }
500
+ elsif float_kind?(info.kind)
501
+ float_equal?(a, b)
502
+ else
503
+ a == b
504
+ end
505
+ end
506
+
507
+ # @api private
508
+ def self.__hash_value(info, v)
509
+ if info.map
510
+ return nil if v.nil? || v.empty?
511
+ float_kind?(info.value.kind) ? v.transform_values { |f| hashable_float(f) } : v
512
+ elsif info.repeated
513
+ return nil if v.nil? || v.empty?
514
+ float_kind?(info.kind) ? v.map { |f| hashable_float(f) } : v
515
+ elsif float_kind?(info.kind)
516
+ hashable_float(v)
517
+ else
518
+ v
519
+ end
520
+ end
521
+
522
+ # @api private
523
+ def self.__copy_value(v)
524
+ case v
525
+ when Message then v.dup
526
+ when Array then v.map { |e| e.is_a?(Message) ? e.dup : e }
527
+ when Hash then v.transform_values { |e| e.is_a?(Message) ? e.dup : e }
528
+ else v
529
+ end
530
+ end
531
+
532
+ def self.float_kind?(kind)
533
+ kind == FieldInfo::DOUBLE || kind == FieldInfo::FLOAT
534
+ end
535
+
536
+ def self.float_equal?(a, b)
537
+ a == b || (a.is_a?(Float) && b.is_a?(Float) && a.nan? && b.nan?)
538
+ end
539
+
540
+ def self.hashable_float(f)
541
+ (f.is_a?(Float) && f.nan?) ? :nan : f
542
+ end
543
+ private_class_method :float_kind?, :float_equal?, :hashable_float
544
+
545
+ private
546
+
547
+ def __info(name)
548
+ self.class.__by_name[name] || raise(ArgumentError, "#{self.class.descriptor.full_name} has no field #{name.inspect}")
549
+ end
550
+
551
+ def __assign(hash)
552
+ by_name = self.class.__by_name
553
+ hash.each do |key, value|
554
+ info = by_name[key]
555
+ raise ArgumentError, "#{self.class.descriptor.full_name} has no field #{key.inspect}" unless info
556
+ __set(info.index, value)
557
+ end
558
+ end
559
+
560
+ def __clear_siblings(info)
561
+ siblings = info.siblings
562
+ siblings&.each { |s| @values[s] = nil }
563
+ end
564
+
565
+ def __check_frozen
566
+ raise FrozenError.new("can't modify frozen #{self.class.descriptor.full_name}", receiver: self) if frozen?
567
+ end
568
+ end
569
+ end
570
+
571
+ require_relative "message/field_info"
572
+ require_relative "message/coercion"
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "protostar"
4
+ require "protostar/google/protobuf/compiler/plugin_pb"
5
+
6
+ module Protostar
7
+ # `protoc-gen-protostar`: a protoc plugin writing the same files as
8
+ # `protostar compile`. The parameter selects the outputs, comma-separated:
9
+ # `ruby` (protostar's native Ruby, the default), `google` (protoc's
10
+ # google-protobuf Ruby), `rbs` and `grpc`.
11
+ #
12
+ # protoc --plugin=protoc-gen-protostar --protostar_out=ruby,grpc:lib acme/service.proto
13
+ module Plugin
14
+ Compiler = Protostar::Compiler
15
+ Emitters = Compiler::Emitters
16
+ Request = Protostar::Google::Protobuf::Compiler::CodeGeneratorRequest
17
+ Response = Protostar::Google::Protobuf::Compiler::CodeGeneratorResponse
18
+ Feature = Response::Feature
19
+ OUTPUTS = %w[ruby google rbs grpc].freeze
20
+
21
+ module_function
22
+
23
+ # Reads a CodeGeneratorRequest from `input` and writes the response to `output`
24
+ def run(input = $stdin, output = $stdout)
25
+ input.binmode
26
+ output.binmode
27
+ output.write(respond(input.read))
28
+ output.flush
29
+ 0
30
+ end
31
+
32
+ # @param bytes [String] a serialized CodeGeneratorRequest
33
+ # @return [String] a serialized CodeGeneratorResponse
34
+ def respond(bytes)
35
+ response = Response.new(
36
+ supported_features: Feature::FEATURE_PROTO3_OPTIONAL | Feature::FEATURE_SUPPORTS_EDITIONS,
37
+ minimum_edition: Schema::Features::EDITIONS[:EDITION_PROTO2],
38
+ maximum_edition: Schema::Features::EDITIONS[:EDITION_2024]
39
+ )
40
+ begin
41
+ generate(Request.decode(bytes)).each do |name, content|
42
+ response.file << Response::File.new(name: name, content: content)
43
+ end
44
+ rescue Protostar::Error => e
45
+ response.error = e.message
46
+ response.file.clear
47
+ end
48
+ response.encode
49
+ end
50
+
51
+ # @param request [Request]
52
+ # @return [Hash{String => String}] file name to content
53
+ def generate(request)
54
+ outputs = request.parameter.split(",").map(&:strip).reject(&:empty?)
55
+ outputs = ["ruby"] if outputs.empty?
56
+ unknown = outputs - OUTPUTS
57
+ raise Protostar::Error, "unknown output #{unknown.first.inspect}; use #{OUTPUTS.join(", ")}" unless unknown.empty?
58
+ if outputs.include?("ruby") && outputs.include?("google")
59
+ raise Protostar::Error, "ruby and google both write *_pb.rb; ask for one of them"
60
+ end
61
+ pool = Schema::Pool.new
62
+ # protoc has already warned about extension numbers used twice
63
+ request.proto_file.each { |proto| pool.add(Schema::File.from_proto(proto, pool: pool), warn: false) }
64
+ files = request.file_to_generate.map do |name|
65
+ pool.file(name) || raise(Protostar::Error, "#{name} is not among the request's proto files")
66
+ end
67
+ generated = {}
68
+ outputs.each do |output|
69
+ files.each do |file|
70
+ if output == "rbs" && file.syntax == :editions && !outputs.include?("ruby")
71
+ raise Protostar::Error, "#{file.name}: #{CLI::RBS_EDITIONS}"
72
+ end
73
+ begin
74
+ generated.merge!(emitter(output, pool, outputs).emit([file]))
75
+ rescue Emitters::Error => e
76
+ raise Protostar::Error, "#{file.name}: #{e.message}"
77
+ end
78
+ end
79
+ end
80
+ generated
81
+ end
82
+
83
+ def emitter(output, pool, outputs)
84
+ case output
85
+ when "ruby" then Emitters::Ruby.new
86
+ when "google" then Emitters::GoogleRuby.new(pool: pool)
87
+ when "rbs" then outputs.include?("ruby") ? Emitters::ProtostarRbs.new : Emitters::Rbs.new
88
+ when "grpc" then Emitters::Grpc.new
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ require "protostar/cli"