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,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ module Schema
5
+ # Parses `FieldDescriptorProto.default_value` text into Ruby values
6
+ # @api private
7
+ module DefaultValue
8
+ INTEGER_TYPES = %i[int32 int64 uint32 uint64 sint32 sint64 fixed32 fixed64 sfixed32 sfixed64].freeze
9
+ SIMPLE_ESCAPES = {
10
+ "a" => 7, "b" => 8, "f" => 12, "n" => 10, "r" => 13, "t" => 9, "v" => 11,
11
+ "\\" => 92, "'" => 39, "\"" => 34, "?" => 63
12
+ }.freeze
13
+
14
+ # protoc's SimpleDtoa: the shortest of %.15g and %.17g that reads back exactly
15
+ # @param value [Float]
16
+ # @return [String]
17
+ def self.simple_dtoa(value)
18
+ return "inf" if value == Float::INFINITY
19
+ return "-inf" if value == -Float::INFINITY
20
+ return "nan" if value.nan?
21
+ text = format("%.15g", value)
22
+ # the 15-digit form is kept only when it reads back as the very same double
23
+ text = format("%.17g", value) unless [Float(text)].pack("G") == [value].pack("G")
24
+ text
25
+ end
26
+
27
+ # @param text [String] as protoc writes it
28
+ # @param type [Symbol] the field's short type
29
+ # @param enum [Enum, nil] the field's enum type
30
+ # @return [Object]
31
+ def self.parse(text, type, enum)
32
+ case type
33
+ when *INTEGER_TYPES then parse_integer(text)
34
+ when :double then parse_float(text)
35
+ when :float then to_float32(parse_float(text))
36
+ when :bool
37
+ case text
38
+ when "true" then true
39
+ when "false" then false
40
+ else raise Error, "invalid bool default #{text.inspect}"
41
+ end
42
+ when :string then text
43
+ when :bytes then unescape_bytes(text)
44
+ when :enum
45
+ raise Error, "enum default #{text.inspect} is not a value of #{enum.full_name}" unless enum.value(text)
46
+ text.to_sym
47
+ else
48
+ raise Error, "a #{type} field cannot have a default value"
49
+ end
50
+ end
51
+
52
+ def self.parse_integer(text)
53
+ if text.match?(/\A-?0[xX]\h+\z/)
54
+ Integer(text, 16)
55
+ else
56
+ Integer(text, 10)
57
+ end
58
+ rescue ArgumentError
59
+ raise Error, "invalid integer default #{text.inspect}"
60
+ end
61
+
62
+ def self.parse_float(text)
63
+ case text
64
+ when "inf", "infinity" then Float::INFINITY
65
+ when "-inf", "-infinity" then -Float::INFINITY
66
+ when "nan", "-nan" then Float::NAN
67
+ else Float(text)
68
+ end
69
+ rescue ArgumentError
70
+ raise Error, "invalid floating point default #{text.inspect}"
71
+ end
72
+
73
+ # The value a float field actually holds: the nearest single-precision value
74
+ def self.to_float32(value)
75
+ return value if value.nan? || value.infinite?
76
+ [value].pack("e").unpack1("e")
77
+ end
78
+
79
+ # Undoes protoc's CEscape: simple escapes, 1-3 digit octal, \x hex
80
+ def self.unescape_bytes(text)
81
+ text = text.b
82
+ out = "".b
83
+ i = 0
84
+ n = text.bytesize
85
+ while i < n
86
+ byte = text.getbyte(i)
87
+ if byte != 92 || i + 1 >= n # backslash
88
+ out << byte
89
+ i += 1
90
+ next
91
+ end
92
+ c = text.byteslice(i + 1, 1)
93
+ if (simple = SIMPLE_ESCAPES[c])
94
+ out << simple
95
+ i += 2
96
+ elsif c.match?(/[0-7]/)
97
+ digits = text.byteslice(i + 1, 3)[/\A[0-7]{1,3}/]
98
+ out << (digits.to_i(8) & 0xFF)
99
+ i += 1 + digits.bytesize
100
+ elsif c == "x" || c == "X"
101
+ digits = text.byteslice(i + 2, 2).to_s[/\A\h{1,2}/]
102
+ raise Error, "invalid \\x escape in bytes default #{text.inspect}" unless digits
103
+ out << digits.to_i(16)
104
+ i += 2 + digits.bytesize
105
+ else
106
+ raise Error, "invalid escape \\#{c} in bytes default #{text.inspect}"
107
+ end
108
+ end
109
+ out
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ module Schema
5
+ # What every descriptor has. Made by a {Draft}, frozen and shareable once it is finished.
6
+ class Descriptor
7
+ # @return [String] the simple name
8
+ attr_reader :name
9
+ # @return [String] the fully qualified name, without a leading dot
10
+ attr_reader :full_name
11
+ # @return [Integer] position among its siblings, in declaration order
12
+ attr_reader :index
13
+ # @return [Features] resolved features
14
+ attr_reader :features
15
+
16
+ # @return [Options] declared options; once resolved, also the resolved {Options#features}
17
+ def options
18
+ @options || Options::UNRESOLVED.fetch(self.class)
19
+ end
20
+
21
+ # Sets attributes that only become known while linking. Drafts are the
22
+ # callers; everything is frozen once the draft is finished.
23
+ # @api private
24
+ def __link(**attrs)
25
+ attrs.each { |key, value| instance_variable_set(:"@#{key}", value) }
26
+ self
27
+ end
28
+
29
+ def inspect
30
+ "#<#{self.class.name} #{full_name}>"
31
+ end
32
+ alias_method :to_s, :inspect
33
+
34
+ # `to_proto` as a Hash, for reading; nothing builds from it
35
+ # @return [Hash]
36
+ def to_h
37
+ to_proto.to_h
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ module Schema
5
+ module Draft
6
+ # A copy of a built file for another pool, its imports taken from that
7
+ # pool, with each descriptor's options replaced by what the block
8
+ # returns (a MessageValue, or nil for none)
9
+ # @param file [Schema::File]
10
+ # @param pool [Pool] holds the copy's imports
11
+ # @param source_info [SourceInfo, nil]
12
+ # @yieldparam descriptor [Descriptor]
13
+ # @return [Schema::File] frozen; not added to the pool
14
+ def self.copy(file, pool:, source_info: file.source_info, &options)
15
+ Copy.new(file, options).call(pool, source_info)
16
+ end
17
+
18
+ # @api private
19
+ class Copy
20
+ def initialize(file, options)
21
+ @file = file
22
+ @options = options
23
+ end
24
+
25
+ def call(pool, source_info)
26
+ file = @file
27
+ syntax = file.proto_syntax&.to_sym
28
+ draft = Schema::File.draft(file.name, package: file.package, syntax: syntax, edition: file.edition,
29
+ dependencies: file.dependencies, public_dependencies: file.public_dependencies,
30
+ weak_dependencies: file.weak_dependencies, option_dependencies: file.option_dependencies,
31
+ options: options(file))
32
+ draft.source_info = source_info if source_info
33
+ file.messages.each { |m| message(draft, m) }
34
+ file.enums.each { |e| enum(draft, e) }
35
+ file.extensions.each { |f| field(draft, f, nil) }
36
+ file.services.each do |service|
37
+ draft.service(service.name, options: options(service)) do |s|
38
+ service.rpcs.each do |m|
39
+ s.rpc(m.name, ".#{m.input_type.full_name}", ".#{m.output_type.full_name}",
40
+ client_streaming: m.client_streaming?, server_streaming: m.server_streaming?, options: options(m))
41
+ end
42
+ end
43
+ end
44
+ draft.resolve(pool).freeze!
45
+ end
46
+
47
+ private
48
+
49
+ def options(descriptor)
50
+ @options.call(descriptor)
51
+ end
52
+
53
+ def message(scope, message)
54
+ scope.message(message.name, visibility: message.visibility, options: options(message)) do |m|
55
+ oneofs = message.oneofs.map { |o| m.oneof(o.name, options: options(o)) }
56
+ message.fields.each { |f| field(m, f, f.oneof && oneofs[f.oneof.index]) }
57
+ message.nested_messages.each { |n| message(m, n) }
58
+ message.nested_enums.each { |e| enum(m, e) }
59
+ message.extensions.each { |f| field(m, f, nil) }
60
+ message.extension_range_descriptors.each { |r| m.extension_range(r.range, options: options(r)) }
61
+ message.reserved_ranges.each { |r| m.reserved(r) }
62
+ m.reserved(*message.reserved_names)
63
+ end
64
+ end
65
+
66
+ def field(scope, field, oneof)
67
+ type_name = field.type_name && ".#{field.type_name}"
68
+ kwargs = {label: field.declared_label, default: field.raw_default, json_name: field.json_name,
69
+ optional: field.proto3_optional?, options: options(field)}
70
+ if field.extension?
71
+ scope.extension(field.name, field.number, field.declared_type, type_name,
72
+ extendee: ".#{field.extendee.full_name}", **kwargs)
73
+ else
74
+ scope.field(field.name, field.number, field.declared_type, type_name, oneof: oneof, **kwargs)
75
+ end
76
+ end
77
+
78
+ def enum(scope, enum)
79
+ scope.enum(enum.name, visibility: enum.visibility, options: options(enum)) do |e|
80
+ enum.values.each { |v| e.value(v.name, v.number, options: options(v)) }
81
+ enum.reserved_ranges.each { |r| e.reserved(r) }
82
+ e.reserved(*enum.reserved_names)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ module Schema
5
+ module Draft
6
+ # Options, features and defaults of a linked file, as protoc's
7
+ # DescriptorBuilder resolves them once options are known
8
+ # @api private
9
+ class Resolution
10
+ OPTIONS = {
11
+ Schema::File => FileOptions, Schema::Message => MessageOptions, Field => FieldOptions,
12
+ Schema::Oneof => OneofOptions, Schema::Enum => EnumOptions, EnumValue => EnumValueOptions,
13
+ Schema::Service => ServiceOptions, Schema::Method => MethodOptions,
14
+ ExtensionRange => ExtensionRangeOptions
15
+ }.freeze
16
+
17
+ def initialize(file, fields, specs, pool)
18
+ @file = file
19
+ @fields = fields
20
+ @specs = specs
21
+ @pool = pool
22
+ @features = {}
23
+ @empty = {}
24
+ end
25
+
26
+ def call
27
+ finder = extension_finder
28
+ @declared = {}.compare_by_identity
29
+ @specs.each do |descriptor, spec|
30
+ options = OPTIONS.fetch(descriptor.class).build(spec, finder, descriptor.full_name)
31
+ @declared[descriptor] = options if options
32
+ end
33
+ resolve_features
34
+ parse_defaults
35
+ end
36
+
37
+ private
38
+
39
+ # Custom options are extensions of this file, its imports and theirs,
40
+ # or else of whatever the pool holds (`import option` files)
41
+ def extension_finder
42
+ files = []
43
+ seen = {}
44
+ queue = [@file]
45
+ until queue.empty?
46
+ f = queue.shift
47
+ next if seen[f.name]
48
+ seen[f.name] = true
49
+ files << f
50
+ queue.concat(f.dependency_files)
51
+ end
52
+ lambda do |name|
53
+ files.each do |f|
54
+ found = f.find_extension(name)
55
+ return found if found
56
+ end
57
+ found = @pool&.find_symbol(name)
58
+ found if found.is_a?(Field) && found.extension?
59
+ end
60
+ end
61
+
62
+ def declared(descriptor)
63
+ @declared[descriptor]
64
+ end
65
+
66
+ # Options of this file's messages are bound in tree order, so a
67
+ # message type elsewhere in the file may not be bound yet
68
+ def map_entry?(message)
69
+ return message.map_entry? unless message.file.equal?(@file)
70
+ declared(message)&.[](:map_entry) == true
71
+ end
72
+
73
+ def bind(descriptor, features)
74
+ options = @declared[descriptor]
75
+ bound = if options
76
+ options.with_features(features)
77
+ else
78
+ klass = OPTIONS.fetch(descriptor.class)
79
+ (@empty[klass] ||= {})[features] ||= klass.new(features: features)
80
+ end
81
+ descriptor.__link(features: features, options: bound)
82
+ end
83
+
84
+ def resolve_features
85
+ file_features = intern(Features.defaults(@file.edition_number).merge(declared(@file)&.[](:features)))
86
+ bind(@file, file_features)
87
+ @file.messages.each { |m| resolve_message_features(m, file_features) }
88
+ @file.enums.each { |e| resolve_enum_features(e, file_features) }
89
+ @file.extensions.each { |f| resolve_field_features(f, file_features) }
90
+ @file.services.each do |service|
91
+ service_features = inherit(file_features, service)
92
+ service.rpcs.each { |m| inherit(service_features, m) }
93
+ end
94
+ end
95
+
96
+ def resolve_message_features(message, parent)
97
+ features = inherit(parent, message)
98
+ message.oneofs.each { |o| inherit(features, o) }
99
+ message.fields.each { |f| resolve_field_features(f, f.oneof ? f.oneof.features : features) }
100
+ message.extension_range_descriptors.each { |r| inherit(features, r) }
101
+ message.nested_messages.each { |m| resolve_message_features(m, features) }
102
+ message.nested_enums.each { |e| resolve_enum_features(e, features) }
103
+ message.extensions.each { |f| resolve_field_features(f, features) }
104
+ end
105
+
106
+ def resolve_enum_features(enum, parent)
107
+ features = inherit(parent, enum)
108
+ enum.values.each { |v| inherit(features, v) }
109
+ end
110
+
111
+ # protoc's InferLegacyProtoFeatures for proto2 and proto3 files, then the
112
+ # label and type adjustments of DescriptorBuilder::PostProcessFieldFeatures
113
+ def resolve_field_features(field, parent)
114
+ options = declared(field)
115
+ features = parent.merge(options&.[](:features))
116
+ if @file.syntax != :editions
117
+ legacy = nil
118
+ legacy = {field_presence: :LEGACY_REQUIRED} if field.declared_label == :required
119
+ (legacy ||= {})[:message_encoding] = :DELIMITED if field.declared_type == :group
120
+ if options&.key?(:packed)
121
+ (legacy ||= {})[:repeated_field_encoding] = options[:packed] ? :PACKED : :EXPANDED
122
+ end
123
+ (legacy ||= {})[:field_presence] = :EXPLICIT if field.proto3_optional?
124
+ features = features.with(**legacy) if legacy
125
+ end
126
+ features = intern(features)
127
+ label = field.declared_label
128
+ label = :required if label == :optional && features.field_presence == :LEGACY_REQUIRED
129
+ type = field.declared_type
130
+ if type == :message && features.message_encoding == :DELIMITED
131
+ holder = field.extension? ? field.extendee : field.containing_message
132
+ type = :group unless map_entry?(holder) || map_entry?(field.message_type)
133
+ end
134
+ bind(field, features)
135
+ field.__link(label: label, type: type)
136
+ end
137
+
138
+ def inherit(parent, descriptor)
139
+ features = intern(parent.merge(declared(descriptor)&.[](:features)))
140
+ bind(descriptor, features)
141
+ features
142
+ end
143
+
144
+ def intern(features)
145
+ @features[features] ||= features
146
+ end
147
+
148
+ def parse_defaults
149
+ @fields.each do |field, _scope|
150
+ value = nil
151
+ if (text = field.raw_default)
152
+ begin
153
+ value = DefaultValue.parse(text, field.type, field.enum_type)
154
+ rescue Error => e
155
+ raise Error, "#{@file.name}: #{field.full_name}: #{e.message}"
156
+ end
157
+ end
158
+ implicit = if !value.nil?
159
+ value
160
+ elsif field.type == :enum
161
+ field.enum_type.values.first&.name&.to_sym
162
+ else
163
+ Field::ZERO[field.type]
164
+ end
165
+ field.__link(default_value: value, default: implicit)
166
+ end
167
+ end
168
+ end
169
+ end
170
+
171
+ class Options
172
+ # What a descriptor answers before its options are resolved: an empty
173
+ # instance of its options class
174
+ UNRESOLVED = Draft::Resolution::OPTIONS.transform_values { |klass| klass.new.freeze }.freeze
175
+ end
176
+ end
177
+ end