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,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ module Schema
5
+ # An enum type
6
+ class Enum < Descriptor
7
+ # @return [File]
8
+ attr_reader :file
9
+ # @return [Message, nil]
10
+ attr_reader :containing_message
11
+ # @return [Array<EnumValue>] declaration order
12
+ attr_reader :values
13
+ # @return [Hash{Integer => EnumValue}] the first declared value wins for aliases
14
+ attr_reader :values_by_number
15
+ # @return [Hash{String => EnumValue}]
16
+ attr_reader :values_by_name
17
+ # @return [Array<Range>] inclusive
18
+ attr_reader :reserved_ranges
19
+ # @return [Array<String>]
20
+ attr_reader :reserved_names
21
+ # @return [Symbol, nil] declared `visibility`: :local or :export (edition 2024), nil when not declared
22
+ attr_reader :visibility
23
+
24
+ # @api private
25
+ def initialize(name:, full_name:, file:, containing_message:, index:)
26
+ @name = name
27
+ @full_name = full_name
28
+ @file = file
29
+ @containing_message = containing_message
30
+ @index = index
31
+ @reserved_ranges = Freezer::EMPTY_ARRAY
32
+ @reserved_names = Freezer::EMPTY_ARRAY
33
+ @visibility = nil
34
+ end
35
+
36
+ # @api private
37
+ def __values(values)
38
+ @values = values
39
+ @values_by_number = {}
40
+ @values_by_name = {}
41
+ values.each do |v|
42
+ @values_by_number[v.number] ||= v
43
+ @values_by_name[v.name] ||= v
44
+ end
45
+ @values_by_number.freeze
46
+ @values_by_name.freeze
47
+ self
48
+ end
49
+
50
+ # @param key [Integer, String, Symbol] a value number or name
51
+ # @return [EnumValue, nil]
52
+ def value(key)
53
+ key.is_a?(Integer) ? @values_by_number[key] : @values_by_name[key.to_s]
54
+ end
55
+
56
+ # @return [Boolean]
57
+ def allow_alias?
58
+ options.allow_alias?
59
+ end
60
+
61
+ # @return [Boolean] unknown numbers go to unknown fields (proto2, editions
62
+ # `enum_type = CLOSED`) instead of being kept in the field (proto3, `OPEN`)
63
+ def closed?
64
+ @features.enum_type == :CLOSED
65
+ end
66
+
67
+ # @return [Boolean]
68
+ def open?
69
+ !closed?
70
+ end
71
+
72
+ # @return [Protostar::Google::Protobuf::EnumDescriptorProto]
73
+ def to_proto
74
+ Proto::Writer.enum(self)
75
+ end
76
+ end
77
+
78
+ # A value of an enum. Its {#full_name} lives in the enum's enclosing scope,
79
+ # as in protoc: value `FOO` of `pkg.Color` is `pkg.FOO`.
80
+ class EnumValue < Descriptor
81
+ # @return [Integer]
82
+ attr_reader :number
83
+ # @return [Enum]
84
+ attr_reader :enum
85
+
86
+ # @api private
87
+ def initialize(name:, full_name:, number:, enum:, index:)
88
+ @name = name
89
+ @full_name = full_name
90
+ @number = number
91
+ @enum = enum
92
+ @index = index
93
+ end
94
+
95
+ # @return [File]
96
+ def file
97
+ @enum.file
98
+ end
99
+
100
+ # @return [Protostar::Google::Protobuf::EnumValueDescriptorProto]
101
+ def to_proto
102
+ Proto::Writer.enum_value(self)
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ module Schema
5
+ # One `extensions` declaration of a message, with its options and features
6
+ class ExtensionRange < Descriptor
7
+ # @return [Message]
8
+ attr_reader :containing_message
9
+ # @return [Range] inclusive
10
+ attr_reader :range
11
+
12
+ # @api private
13
+ def initialize(containing_message:, index:, range:)
14
+ @containing_message = containing_message
15
+ @index = index
16
+ @range = range
17
+ @name = "#{range.begin}..#{range.end}"
18
+ @full_name = "#{containing_message.full_name}.extensions(#{@name})"
19
+ end
20
+
21
+ # @return [File]
22
+ def file
23
+ @containing_message.file
24
+ end
25
+
26
+ # @return [Integer] first extension number, inclusive
27
+ def start
28
+ @range.begin
29
+ end
30
+
31
+ # @return [Integer] last extension number, inclusive
32
+ def end
33
+ @range.end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ module Schema
5
+ # Resolved editions features of a descriptor. proto2 and proto3 files are
6
+ # resolved as the editions EDITION_PROTO2 and EDITION_PROTO3, the way protoc
7
+ # does internally, so every descriptor has a complete set.
8
+ #
9
+ # Custom (extension) features such as `pb.cpp` are carried in {#extensions}
10
+ # as merged explicit values; their defaults are not known to this model.
11
+ class Features
12
+ # The Edition enum from descriptor.proto (lines 45-82)
13
+ EDITIONS = {
14
+ EDITION_UNKNOWN: 0,
15
+ EDITION_LEGACY: 900,
16
+ EDITION_PROTO2: 998,
17
+ EDITION_PROTO3: 999,
18
+ EDITION_2023: 1000,
19
+ EDITION_2024: 1001,
20
+ EDITION_2026: 1002,
21
+ EDITION_UNSTABLE: 9999,
22
+ EDITION_1_TEST_ONLY: 1,
23
+ EDITION_2_TEST_ONLY: 2,
24
+ EDITION_99997_TEST_ONLY: 99997,
25
+ EDITION_99998_TEST_ONLY: 99998,
26
+ EDITION_99999_TEST_ONLY: 99999,
27
+ EDITION_MAX: 0x7FFFFFFF
28
+ }.freeze
29
+
30
+ # Edition numbers to their enum names
31
+ EDITION_NAMES = EDITIONS.invert.freeze
32
+
33
+ # The `edition_defaults` annotations on FeatureSet in descriptor.proto,
34
+ # as [edition, value] pairs in ascending edition order. A feature takes the
35
+ # value of the last pair whose edition is at or before the file's edition.
36
+ DEFAULTS = {
37
+ # descriptor.proto lines 1074-1076
38
+ field_presence: [[:EDITION_LEGACY, :EXPLICIT], [:EDITION_PROTO3, :IMPLICIT], [:EDITION_2023, :EXPLICIT]],
39
+ # lines 1091-1092
40
+ enum_type: [[:EDITION_LEGACY, :CLOSED], [:EDITION_PROTO3, :OPEN]],
41
+ # lines 1107-1108
42
+ repeated_field_encoding: [[:EDITION_LEGACY, :EXPANDED], [:EDITION_PROTO3, :PACKED]],
43
+ # lines 1124-1125
44
+ utf8_validation: [[:EDITION_LEGACY, :NONE], [:EDITION_PROTO3, :VERIFY]],
45
+ # line 1140
46
+ message_encoding: [[:EDITION_LEGACY, :LENGTH_PREFIXED]],
47
+ # lines 1156-1157
48
+ json_format: [[:EDITION_LEGACY, :LEGACY_BEST_EFFORT], [:EDITION_PROTO3, :ALLOW]],
49
+ # lines 1180-1182
50
+ enforce_naming_style: [[:EDITION_LEGACY, :STYLE_LEGACY], [:EDITION_2024, :STYLE2024], [:EDITION_2026, :STYLE2026]],
51
+ # lines 1212-1214
52
+ default_symbol_visibility: [[:EDITION_LEGACY, :EXPORT_ALL], [:EDITION_2024, :EXPORT_TOP_LEVEL], [:EDITION_2026, :STRICT]],
53
+ # lines 1241-1245
54
+ enforce_proto_limits: [[:EDITION_LEGACY, :LEGACY_NO_EXPLICIT_LIMITS], [:EDITION_2026, :PROTO_LIMITS2026]]
55
+ }.transform_values { |pairs| pairs.map(&:freeze).freeze }.freeze
56
+
57
+ # Names of the core features, in FeatureSet field number order
58
+ NAMES = DEFAULTS.keys.freeze
59
+
60
+ # @param edition [Symbol, Integer] an Edition enum name or number
61
+ # @return [Integer]
62
+ def self.edition_number(edition)
63
+ return edition if edition.is_a?(Integer)
64
+ EDITIONS.fetch(edition) { raise Error, "unknown edition #{edition.inspect}" }
65
+ end
66
+
67
+ # The defaults for an edition, as protoc computes them from descriptor.proto
68
+ # @param edition [Symbol, Integer]
69
+ # @return [Features]
70
+ def self.defaults(edition)
71
+ number = edition_number(edition)
72
+ values = DEFAULTS.transform_values do |pairs|
73
+ pick = pairs.reverse_each.find { |(ed, _)| EDITIONS.fetch(ed) <= number }
74
+ raise Error, "no feature defaults for edition #{edition.inspect}" unless pick
75
+ pick.last
76
+ end
77
+ new(values, Freezer::EMPTY_HASH)
78
+ end
79
+
80
+ # @return [Hash{Field => MessageValue}] merged explicit custom features by extension
81
+ attr_reader :extensions
82
+
83
+ # @api private
84
+ def initialize(values, extensions)
85
+ @values = values.freeze
86
+ @extensions = extensions
87
+ NAMES.each { |name| instance_variable_set(:"@#{name}", values.fetch(name)) }
88
+ @hash = [@values, @extensions].hash
89
+ freeze
90
+ end
91
+
92
+ # @!attribute [r] field_presence
93
+ # @return [Symbol] :EXPLICIT, :IMPLICIT or :LEGACY_REQUIRED
94
+ # @!attribute [r] enum_type
95
+ # @return [Symbol] :OPEN or :CLOSED
96
+ # @!attribute [r] repeated_field_encoding
97
+ # @return [Symbol] :PACKED or :EXPANDED
98
+ # @!attribute [r] utf8_validation
99
+ # @return [Symbol] :VERIFY or :NONE
100
+ # @!attribute [r] message_encoding
101
+ # @return [Symbol] :LENGTH_PREFIXED or :DELIMITED
102
+ # @!attribute [r] json_format
103
+ # @return [Symbol] :ALLOW or :LEGACY_BEST_EFFORT
104
+ # @!attribute [r] enforce_naming_style
105
+ # @return [Symbol]
106
+ # @!attribute [r] default_symbol_visibility
107
+ # @return [Symbol]
108
+ # @!attribute [r] enforce_proto_limits
109
+ # @return [Symbol]
110
+ attr_reader :field_presence, :enum_type, :repeated_field_encoding, :utf8_validation, :message_encoding,
111
+ :json_format, :enforce_naming_style, :default_symbol_visibility, :enforce_proto_limits
112
+
113
+ # Overlays a declared `features` option (a FeatureSet) on these features
114
+ # @param declared [MessageValue, nil]
115
+ # @return [Features] self when the overlay changes nothing
116
+ def merge(declared)
117
+ return self if declared.nil? || declared.empty?
118
+ values = @values
119
+ declared.fields.each do |key, value|
120
+ next unless NAMES.include?(key) && value.is_a?(Symbol)
121
+ values = values.merge(key => value) unless values[key] == value
122
+ end
123
+ extensions = MessageValue.merge_values(@extensions, declared.extensions)
124
+ return self if values.equal?(@values) && extensions.equal?(@extensions)
125
+ Features.new(values, extensions)
126
+ end
127
+
128
+ # @param values [Hash{Symbol => Symbol}] core features to override
129
+ # @return [Features] self when nothing changes
130
+ def with(**values)
131
+ return self if values.all? { |k, v| @values[k] == v }
132
+ Features.new(@values.merge(values), @extensions)
133
+ end
134
+
135
+ # @return [Hash{Symbol => Symbol}] the core features by name
136
+ def to_h
137
+ @values
138
+ end
139
+
140
+ def ==(other)
141
+ other.is_a?(Features) && other.to_h == @values && other.extensions == @extensions
142
+ end
143
+ alias_method :eql?, :==
144
+
145
+ attr_reader :hash
146
+
147
+ def inspect
148
+ custom = @extensions.map { |f, v| " (#{f.full_name}): #{v.inspect}" }.join
149
+ "#<#{self.class.name} #{@values.inspect}#{custom}>"
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ module Schema
5
+ # A field of a message, or an extension
6
+ class Field < Descriptor
7
+ # FieldDescriptorProto.Type names to the short type symbols
8
+ TYPES = {
9
+ TYPE_DOUBLE: :double, TYPE_FLOAT: :float, TYPE_INT64: :int64, TYPE_UINT64: :uint64,
10
+ TYPE_INT32: :int32, TYPE_FIXED64: :fixed64, TYPE_FIXED32: :fixed32, TYPE_BOOL: :bool,
11
+ TYPE_STRING: :string, TYPE_GROUP: :group, TYPE_MESSAGE: :message, TYPE_BYTES: :bytes,
12
+ TYPE_UINT32: :uint32, TYPE_ENUM: :enum, TYPE_SFIXED32: :sfixed32, TYPE_SFIXED64: :sfixed64,
13
+ TYPE_SINT32: :sint32, TYPE_SINT64: :sint64
14
+ }.freeze
15
+
16
+ # FieldDescriptorProto.Label names to the short label symbols
17
+ LABELS = {LABEL_OPTIONAL: :optional, LABEL_REQUIRED: :required, LABEL_REPEATED: :repeated}.freeze
18
+
19
+ # @api private
20
+ PROTO_TYPES = TYPES.invert.freeze
21
+ # @api private
22
+ PROTO_LABELS = LABELS.invert.freeze
23
+
24
+ # Wire type of a single, unpacked value of each type
25
+ WIRE_TYPES = {
26
+ double: 1, float: 5, int64: 0, uint64: 0, int32: 0, fixed64: 1, fixed32: 5, bool: 0,
27
+ string: 2, group: 3, message: 2, bytes: 2, uint32: 0, enum: 0, sfixed32: 5, sfixed64: 1,
28
+ sint32: 0, sint64: 0
29
+ }.freeze
30
+
31
+ # The implicit default of each scalar type
32
+ ZERO = {
33
+ double: 0.0, float: 0.0, int64: 0, uint64: 0, int32: 0, fixed64: 0, fixed32: 0, bool: false,
34
+ string: "".encode(Encoding::UTF_8).freeze, bytes: "".b.freeze, uint32: 0, sfixed32: 0,
35
+ sfixed64: 0, sint32: 0, sint64: 0
36
+ }.freeze
37
+
38
+ UNPACKABLE = %i[string bytes message group].freeze
39
+
40
+ # @return [Integer]
41
+ attr_reader :number
42
+ # @return [Symbol] :optional, :required or :repeated, with editions
43
+ # `LEGACY_REQUIRED` presence reported as :required
44
+ attr_reader :label
45
+ # @return [Symbol] :int32, :string, :message, ... with editions
46
+ # `DELIMITED` message fields reported as :group, as protoc does
47
+ attr_reader :type
48
+ # @return [String, nil] full name of the message or enum type, no leading dot
49
+ attr_reader :type_name
50
+ # @return [Message, nil]
51
+ attr_reader :message_type
52
+ # @return [Enum, nil]
53
+ attr_reader :enum_type
54
+ # @return [Object, nil] the declared default parsed to a Ruby value
55
+ # (Integer, Float, true/false, String, binary String, or Symbol for enums)
56
+ attr_reader :default_value
57
+ # @return [Object, nil] {#default_value}, or the type's implicit default:
58
+ # zero, false, an empty String, the first value's name for an enum, nil for messages.
59
+ # For repeated fields this is the default of a single element.
60
+ attr_reader :default
61
+ # @return [String]
62
+ attr_reader :json_name
63
+ # @return [Oneof, nil] the containing oneof, synthetic ones included
64
+ attr_reader :oneof
65
+ # @return [Message, nil] for an extension, the extended message
66
+ attr_reader :extendee
67
+ # @return [Message, nil] the message holding the field; for an extension the
68
+ # message it was declared in, or nil when declared at file level
69
+ attr_reader :containing_message
70
+ # @return [File]
71
+ attr_reader :file
72
+
73
+ # @api private
74
+ def initialize(name:, full_name:, number:, label:, type:, type_name:, extendee_name:, raw_default:, json_name:,
75
+ proto3_optional:, extension:, containing_message:, file:, index:)
76
+ @name = name
77
+ @full_name = full_name
78
+ @number = number
79
+ @declared_label = label
80
+ @declared_type = type
81
+ @label = label
82
+ @type = type
83
+ @type_name = type_name
84
+ @extendee_name = extendee_name
85
+ @raw_default = raw_default
86
+ @json_name = json_name
87
+ @proto3_optional = proto3_optional
88
+ @extension = extension
89
+ @containing_message = containing_message
90
+ @file = file
91
+ @index = index
92
+ end
93
+
94
+ # @api private
95
+ # @return [String, nil] the default as protoc stores it in `default_value`
96
+ attr_reader :raw_default
97
+ # @api private
98
+ # @return [String, nil] the extendee as written, until it is resolved
99
+ attr_reader :extendee_name
100
+ # @api private
101
+ # @return [Symbol, nil] the label as declared: :repeated, :optional or :required,
102
+ # before editions features turn `LEGACY_REQUIRED` into :required
103
+ attr_reader :declared_label
104
+ # @api private
105
+ # @return [Symbol, nil] the type as declared, before editions features turn
106
+ # `DELIMITED` message fields into :group; nil while a type name is unresolved
107
+ attr_reader :declared_type
108
+
109
+ # @return [Boolean]
110
+ def proto3_optional?
111
+ @proto3_optional
112
+ end
113
+
114
+ # @return [Boolean]
115
+ def extension?
116
+ @extension
117
+ end
118
+
119
+ # @return [Oneof, nil] the containing oneof unless it is synthetic
120
+ def real_oneof
121
+ @oneof unless @oneof.nil? || @oneof.synthetic?
122
+ end
123
+
124
+ # @return [Boolean]
125
+ def repeated?
126
+ @label == :repeated
127
+ end
128
+
129
+ # @return [Boolean]
130
+ def required?
131
+ @label == :required
132
+ end
133
+
134
+ # @return [Boolean]
135
+ def optional?
136
+ @label == :optional
137
+ end
138
+
139
+ # @return [Boolean] a `map<K, V>` field: repeated, of a map entry message type
140
+ def map?
141
+ @label == :repeated && !@message_type.nil? && @message_type.map_entry?
142
+ end
143
+
144
+ # @return [Field, nil] the key field of a map field's entry
145
+ def map_key
146
+ @message_type.fields_by_number[1] if map?
147
+ end
148
+
149
+ # @return [Field, nil] the value field of a map field's entry
150
+ def map_value
151
+ @message_type.fields_by_number[2] if map?
152
+ end
153
+
154
+ # @return [Boolean] not a message or group; enums count as scalars
155
+ def scalar?
156
+ @type != :message && @type != :group
157
+ end
158
+
159
+ # @return [Boolean] repeated and of a type that can be packed
160
+ def packable?
161
+ @label == :repeated && !UNPACKABLE.include?(@type)
162
+ end
163
+
164
+ # @return [Boolean] whether repeated values are written packed. Resolved
165
+ # through features: proto2 defaults to expanded, proto3 to packed, editions
166
+ # follow `repeated_field_encoding`, and a legacy `packed` option wins.
167
+ def packed?
168
+ packable? && @features.repeated_field_encoding == :PACKED
169
+ end
170
+
171
+ # @return [Boolean] explicit presence (has-bits): singular fields that are
172
+ # messages, extensions, oneof members, or not `IMPLICIT` by features
173
+ def presence?
174
+ return false if @label == :repeated
175
+ @type == :message || @type == :group || @extension || !@oneof.nil? ||
176
+ @features.field_presence != :IMPLICIT
177
+ end
178
+
179
+ # @return [Boolean] group, or editions `message_encoding = DELIMITED`
180
+ def delimited?
181
+ @type == :group
182
+ end
183
+
184
+ # @return [Integer] the wire type of one unpacked value
185
+ def wire_type
186
+ WIRE_TYPES.fetch(@type)
187
+ end
188
+
189
+ # @return [Boolean] whether string values must be valid UTF-8 when parsed
190
+ def utf8_validation?
191
+ @type == :string && @features.utf8_validation == :VERIFY
192
+ end
193
+
194
+ # @param default_json_names [Boolean] see {File#to_proto}
195
+ # @return [Protostar::Google::Protobuf::FieldDescriptorProto]
196
+ def to_proto(default_json_names: true)
197
+ Proto::Writer.field(self, default_json_names)
198
+ end
199
+ end
200
+ end
201
+ end