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,779 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ module Schema
5
+ # The construction stage of {Schema}: mutable handles that create
6
+ # descriptors, then link, resolve and freeze them. Map entries, the
7
+ # synthetic oneofs of proto3 `optional` fields, group fields and json
8
+ # names are made here and nowhere else.
9
+ module Draft
10
+ SYNTAXES = {nil => :proto2, :proto2 => :proto2, :proto3 => :proto3, :editions => :editions}.freeze
11
+ SCALARS = (Field::TYPES.values - %i[message enum group]).to_h { |t| [t, true] }.freeze
12
+ TYPES = Field::TYPES.values.to_h { |t| [t, true] }.freeze
13
+ LABELS = %i[optional required repeated].freeze
14
+
15
+ # protoc's name for the entry message of a map field: the field name in
16
+ # CamelCase plus "Entry"
17
+ # @param field_name [String]
18
+ # @return [String]
19
+ def self.map_entry_name(field_name)
20
+ out = +""
21
+ cap_next = true
22
+ field_name.to_s.each_char do |c|
23
+ if c == "_"
24
+ cap_next = true
25
+ elsif cap_next
26
+ out << (c.between?("a", "z") ? c.upcase : c)
27
+ cap_next = false
28
+ else
29
+ out << c
30
+ end
31
+ end
32
+ out << "Entry"
33
+ end
34
+
35
+ # The names protoc's parser gives the synthetic oneofs of proto3
36
+ # `optional` fields: `_` and the field name, with `X` prepended until
37
+ # nothing in the message has that name
38
+ # @param field_names [Array<String>] the fields needing one, in order
39
+ # @param taken [Array<String>] every field and oneof name of the message
40
+ # @return [Array<String>]
41
+ def self.synthetic_oneof_names(field_names, taken)
42
+ names = taken.to_h { |n| [n, true] }
43
+ field_names.map do |field_name|
44
+ name = field_name.start_with?("_") ? field_name : "_#{field_name}"
45
+ name = "X#{name}" while names.key?(name)
46
+ names[name] = true
47
+ name
48
+ end
49
+ end
50
+
51
+ # The field a `group Name = n { ... }` declares: the group's name in lower case
52
+ # @return [String]
53
+ def self.group_field_name(group_name)
54
+ group_name.to_s.downcase(:ascii)
55
+ end
56
+
57
+ # protoc's spelling of a default value
58
+ # @api private
59
+ def self.default_text(value)
60
+ case value
61
+ when nil, String then value
62
+ when Symbol then value.to_s
63
+ when Float then DefaultValue.simple_dtoa(value)
64
+ else value.to_s
65
+ end
66
+ end
67
+
68
+ VISIBILITIES = {
69
+ nil => nil, :local => :local, :export => :export, :VISIBILITY_LOCAL => :local,
70
+ :VISIBILITY_EXPORT => :export, :VISIBILITY_UNSET => nil
71
+ }.freeze
72
+
73
+ # :local or :export from either spelling; nil when unset
74
+ # @api private
75
+ def self.visibility(value)
76
+ VISIBILITIES.fetch(value) { raise Error, "unknown visibility #{value.inspect}" }
77
+ end
78
+
79
+ # Inclusive bounds of an Integer or a Range
80
+ # @api private
81
+ def self.bounds(item)
82
+ case item
83
+ when Integer then [item, item]
84
+ when ::Range
85
+ last = item.exclude_end? ? item.end - 1 : item.end
86
+ [item.begin, last]
87
+ else raise Error, "expected an Integer or a Range, got #{item.inspect}"
88
+ end
89
+ end
90
+
91
+ # What files and messages share: nested messages, enums and extensions
92
+ class Scope
93
+ # @api private
94
+ attr_reader :file_draft
95
+
96
+ # A message type
97
+ # @param name [String]
98
+ # @param visibility [Symbol, nil] :local or :export (edition 2024)
99
+ # @param options [Hash, nil]
100
+ # @yieldparam m [Message]
101
+ # @return [Message]
102
+ def message(name, visibility: nil, options: nil, &block)
103
+ name = name.to_s
104
+ descriptor = Schema::Message.new(name: name, full_name: qualify(name), file: @file_draft.descriptor,
105
+ containing_message: containing_message, index: @messages.size)
106
+ draft = Message.new(@file_draft, descriptor, Draft.visibility(visibility), options)
107
+ @messages << draft
108
+ @file_draft.register(descriptor)
109
+ block&.call(draft)
110
+ draft
111
+ end
112
+
113
+ # An enum type
114
+ # @yieldparam e [Enum]
115
+ # @return [Enum]
116
+ def enum(name, visibility: nil, options: nil, &block)
117
+ name = name.to_s
118
+ descriptor = Schema::Enum.new(name: name, full_name: qualify(name), file: @file_draft.descriptor,
119
+ containing_message: containing_message, index: @enums.size)
120
+ draft = Enum.new(@file_draft, descriptor, scope_name, Draft.visibility(visibility), options)
121
+ @enums << draft
122
+ @file_draft.register(descriptor)
123
+ block&.call(draft)
124
+ draft
125
+ end
126
+
127
+ # Extensions of another message, declared in this scope
128
+ # @param extendee [String] the extended message, as written
129
+ # @yieldparam e [Extend]
130
+ def extend(extendee, &block)
131
+ block.call(Extend.new(self, extendee.to_s))
132
+ self
133
+ end
134
+
135
+ # One extension field; the arguments of {Message#field} plus the extendee
136
+ # @return [Schema::Field]
137
+ def extension(name, number, type, type_name = nil, extendee:, label: :optional, default: nil, json_name: nil,
138
+ optional: false, options: nil)
139
+ field = @file_draft.new_field(self, name, number, type, type_name, @extensions.size, extendee.to_s, label,
140
+ default, json_name, optional, options, nil)
141
+ @extensions << field
142
+ field
143
+ end
144
+
145
+ # @api private
146
+ def qualify(name)
147
+ scope = scope_name
148
+ -(scope.empty? ? name : "#{scope}.#{name}")
149
+ end
150
+
151
+ def inspect
152
+ "#<#{self.class.name} #{@descriptor.full_name}>"
153
+ end
154
+ end
155
+
156
+ # A file under construction
157
+ class File < Scope
158
+ # @return [Schema::File] the file being built; frozen after {#freeze!}
159
+ attr_reader :descriptor
160
+
161
+ # @api private
162
+ def initialize(name, package:, syntax:, edition:, dependencies:, public_dependencies:, weak_dependencies:,
163
+ option_dependencies:, options:)
164
+ @file_draft = self
165
+ @messages = []
166
+ @enums = []
167
+ @extensions = []
168
+ @services = []
169
+ @fields = []
170
+ @methods = []
171
+ @duplicates = []
172
+ @options = {}.compare_by_identity
173
+ @dependencies = Array(dependencies).map { |d| -d.to_s }.freeze
174
+ @public = indexes(name, public_dependencies)
175
+ @weak = indexes(name, weak_dependencies)
176
+ @option_dependencies = Array(option_dependencies).map { |d| -d.to_s }.freeze
177
+ syntax, edition, proto_syntax = syntax_and_edition(name, syntax, edition)
178
+ @descriptor = Schema::File.new(name: -name.to_s, package: -package.to_s, syntax: syntax, edition: edition,
179
+ proto_syntax: proto_syntax)
180
+ @options[@descriptor] = options
181
+ end
182
+
183
+ # @return [String]
184
+ def scope_name
185
+ @descriptor.package
186
+ end
187
+
188
+ # A service
189
+ # @yieldparam s [Service]
190
+ # @return [Service]
191
+ def service(name, options: nil, &block)
192
+ name = name.to_s
193
+ descriptor = Schema::Service.new(name: name, full_name: qualify(name), file: @descriptor,
194
+ index: @services.size)
195
+ draft = Service.new(self, descriptor)
196
+ @options[descriptor] = options
197
+ @services << draft
198
+ register(descriptor)
199
+ block&.call(draft)
200
+ draft
201
+ end
202
+
203
+ # @param info [SourceInfo, Array<SourceInfo::Location>, nil]
204
+ def source_info=(info)
205
+ info = SourceInfo.new(info) if info.is_a?(Array)
206
+ @descriptor.__link(source_info: info)
207
+ end
208
+
209
+ # Sets (or replaces) the options of a descriptor of this file
210
+ # @param descriptor [Descriptor]
211
+ # @param options [Hash, nil]
212
+ def set_options(descriptor, options)
213
+ @options[descriptor] = options
214
+ end
215
+
216
+ # Finds dependencies in the pool, completes every message's lookup
217
+ # tables and resolves every type name, extendee and method type. Runs
218
+ # once; {#resolve} calls it when it has not run.
219
+ # @param pool [Pool] where the dependencies are
220
+ # @return [self]
221
+ # @raise [Error, ResolveError]
222
+ def link(pool)
223
+ return self if @linked
224
+ unless @duplicates.empty?
225
+ first, second = @duplicates.first
226
+ raise Error, "#{@descriptor.name}: \"#{first.full_name}\" is defined twice " \
227
+ "(#{kind_name(first)} and #{kind_name(second)})"
228
+ end
229
+ files = @dependencies.map do |dep|
230
+ pool.file(dep) || raise(Error, "#{@descriptor.name}: dependency \"#{dep}\" is not in the pool")
231
+ end
232
+ @descriptor.__dependencies(dependencies: @dependencies, public_dependencies: @public,
233
+ weak_dependencies: @weak, option_dependencies: @option_dependencies, dependency_files: files.freeze)
234
+ seal
235
+ link_fields(pool)
236
+ link_methods(pool)
237
+ @linked = true
238
+ self
239
+ end
240
+
241
+ # Links (when not done yet), then builds options, resolves features and
242
+ # parses defaults. May run again after options change.
243
+ # @param pool [Pool]
244
+ # @return [self]
245
+ def resolve(pool)
246
+ link(pool)
247
+ Resolution.new(@descriptor, @fields, @options, pool).call
248
+ @resolved = true
249
+ self
250
+ end
251
+
252
+ # Freezes the whole graph and makes it Ractor-shareable
253
+ # @return [Schema::File]
254
+ def freeze!
255
+ raise Error, "#{@descriptor.name}: resolve the draft before freezing it" unless @resolved
256
+ Freezer.deep_freeze(@descriptor)
257
+ Ractor.make_shareable(@descriptor)
258
+ end
259
+
260
+ # Completes every message's, enum's and service's lookup tables once
261
+ # nothing more will be added. {#link} does it; a caller that resolves
262
+ # names itself (the compiler) calls it first.
263
+ # @api private
264
+ def __seal
265
+ seal
266
+ self
267
+ end
268
+
269
+ # Sets what a field's type name resolved to, for a caller that
270
+ # resolves names itself; {#link} then leaves the field alone
271
+ # @api private
272
+ def __resolve_type(field, target)
273
+ type = field.declared_type || (target.is_a?(Schema::Message) ? :message : :enum)
274
+ kind = target.is_a?(Schema::Message) ? :message_type : :enum_type
275
+ field.__link(kind => target, :type_name => target.full_name, :declared_type => type, :type => type)
276
+ end
277
+
278
+ # @api private
279
+ def __resolve_extendee(field, target)
280
+ field.__link(extendee: target)
281
+ end
282
+
283
+ # @api private
284
+ def __resolve_rpc(method, input: nil, output: nil)
285
+ method.__link(input_type: input) if input
286
+ method.__link(output_type: output) if output
287
+ end
288
+
289
+ # @api private
290
+ def register(descriptor)
291
+ symbols = @descriptor.symbols
292
+ if (existing = symbols[descriptor.full_name])
293
+ @duplicates << [existing, descriptor]
294
+ else
295
+ symbols[descriptor.full_name] = descriptor
296
+ end
297
+ end
298
+
299
+ # @api private
300
+ def new_field(scope, name, number, type, type_name, index, extendee, label, default, json_name, optional, options,
301
+ oneof)
302
+ name = name.to_s
303
+ raise Error, "#{@descriptor.name}: field #{name} needs an integer number" unless number.is_a?(Integer)
304
+ label = (label || :optional).to_sym
305
+ raise Error, "#{@descriptor.name}: unknown label #{label.inspect} for #{name}" unless LABELS.include?(label)
306
+ if type.is_a?(String)
307
+ raise Error, "#{@descriptor.name}: #{name} has two type names" if type_name
308
+ type_name = type
309
+ type = nil
310
+ elsif !type.nil?
311
+ type = type.to_sym
312
+ raise Error, "#{@descriptor.name}: unknown field type #{type.inspect} for #{name}" unless TYPES.key?(type)
313
+ end
314
+ if type_name.nil? && !SCALARS.key?(type)
315
+ raise Error, "#{@descriptor.name}: #{name} needs a type name"
316
+ elsif type_name && SCALARS.key?(type)
317
+ raise Error, "#{@descriptor.name}: #{name} is a #{type} and cannot have a type name"
318
+ end
319
+ message = scope.is_a?(Message) ? scope.descriptor : nil
320
+ field = Field.new(
321
+ name: name, full_name: scope.qualify(name), number: number, label: label, type: type,
322
+ type_name: type_name && -type_name.to_s, extendee_name: extendee && -extendee, raw_default:
323
+ Draft.default_text(default)&.then { |t| -t }, json_name: -(json_name&.to_s || Schema.json_name(name)),
324
+ proto3_optional: optional ? true : false, extension: !extendee.nil?, containing_message: message,
325
+ file: @descriptor, index: index
326
+ )
327
+ @options[field] = options
328
+ @fields << [field, scope.scope_name]
329
+ register(field)
330
+ scope.__oneof_member(field, oneof) if extendee.nil? && (oneof || optional)
331
+ field
332
+ end
333
+
334
+ # @api private
335
+ def new_method(method, options)
336
+ @options[method] = options
337
+ @methods << method
338
+ register(method)
339
+ end
340
+
341
+ private
342
+
343
+ def kind_name(descriptor)
344
+ descriptor.class.name.split("::").last
345
+ end
346
+
347
+ def indexes(name, list)
348
+ deps = Array(@dependencies)
349
+ list = Array(list).map do |dep|
350
+ next dep if dep.is_a?(Integer) && dep >= 0 && dep < deps.size
351
+ index = deps.index(dep.to_s)
352
+ raise Error, "#{name}: #{dep.inspect} is not among the dependencies" unless index
353
+ index
354
+ end
355
+ list.empty? ? Freezer::EMPTY_ARRAY : list.freeze
356
+ end
357
+
358
+ def syntax_and_edition(name, syntax, edition)
359
+ syntax = syntax.to_sym if syntax.is_a?(String)
360
+ raise Error, "#{name}: unknown syntax #{syntax.inspect}" unless SYNTAXES.key?(syntax)
361
+ proto_syntax = syntax&.to_s
362
+ if edition.is_a?(Integer)
363
+ edition = Features::EDITION_NAMES.fetch(edition, edition)
364
+ elsif edition
365
+ edition = edition.to_sym
366
+ raise Error, "#{name}: unknown edition #{edition.inspect}" unless Features::EDITIONS.key?(edition)
367
+ end
368
+ if edition && syntax.nil?
369
+ syntax = :editions
370
+ proto_syntax = "editions"
371
+ end
372
+ resolved = SYNTAXES.fetch(syntax)
373
+ if resolved == :editions && edition.nil?
374
+ raise Error, "#{name}: syntax \"editions\" needs an edition"
375
+ elsif resolved != :editions && edition
376
+ raise Error, "#{name}: syntax #{proto_syntax.inspect} cannot have an edition"
377
+ end
378
+ [resolved, edition, proto_syntax && -proto_syntax]
379
+ end
380
+
381
+ def containing_message
382
+ nil
383
+ end
384
+
385
+ def seal
386
+ return if @sealed
387
+ @sealed = true
388
+ messages = @messages.map { |m| m.__seal(@options) }.freeze
389
+ enums = @enums.map { |e| e.__seal(@options) }.freeze
390
+ services = @services.map(&:__seal).freeze
391
+ extensions = @extensions.freeze
392
+ all = []
393
+ walk = lambda do |m|
394
+ all << m
395
+ m.nested_messages.each(&walk)
396
+ end
397
+ messages.each(&walk)
398
+ @descriptor.__link(
399
+ messages: messages, enums: enums, services: services, extensions: extensions,
400
+ all_messages: all.freeze, all_enums: (enums + all.flat_map(&:nested_enums)).freeze,
401
+ all_extensions: (extensions + all.flat_map(&:extensions)).freeze
402
+ )
403
+ end
404
+
405
+ def link_fields(pool)
406
+ @fields.each do |field, scope|
407
+ attrs = {}
408
+ if field.type_name && field.message_type.nil? && field.enum_type.nil?
409
+ target = resolve_name(pool, field.type_name, scope, field)
410
+ type = field.declared_type
411
+ case target
412
+ when Schema::Message
413
+ type ||= :message
414
+ unless type == :message || type == :group
415
+ raise ResolveError.new("#{@descriptor.name}: #{field.full_name}: \"#{field.type_name}\" is not an " \
416
+ "enum type", name: field.type_name, scope: scope)
417
+ end
418
+ attrs[:message_type] = target
419
+ when Schema::Enum
420
+ type ||= :enum
421
+ unless type == :enum
422
+ raise ResolveError.new("#{@descriptor.name}: #{field.full_name}: \"#{field.type_name}\" is not a " \
423
+ "message type", name: field.type_name, scope: scope)
424
+ end
425
+ attrs[:enum_type] = target
426
+ else
427
+ raise ResolveError.new("#{@descriptor.name}: #{field.full_name}: \"#{field.type_name}\" is not a type",
428
+ name: field.type_name, scope: scope)
429
+ end
430
+ attrs.merge!(type_name: target.full_name, declared_type: type, type: type)
431
+ end
432
+ if field.extension? && field.extendee.nil?
433
+ extendee = resolve_name(pool, field.extendee_name, scope, field)
434
+ unless extendee.is_a?(Schema::Message)
435
+ raise ResolveError.new("#{@descriptor.name}: #{field.full_name}: \"#{field.extendee_name}\" is not a " \
436
+ "message type", name: field.extendee_name, scope: scope)
437
+ end
438
+ attrs[:extendee] = extendee
439
+ end
440
+ field.__link(**attrs) unless attrs.empty?
441
+ end
442
+ end
443
+
444
+ def link_methods(pool)
445
+ @methods.each do |method|
446
+ next if method.input_type && method.output_type
447
+ scope = method.service.full_name
448
+ input = resolve_name(pool, method.input_type_name, scope, method)
449
+ output = resolve_name(pool, method.output_type_name, scope, method)
450
+ [[method.input_type_name, input], [method.output_type_name, output]].each do |written, found|
451
+ next if found.is_a?(Schema::Message)
452
+ raise ResolveError.new("#{@descriptor.name}: #{method.full_name}: \"#{written}\" is not a message type",
453
+ name: written, scope: scope)
454
+ end
455
+ method.__link(input_type: input, output_type: output)
456
+ end
457
+ end
458
+
459
+ def resolve_name(pool, name, scope, element)
460
+ Resolver.resolve(name, scope: scope, file: @descriptor, pool: pool)
461
+ rescue ResolveError => e
462
+ raise ResolveError.new("#{@descriptor.name}: #{element.full_name}: #{e.message}", name: e.name,
463
+ scope: e.scope)
464
+ end
465
+ end
466
+
467
+ # A message under construction: the block argument of {Scope#message}
468
+ class Message < Scope
469
+ # @return [Schema::Message]
470
+ attr_reader :descriptor
471
+
472
+ # @api private
473
+ def initialize(file_draft, descriptor, visibility, options)
474
+ @file_draft = file_draft
475
+ @descriptor = descriptor
476
+ @visibility = visibility
477
+ @messages = []
478
+ @enums = []
479
+ @extensions = []
480
+ @fields = []
481
+ @oneofs = []
482
+ @oneof_names = {}
483
+ @synthetic = []
484
+ @ranges = []
485
+ @reserved_ranges = []
486
+ @reserved_names = []
487
+ file_draft.set_options(descriptor, options)
488
+ end
489
+
490
+ # @return [String]
491
+ def scope_name
492
+ @descriptor.full_name
493
+ end
494
+
495
+ # A field
496
+ # @param name [String, Symbol]
497
+ # @param number [Integer]
498
+ # @param type [Symbol, String] :int32, :string, ... :message, :enum or :group (then
499
+ # give `type_name`), or a type name whose kind is found when linking
500
+ # @param type_name [String, nil] the message or enum type as written, e.g. ".acme.Person"
501
+ # @param label [Symbol] :optional, :required or :repeated
502
+ # @param default [String, Object, nil] the default; text is taken as protoc stores it
503
+ # @param json_name [String, nil] when not the one derived from the name
504
+ # @param optional [Boolean] a proto3 `optional` field; its synthetic oneof is made here
505
+ # unless `oneof:` names one
506
+ # @param oneof [String, Symbol, Schema::Oneof, nil] the oneof the field belongs to
507
+ # @param options [Hash, nil]
508
+ # @return [Schema::Field]
509
+ def field(name, number, type, type_name = nil, label: :optional, default: nil, json_name: nil, optional: false,
510
+ oneof: nil, options: nil)
511
+ field = @file_draft.new_field(self, name, number, type, type_name, @fields.size, nil, label, default,
512
+ json_name, optional, options, oneof)
513
+ @fields << field
514
+ field
515
+ end
516
+
517
+ # A `map<K, V>` field and the entry message protoc synthesizes for it
518
+ # @param key [Symbol] the key type
519
+ # @param value [Symbol, String] a scalar type, or the value's message or enum type name
520
+ # @param value_type_name [String, nil] the type name when `value` is :message or :enum
521
+ # @param options [Hash, nil] the field's options; its `features` go to the key and value too
522
+ # @return [Schema::Field]
523
+ def map(name, number, key:, value:, value_type_name: nil, json_name: nil, options: nil)
524
+ name = name.to_s
525
+ if value.is_a?(String)
526
+ value_type_name = value
527
+ value = nil
528
+ end
529
+ features = options && (options[:features] || options["features"])
530
+ entry_options = features && {features: features}
531
+ entry = message(Draft.map_entry_name(name), options: {map_entry: true})
532
+ entry.field("key", 1, key, options: entry_options)
533
+ entry.field("value", 2, value, value_type_name, options: entry_options)
534
+ field(name, number, :message, ".#{entry.descriptor.full_name}", label: :repeated, json_name: json_name,
535
+ options: options)
536
+ end
537
+
538
+ # A proto2 group: a nested message and a field of type group named after it
539
+ # @param message_options [Hash, nil] the nested message's options
540
+ # @yieldparam g [Message]
541
+ # @return [Schema::Field]
542
+ def group(name, number, label: :optional, json_name: nil, oneof: nil, options: nil, message_options: nil,
543
+ &block)
544
+ nested = message(name.to_s, options: message_options, &block)
545
+ field(Draft.group_field_name(name), number, :group, ".#{nested.descriptor.full_name}", label: label,
546
+ json_name: json_name, oneof: oneof, options: options)
547
+ end
548
+
549
+ # A oneof. Fields given in the block belong to it; fields elsewhere may
550
+ # name it with `oneof:`. Declaring it again only sets its options.
551
+ # @yieldparam o [Oneof]
552
+ # @return [Schema::Oneof]
553
+ def oneof(name, options: nil, &block)
554
+ oneof = declare_oneof(name.to_s)
555
+ @file_draft.set_options(oneof, options) if options
556
+ block&.call(Oneof.new(self, oneof))
557
+ oneof
558
+ end
559
+
560
+ # One extension range, inclusive
561
+ # @param range [Range, Integer]
562
+ # @return [Schema::ExtensionRange]
563
+ def extension_range(range, options: nil)
564
+ first, last = Draft.bounds(range)
565
+ descriptor = Schema::ExtensionRange.new(containing_message: @descriptor, index: @ranges.size,
566
+ range: first..last)
567
+ @file_draft.set_options(descriptor, options)
568
+ @ranges << descriptor
569
+ descriptor
570
+ end
571
+
572
+ # Extension ranges, inclusive, sharing options
573
+ # @param ranges [Array<Range, Integer>]
574
+ def extensions(*ranges, options: nil)
575
+ ranges.each { |r| extension_range(r, options: options) }
576
+ self
577
+ end
578
+
579
+ # Reserved numbers (Integers or inclusive Ranges) and names (Strings or Symbols)
580
+ def reserved(*items)
581
+ items.each do |item|
582
+ if item.is_a?(String) || item.is_a?(Symbol)
583
+ @reserved_names << -item.to_s
584
+ else
585
+ first, last = Draft.bounds(item)
586
+ @reserved_ranges << (first..last)
587
+ end
588
+ end
589
+ self
590
+ end
591
+
592
+ # @api private
593
+ def __oneof_member(field, oneof)
594
+ if oneof.nil?
595
+ @synthetic << field
596
+ return
597
+ end
598
+ oneof = declare_oneof(oneof.to_s) unless oneof.is_a?(Schema::Oneof)
599
+ oneof.fields << field
600
+ field.__link(oneof: oneof)
601
+ end
602
+
603
+ # @api private
604
+ def __seal(options)
605
+ unless @synthetic.empty?
606
+ taken = @fields.map(&:name) + @oneofs.map(&:name)
607
+ names = Draft.synthetic_oneof_names(@synthetic.map(&:name), taken)
608
+ @synthetic.zip(names) { |field, name| __oneof_member(field, declare_oneof(name)) }
609
+ @synthetic = []
610
+ end
611
+ @oneofs.each do |oneof|
612
+ members = oneof.fields.freeze
613
+ oneof.__link(synthetic: members.size == 1 && members.first.proto3_optional?)
614
+ end
615
+ nested = @messages.map { |m| m.__seal(options) }.freeze
616
+ enums = @enums.map { |e| e.__seal(options) }.freeze
617
+ @descriptor.__link(reserved_ranges: frozen(@reserved_ranges), reserved_names: frozen(@reserved_names),
618
+ visibility: @visibility)
619
+ @descriptor.__members(fields: frozen(@fields), oneofs: frozen(@oneofs), nested_messages: nested,
620
+ nested_enums: enums, extensions: frozen(@extensions), extension_range_descriptors: frozen(@ranges))
621
+ end
622
+
623
+ private
624
+
625
+ def containing_message
626
+ @descriptor
627
+ end
628
+
629
+ def declare_oneof(name)
630
+ existing = @oneof_names[name]
631
+ return existing if existing
632
+ oneof = Schema::Oneof.new(name: -name, full_name: -"#{@descriptor.full_name}.#{name}",
633
+ containing_message: @descriptor, index: @oneofs.size)
634
+ @oneofs << oneof
635
+ @oneof_names[name] = oneof
636
+ @file_draft.set_options(oneof, nil)
637
+ @file_draft.register(oneof)
638
+ oneof
639
+ end
640
+
641
+ def frozen(list)
642
+ list.empty? ? Freezer::EMPTY_ARRAY : list.dup.freeze
643
+ end
644
+ end
645
+
646
+ # The block argument of {Message#oneof}
647
+ class Oneof
648
+ # @api private
649
+ def initialize(message, oneof)
650
+ @message = message
651
+ @oneof = oneof
652
+ end
653
+
654
+ # A member field; the arguments of {Message#field}
655
+ def field(name, number, type, type_name = nil, **kwargs)
656
+ @message.field(name, number, type, type_name, oneof: @oneof, **kwargs)
657
+ end
658
+
659
+ # A member group; the arguments of {Message#group}
660
+ def group(name, number, **kwargs, &block)
661
+ @message.group(name, number, oneof: @oneof, **kwargs, &block)
662
+ end
663
+ end
664
+
665
+ # The block argument of {Scope#extend}
666
+ class Extend
667
+ # @api private
668
+ def initialize(scope, extendee)
669
+ @scope = scope
670
+ @extendee = extendee
671
+ end
672
+
673
+ # An extension field; the arguments of {Message#field}
674
+ # @return [Schema::Field]
675
+ def field(name, number, type, type_name = nil, **kwargs)
676
+ @scope.extension(name, number, type, type_name, extendee: @extendee, **kwargs)
677
+ end
678
+ end
679
+
680
+ # An enum under construction: the block argument of {Scope#enum}
681
+ class Enum
682
+ def inspect
683
+ "#<#{self.class.name} #{@descriptor.full_name}>"
684
+ end
685
+
686
+ # @return [Schema::Enum]
687
+ attr_reader :descriptor
688
+
689
+ # @api private
690
+ def initialize(file_draft, descriptor, scope, visibility, options)
691
+ @file_draft = file_draft
692
+ @descriptor = descriptor
693
+ @scope = scope
694
+ @visibility = visibility
695
+ @values = []
696
+ @reserved_ranges = []
697
+ @reserved_names = []
698
+ file_draft.set_options(descriptor, options)
699
+ end
700
+
701
+ # A value. Its full name is a sibling of the enum's, as in C++.
702
+ # @return [Schema::EnumValue]
703
+ def value(name, number, options: nil)
704
+ name = name.to_s
705
+ raise Error, "#{@descriptor.full_name}: value #{name} needs an integer number" unless number.is_a?(Integer)
706
+ value = EnumValue.new(name: -name, full_name: -(@scope.empty? ? name : "#{@scope}.#{name}"),
707
+ number: number, enum: @descriptor, index: @values.size)
708
+ @file_draft.set_options(value, options)
709
+ @file_draft.register(value)
710
+ @values << value
711
+ value
712
+ end
713
+
714
+ # Reserved numbers (Integers or inclusive Ranges) and names
715
+ def reserved(*items)
716
+ items.each do |item|
717
+ if item.is_a?(String) || item.is_a?(Symbol)
718
+ @reserved_names << -item.to_s
719
+ else
720
+ first, last = Draft.bounds(item)
721
+ @reserved_ranges << (first..last)
722
+ end
723
+ end
724
+ self
725
+ end
726
+
727
+ # @api private
728
+ def __seal(_options)
729
+ @descriptor.__link(
730
+ reserved_ranges: @reserved_ranges.empty? ? Freezer::EMPTY_ARRAY : @reserved_ranges.dup.freeze,
731
+ reserved_names: @reserved_names.empty? ? Freezer::EMPTY_ARRAY : @reserved_names.dup.freeze,
732
+ visibility: @visibility
733
+ )
734
+ @descriptor.__values(@values.empty? ? Freezer::EMPTY_ARRAY : @values.dup.freeze)
735
+ end
736
+ end
737
+
738
+ # A service under construction: the block argument of {File#service}
739
+ class Service
740
+ def inspect
741
+ "#<#{self.class.name} #{@descriptor.full_name}>"
742
+ end
743
+
744
+ # @return [Schema::Service]
745
+ attr_reader :descriptor
746
+
747
+ # @api private
748
+ def initialize(file_draft, descriptor)
749
+ @file_draft = file_draft
750
+ @descriptor = descriptor
751
+ @rpcs = []
752
+ end
753
+
754
+ # @param name [String, Symbol]
755
+ # @param input_type [String] as written, e.g. ".acme.Request"
756
+ # @param output_type [String]
757
+ # @return [Schema::Method]
758
+ def rpc(name, input_type, output_type, client_streaming: false, server_streaming: false, options: nil)
759
+ name = name.to_s
760
+ method = Schema::Method.new(name: -name, full_name: -"#{@descriptor.full_name}.#{name}",
761
+ service: @descriptor, index: @rpcs.size, input_type_name: -input_type.to_s,
762
+ output_type_name: -output_type.to_s, client_streaming: client_streaming ? true : false,
763
+ server_streaming: server_streaming ? true : false)
764
+ @file_draft.new_method(method, options)
765
+ @rpcs << method
766
+ method
767
+ end
768
+
769
+ # @api private
770
+ def __seal
771
+ @descriptor.__rpcs(@rpcs.empty? ? Freezer::EMPTY_ARRAY : @rpcs.dup.freeze)
772
+ end
773
+ end
774
+ end
775
+ end
776
+ end
777
+
778
+ require_relative "draft/copy"
779
+ require_relative "draft/resolution"