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,576 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ module Schema
5
+ # The one place that knows protoc's shape. {File#to_proto} writes a
6
+ # file as the `FileDescriptorProto` message of the bootstrap classes
7
+ # (`Protostar::Google::Protobuf`), as protoc's `FileDescriptor::CopyTo`
8
+ # would; {File.from_proto} reads one back into a draft. Custom options
9
+ # exist as extension bytes inside options messages only here: written by
10
+ # {MessageValue#encode}, read by {MessageValue.decode}.
11
+ module Proto
12
+ # descriptor.proto's classes are autoloaded on first reference, which
13
+ # Ruby 3.2 and 3.3 can only do in the main Ractor
14
+ # @return [Module] `Protostar::Google::Protobuf`, descriptor.proto's message classes
15
+ def self.types
16
+ ::Protostar::Google::Protobuf
17
+ end
18
+
19
+ # @param name [Symbol, String] a descriptor.proto message, e.g. :FieldOptions
20
+ # @return [Schema::Message] its descriptor, as the bootstrap defines it
21
+ def self.schema(name)
22
+ types.const_get(name).descriptor
23
+ end
24
+
25
+ # @param bytes [String] a serialized FileDescriptorSet
26
+ # @return [Array<String>] each serialized FileDescriptorProto
27
+ def self.each_file(bytes)
28
+ r = Wire::Reader.new(bytes)
29
+ files = []
30
+ until r.eof?
31
+ number, wire_type = r.read_tag
32
+ if number == 1 && wire_type == Wire::LENGTH_DELIMITED
33
+ files << r.read_bytes
34
+ else
35
+ r.skip_field(number, wire_type)
36
+ end
37
+ end
38
+ files
39
+ end
40
+
41
+ # Descriptors as descriptor.proto messages
42
+ # @api private
43
+ module Writer
44
+ module_function
45
+
46
+ def file(file, source_info:, json_names:)
47
+ attrs = {name: file.name}
48
+ attrs[:package] = file.package unless file.package.empty?
49
+ attrs[:dependency] = file.dependencies unless file.dependencies.empty?
50
+ attrs[:public_dependency] = file.public_dependencies unless file.public_dependencies.empty?
51
+ attrs[:weak_dependency] = file.weak_dependencies unless file.weak_dependencies.empty?
52
+ attrs[:option_dependency] = file.option_dependencies unless file.option_dependencies.empty?
53
+ list(attrs, :message_type, file.messages) { |m| message(m, json_names) }
54
+ list(attrs, :enum_type, file.enums) { |e| enum(e) }
55
+ list(attrs, :service, file.services) { |s| service(s) }
56
+ list(attrs, :extension, file.extensions) { |f| field(f, json_names) }
57
+ options(attrs, file)
58
+ attrs[:source_code_info] = source_code_info(file.source_info) if source_info && file.source_info
59
+ attrs[:syntax] = file.proto_syntax if file.proto_syntax
60
+ attrs[:edition] = file.edition if file.edition
61
+ Proto.types::FileDescriptorProto.new(**attrs)
62
+ end
63
+
64
+ def message(message, json_names)
65
+ t = Proto.types
66
+ attrs = {name: message.name}
67
+ list(attrs, :field, message.fields) { |f| field(f, json_names) }
68
+ list(attrs, :extension, message.extensions) { |f| field(f, json_names) }
69
+ list(attrs, :nested_type, message.nested_messages) { |m| message(m, json_names) }
70
+ list(attrs, :enum_type, message.nested_enums) { |e| enum(e) }
71
+ list(attrs, :extension_range, message.extension_range_descriptors) do |r|
72
+ range = {start: r.start, end: r.end + 1}
73
+ options(range, r)
74
+ t::DescriptorProto::ExtensionRange.new(**range)
75
+ end
76
+ list(attrs, :oneof_decl, message.oneofs) { |o| oneof(o) }
77
+ options(attrs, message)
78
+ list(attrs, :reserved_range, message.reserved_ranges) do |r|
79
+ t::DescriptorProto::ReservedRange.new(start: r.begin, end: r.end + 1)
80
+ end
81
+ attrs[:reserved_name] = message.reserved_names unless message.reserved_names.empty?
82
+ attrs[:visibility] = Proto.visibility(message.visibility) if message.visibility
83
+ t::DescriptorProto.new(**attrs)
84
+ end
85
+
86
+ def field(field, json_names)
87
+ attrs = {name: field.name, number: field.number, label: Field::PROTO_LABELS.fetch(field.declared_label),
88
+ type: Field::PROTO_TYPES.fetch(field.declared_type)}
89
+ attrs[:type_name] = ".#{field.type_name}" if field.type_name
90
+ attrs[:extendee] = ".#{field.extendee.full_name}" if field.extension?
91
+ attrs[:default_value] = field.raw_default if field.raw_default
92
+ attrs[:oneof_index] = field.oneof.index if field.oneof
93
+ attrs[:json_name] = field.json_name if json_names || field.json_name != Schema.json_name(field.name)
94
+ options(attrs, field)
95
+ attrs[:proto3_optional] = true if field.proto3_optional?
96
+ Proto.types::FieldDescriptorProto.new(**attrs)
97
+ end
98
+
99
+ def oneof(oneof)
100
+ attrs = {name: oneof.name}
101
+ options(attrs, oneof)
102
+ Proto.types::OneofDescriptorProto.new(**attrs)
103
+ end
104
+
105
+ def enum(enum)
106
+ t = Proto.types
107
+ attrs = {name: enum.name}
108
+ list(attrs, :value, enum.values) { |v| enum_value(v) }
109
+ options(attrs, enum)
110
+ list(attrs, :reserved_range, enum.reserved_ranges) do |r|
111
+ t::EnumDescriptorProto::EnumReservedRange.new(start: r.begin, end: r.end)
112
+ end
113
+ attrs[:reserved_name] = enum.reserved_names unless enum.reserved_names.empty?
114
+ attrs[:visibility] = Proto.visibility(enum.visibility) if enum.visibility
115
+ t::EnumDescriptorProto.new(**attrs)
116
+ end
117
+
118
+ def enum_value(value)
119
+ attrs = {name: value.name, number: value.number}
120
+ options(attrs, value)
121
+ Proto.types::EnumValueDescriptorProto.new(**attrs)
122
+ end
123
+
124
+ def service(service)
125
+ attrs = {name: service.name}
126
+ list(attrs, :method, service.rpcs) { |m| rpc(m) }
127
+ options(attrs, service)
128
+ Proto.types::ServiceDescriptorProto.new(**attrs)
129
+ end
130
+
131
+ def rpc(rpc)
132
+ attrs = {name: rpc.name, input_type: ".#{rpc.input_type.full_name}",
133
+ output_type: ".#{rpc.output_type.full_name}"}
134
+ options(attrs, rpc)
135
+ attrs[:client_streaming] = true if rpc.client_streaming?
136
+ attrs[:server_streaming] = true if rpc.server_streaming?
137
+ Proto.types::MethodDescriptorProto.new(**attrs)
138
+ end
139
+
140
+ def source_code_info(info)
141
+ location = Proto.types::SourceCodeInfo::Location
142
+ locations = info.locations.map do |loc|
143
+ attrs = {path: loc.path, span: loc.span}
144
+ attrs[:leading_comments] = loc.leading_comments if loc.leading_comments
145
+ attrs[:trailing_comments] = loc.trailing_comments if loc.trailing_comments
146
+ attrs[:leading_detached_comments] = loc.leading_detached_comments unless loc.leading_detached_comments.empty?
147
+ location.new(**attrs)
148
+ end
149
+ Proto.types::SourceCodeInfo.new(location: locations)
150
+ end
151
+
152
+ def options(attrs, descriptor)
153
+ options = descriptor.options
154
+ attrs[:options] = options.to_proto if options.declared?
155
+ end
156
+
157
+ FILE_SOURCE_CODE_INFO = 9 # FileDescriptorProto.source_code_info
158
+
159
+ # A serialized FileDescriptorProto with `source_code_info` put in its
160
+ # place among the fields, which are in number order. Thousands of
161
+ # locations are written straight to bytes rather than built as
162
+ # messages first: the same bytes, a quarter of the time.
163
+ def with_source_code_info(bytes, info)
164
+ r = Wire::Reader.new(bytes)
165
+ at = bytes.bytesize
166
+ until r.eof?
167
+ start = r.pos
168
+ number, wire_type = r.read_tag
169
+ if number > FILE_SOURCE_CODE_INFO
170
+ at = start
171
+ break
172
+ end
173
+ r.skip_field(number, wire_type)
174
+ end
175
+ out = bytes.byteslice(0, at)
176
+ w = Wire::Writer.new(out)
177
+ w.write_tag(FILE_SOURCE_CODE_INFO, Wire::LENGTH_DELIMITED)
178
+ w.write_bytes(source_code_info_bytes(info))
179
+ w.flush
180
+ out << bytes.byteslice(at, bytes.bytesize - at)
181
+ end
182
+
183
+ # SourceCodeInfo: location = 1; and in Location, path = 1 and span = 2
184
+ # (packed), leading_comments = 3, trailing_comments = 4,
185
+ # leading_detached_comments = 6
186
+ def source_code_info_bytes(info)
187
+ out = "".b
188
+ w = Wire::Writer.new(out)
189
+ loc_bytes = "".b
190
+ info.locations.each do |loc|
191
+ loc_bytes.clear
192
+ lw = Wire::Writer.new(loc_bytes)
193
+ packed(lw, 1, loc.path)
194
+ packed(lw, 2, loc.span)
195
+ string(lw, 3, loc.leading_comments) if loc.leading_comments
196
+ string(lw, 4, loc.trailing_comments) if loc.trailing_comments
197
+ loc.leading_detached_comments.each { |c| string(lw, 6, c) }
198
+ lw.flush
199
+ w.write_tag(1, Wire::LENGTH_DELIMITED)
200
+ w.write_bytes(loc_bytes)
201
+ end
202
+ w.flush
203
+ out
204
+ end
205
+
206
+ def packed(w, number, values)
207
+ return if values.empty?
208
+ size = values.sum { |v| Wire.varint_size(v) }
209
+ w.write_tag(number, Wire::LENGTH_DELIMITED)
210
+ w.write_varint(size)
211
+ values.each { |v| w.write_varint(v) }
212
+ end
213
+
214
+ def string(w, number, text)
215
+ w.write_tag(number, Wire::LENGTH_DELIMITED)
216
+ w.write_bytes(text.b)
217
+ end
218
+
219
+ def list(attrs, key, items, &block)
220
+ attrs[key] = items.map(&block) unless items.empty?
221
+ end
222
+ end
223
+
224
+ # A FileDescriptorProto message read into a draft
225
+ # @api private
226
+ class Reader
227
+ def initialize(proto, pool)
228
+ @proto = proto
229
+ @pool = pool
230
+ @options = []
231
+ end
232
+
233
+ def call
234
+ p = @proto
235
+ draft = File.draft(p.name, package: (p.package if p.has_package?), syntax: (p.syntax.to_sym if p.has_syntax?),
236
+ edition: (p.edition if p.has_edition?), dependencies: p.dependency.to_a,
237
+ public_dependencies: p.public_dependency.to_a, weak_dependencies: p.weak_dependency.to_a,
238
+ option_dependencies: p.option_dependency.to_a)
239
+ record(draft.descriptor, p)
240
+ draft.source_info = source_info(p.source_code_info) if p.has_source_code_info?
241
+ p.message_type.each { |m| message(draft, m) }
242
+ p.enum_type.each { |e| enum(draft, e) }
243
+ p.extension.each { |f| extension(draft, f) }
244
+ p.service.each do |s|
245
+ service = draft.service(s.name)
246
+ record(service.descriptor, s)
247
+ s["method"].each do |m|
248
+ record(service.rpc(m.name, m.input_type, m.output_type, client_streaming: m.client_streaming,
249
+ server_streaming: m.server_streaming), m)
250
+ end
251
+ end
252
+ draft.link(@pool)
253
+ lookup = extension_lookup(draft.descriptor)
254
+ @options.each do |descriptor, options|
255
+ draft.set_options(descriptor, MessageValue.decode(options.class.descriptor, options.encode, lookup))
256
+ end
257
+ draft.resolve(@pool).freeze!
258
+ end
259
+
260
+ private
261
+
262
+ def record(descriptor, proto)
263
+ @options << [descriptor, proto.options] if proto.has_options?
264
+ end
265
+
266
+ # Extensions from the pool, then those the file defines itself
267
+ def extension_lookup(file)
268
+ own = file.all_extensions.to_h { |e| [[e.extendee.full_name, e.number], e] }
269
+ ->(extendee, number) { @pool.find_extension(extendee, number) || own[[extendee, number]] }
270
+ end
271
+
272
+ def message(scope, proto)
273
+ visibility = Proto.visibility_symbol(proto.visibility) if proto.has_visibility?
274
+ draft = scope.message(proto.name, visibility: visibility)
275
+ record(draft.descriptor, proto)
276
+ oneofs = proto.oneof_decl.map do |o|
277
+ oneof = draft.oneof(o.name)
278
+ record(oneof, o)
279
+ oneof
280
+ end
281
+ proto.field.each { |f| field(draft, f, oneofs) }
282
+ proto.nested_type.each { |m| message(draft, m) }
283
+ proto.enum_type.each { |e| enum(draft, e) }
284
+ proto.extension.each { |f| extension(draft, f) }
285
+ proto.extension_range.each { |r| record(draft.extension_range(r.start..(r.end - 1)), r) }
286
+ proto.reserved_range.each { |r| draft.reserved(r.start..(r.end - 1)) }
287
+ draft.reserved(*proto.reserved_name)
288
+ end
289
+
290
+ def field(scope, proto, oneofs)
291
+ raise Error, "#{@proto.name}: field #{proto.name} has an extendee" if proto.has_extendee?
292
+ oneof = oneofs[proto.oneof_index] if proto.has_oneof_index?
293
+ raise Error, "#{@proto.name}: #{proto.name} has an invalid oneof_index" if proto.has_oneof_index? && !oneof
294
+ type, type_name, kwargs = field_attributes(proto)
295
+ record(scope.field(proto.name, proto.number, type, type_name, oneof: oneof, **kwargs), proto)
296
+ end
297
+
298
+ def extension(scope, proto)
299
+ raise Error, "#{@proto.name}: extension #{proto.name} has no extendee" unless proto.has_extendee?
300
+ type, type_name, kwargs = field_attributes(proto)
301
+ record(scope.extension(proto.name, proto.number, type, type_name, extendee: proto.extendee, **kwargs), proto)
302
+ end
303
+
304
+ def field_attributes(proto)
305
+ type = Field::TYPES.fetch(proto.type) if proto.has_type?
306
+ type_name = proto.type_name if proto.has_type_name?
307
+ kwargs = {label: proto.has_label? ? Field::LABELS.fetch(proto.label) : :optional,
308
+ default: (proto.default_value if proto.has_default_value?),
309
+ json_name: (proto.json_name if proto.has_json_name?), optional: proto.proto3_optional}
310
+ [type, type_name, kwargs]
311
+ end
312
+
313
+ def enum(scope, proto)
314
+ visibility = Proto.visibility_symbol(proto.visibility) if proto.has_visibility?
315
+ draft = scope.enum(proto.name, visibility: visibility)
316
+ record(draft.descriptor, proto)
317
+ proto.value.each { |v| record(draft.value(v.name, v.number), v) }
318
+ proto.reserved_range.each { |r| draft.reserved(r.start..r.end) }
319
+ draft.reserved(*proto.reserved_name)
320
+ end
321
+
322
+ def source_info(proto)
323
+ SourceInfo.new(proto.location.map do |loc|
324
+ SourceInfo::Location.new(path: loc.path.to_a, span: loc.span.to_a,
325
+ leading_comments: (loc.leading_comments if loc.has_leading_comments?),
326
+ trailing_comments: (loc.trailing_comments if loc.has_trailing_comments?),
327
+ leading_detached_comments: loc.leading_detached_comments.to_a)
328
+ end)
329
+ end
330
+ end
331
+
332
+ # descriptor.proto's SymbolVisibility for {Message#visibility} and {Enum#visibility}
333
+ # @api private
334
+ def self.visibility(symbol)
335
+ (symbol == :local) ? :VISIBILITY_LOCAL : :VISIBILITY_EXPORT
336
+ end
337
+
338
+ # @api private
339
+ def self.visibility_symbol(proto)
340
+ Draft.visibility(proto)
341
+ end
342
+
343
+ # Message values to and from wire bytes, driven by {Schema::Message}s:
344
+ # fields in number order with extensions among them, unknown fields
345
+ # last; packed repeated fields per features; MessageSet items for
346
+ # `message_set_wire_format` types; proto3 zero values left out.
347
+ # @api private
348
+ module Codec
349
+ module_function
350
+
351
+ def encode(type, value)
352
+ entries = []
353
+ value.fields.each do |name, v|
354
+ field = type.fields_by_name[name.to_s] || raise(Error, "#{type.full_name} has no field #{name}")
355
+ entries << [field, v]
356
+ end
357
+ value.extensions.each { |field, v| entries << [field, v] }
358
+ out = "".b
359
+ w = Wire::Writer.new(out)
360
+ entries.sort_by! { |f, _| f.number }
361
+ entries.each do |field, v|
362
+ if field.extension? && type.message_set_wire_format?
363
+ write_set_item(w, field, v)
364
+ else
365
+ write_field(w, field, v)
366
+ end
367
+ end
368
+ w.write_raw(value.unknown_fields) unless value.unknown_fields.empty?
369
+ w.flush
370
+ out
371
+ end
372
+
373
+ # A MessageSet item: group 1 holding type_id 2 and message 3
374
+ def write_set_item(w, field, value)
375
+ w.write_tag(1, Wire::START_GROUP)
376
+ w.write_tag(2, Wire::VARINT)
377
+ w.write_varint(field.number)
378
+ w.write_tag(3, Wire::LENGTH_DELIMITED)
379
+ w.write_bytes(encode(field.message_type, value))
380
+ w.write_tag(1, Wire::END_GROUP)
381
+ end
382
+
383
+ def write_field(w, field, value)
384
+ if field.repeated?
385
+ return if value.empty?
386
+ if field.packed?
387
+ body = "".b
388
+ inner = Wire::Writer.new(body)
389
+ value.each { |v| write_value(inner, field, v) }
390
+ inner.flush
391
+ w.write_tag(field.number, Wire::LENGTH_DELIMITED)
392
+ w.write_bytes(body)
393
+ else
394
+ value.each { |v| write_one(w, field, v) }
395
+ end
396
+ else
397
+ return if !field.presence? && zero?(field, value)
398
+ write_one(w, field, value)
399
+ end
400
+ end
401
+
402
+ def zero?(field, value)
403
+ case field.type
404
+ when :string, :bytes then value.empty?
405
+ when :bool then !value
406
+ when :enum then value == 0 || (value.is_a?(Symbol) && field.enum_type.value(value.to_s)&.number == 0)
407
+ when :double, :float then value == 0 && !(1.0 / value).negative?
408
+ else value == 0
409
+ end
410
+ end
411
+
412
+ def write_one(w, field, value)
413
+ case field.type
414
+ when :message
415
+ w.write_tag(field.number, Wire::LENGTH_DELIMITED)
416
+ w.write_bytes(encode(field.message_type, value))
417
+ when :group
418
+ w.write_tag(field.number, Wire::START_GROUP)
419
+ w.write_raw(encode(field.message_type, value))
420
+ w.write_tag(field.number, Wire::END_GROUP)
421
+ else
422
+ w.write_tag(field.number, field.wire_type)
423
+ write_value(w, field, value)
424
+ end
425
+ end
426
+
427
+ def write_value(w, field, v)
428
+ case field.type
429
+ when :int32, :int64, :uint32, :uint64 then w.write_varint(v)
430
+ when :sint32, :sint64 then w.write_zigzag(v)
431
+ when :bool then w.write_varint(v ? 1 : 0)
432
+ when :enum
433
+ number = v.is_a?(Integer) ? v : field.enum_type.value(v.to_s)&.number
434
+ raise Error, "#{v.inspect} is not a value of #{field.enum_type.full_name}" unless number
435
+ w.write_varint(number)
436
+ when :fixed32 then w.write_fixed32(v)
437
+ when :fixed64 then w.write_fixed64(v)
438
+ when :sfixed32 then w.write_sfixed32(v)
439
+ when :sfixed64 then w.write_sfixed64(v)
440
+ when :float then w.write_float(v)
441
+ when :double then w.write_double(v)
442
+ when :string, :bytes then w.write_bytes(v.b)
443
+ end
444
+ end
445
+
446
+ def decode(type, bytes, lookup)
447
+ read(Wire::Reader.new(bytes), type, lookup)
448
+ end
449
+
450
+ def read(r, type, lookup)
451
+ fields = {}
452
+ extensions = {}
453
+ unknown = nil
454
+ chunks = nil
455
+ until r.eof?
456
+ number, wire_type = r.read_tag
457
+ if number == 1 && wire_type == Wire::START_GROUP && type.message_set_wire_format?
458
+ unknown = read_set_item(r, extensions, type, lookup, unknown)
459
+ next
460
+ end
461
+ field = type.fields_by_number[number]
462
+ field ||= lookup&.call(type.full_name, number) if type.extension_number?(number)
463
+ if field.nil? || !compatible?(field, wire_type)
464
+ start = r.pos
465
+ r.skip_field(number, wire_type)
466
+ unknown = (unknown || "".b) << tag_bytes(number, wire_type) << raw(r, start)
467
+ next
468
+ end
469
+ target = field.extension? ? extensions : fields
470
+ key = field.extension? ? field : field.name.to_sym
471
+ if field.type == :message || field.type == :group
472
+ body = if field.type == :group
473
+ start = r.pos
474
+ r.skip_group(number)
475
+ group_body(r, start, number)
476
+ else
477
+ r.read_bytes
478
+ end
479
+ if field.repeated?
480
+ (target[key] ||= []) << decode(field.message_type, body, lookup)
481
+ else
482
+ chunks ||= {}
483
+ (chunks[[target, key, field]] ||= +"".b) << body
484
+ end
485
+ elsif field.repeated?
486
+ list = (target[key] ||= [])
487
+ if wire_type == Wire::LENGTH_DELIMITED && field.packable?
488
+ packed = Wire::Reader.new(r.read_bytes)
489
+ list << read_value(packed, field) until packed.eof?
490
+ else
491
+ list << read_value(r, field)
492
+ end
493
+ else
494
+ target[key] = read_value(r, field)
495
+ end
496
+ end
497
+ chunks&.each do |(target, key, field), body|
498
+ target[key] = decode(field.message_type, body, lookup)
499
+ end
500
+ MessageValue.new(fields, extensions, unknown || MessageValue::EMPTY_BYTES)
501
+ end
502
+
503
+ # One MessageSet item; kept whole as an unknown group when its
504
+ # extension is not known
505
+ def read_set_item(r, extensions, type, lookup, unknown)
506
+ body_start = r.pos
507
+ r.skip_group(1)
508
+ item = Wire::Reader.new(group_body(r, body_start, 1))
509
+ type_id = message = nil
510
+ until item.eof?
511
+ n, wt = item.read_tag
512
+ if n == 2 && wt == Wire::VARINT
513
+ type_id = item.read_varint
514
+ elsif n == 3 && wt == Wire::LENGTH_DELIMITED
515
+ message = item.read_bytes
516
+ else
517
+ item.skip_field(n, wt)
518
+ end
519
+ end
520
+ field = type_id && message && lookup&.call(type.full_name, type_id)
521
+ if field && field.type == :message
522
+ extensions[field] = decode(field.message_type, message, lookup)
523
+ unknown
524
+ else
525
+ (unknown || "".b) << tag_bytes(1, Wire::START_GROUP) << raw(r, body_start)
526
+ end
527
+ end
528
+
529
+ def compatible?(field, wire_type)
530
+ return true if wire_type == field.wire_type
531
+ wire_type == Wire::LENGTH_DELIMITED && field.packable?
532
+ end
533
+
534
+ def tag_bytes(number, wire_type)
535
+ out = "".b
536
+ w = Wire::Writer.new(out)
537
+ w.write_tag(number, wire_type)
538
+ w.flush
539
+ out
540
+ end
541
+
542
+ def raw(r, start)
543
+ r.buffer.byteslice(start, r.pos - start).b
544
+ end
545
+
546
+ def group_body(r, start, number)
547
+ all = raw(r, start)
548
+ all.byteslice(0, all.bytesize - Wire.varint_size(Wire.tag(number, Wire::END_GROUP)))
549
+ end
550
+
551
+ def read_value(r, field)
552
+ case field.type
553
+ when :int32 then r.read_int32
554
+ when :int64 then r.read_int64
555
+ when :uint32 then r.read_uint32
556
+ when :uint64 then r.read_varint
557
+ when :sint32 then r.read_sint32
558
+ when :sint64 then r.read_sint64
559
+ when :bool then r.read_varint != 0
560
+ when :enum
561
+ number = r.read_int32
562
+ field.enum_type.values_by_number[number]&.name&.to_sym || number
563
+ when :fixed32 then r.read_fixed32
564
+ when :fixed64 then r.read_fixed64
565
+ when :sfixed32 then r.read_sfixed32
566
+ when :sfixed64 then r.read_sfixed64
567
+ when :float then r.read_float
568
+ when :double then r.read_double
569
+ when :string then r.read_bytes.force_encoding(Encoding::UTF_8)
570
+ when :bytes then r.read_bytes.b
571
+ end
572
+ end
573
+ end
574
+ end
575
+ end
576
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ module Schema
5
+ # protoc's C++-style scoped name lookup (`DescriptorBuilder::LookupSymbol`),
6
+ # as `FieldDescriptorProto.type_name` in descriptor.proto describes it.
7
+ #
8
+ # A name with a leading dot is fully qualified. Otherwise the *first component*
9
+ # is looked up in the innermost scope, then each enclosing scope out to the
10
+ # root; once it is found (as something that can contain names), the rest of
11
+ # the name must resolve beneath it or the lookup fails without searching
12
+ # further out. Only symbols of the file itself, its direct imports and
13
+ # whatever those re-export through `import public` are visible.
14
+ module Resolver
15
+ # Stands for "this name is a package" during lookup
16
+ PACKAGE = Object.new.freeze
17
+
18
+ # @param name [String] the name as written, e.g. "Foo.Bar" or ".pkg.Foo.Bar"
19
+ # @param scope [String] full name of the enclosing message, or the package ("" for none)
20
+ # @param file [File] the file doing the lookup; it may still be under construction
21
+ # @param pool [Pool, nil] consulted only to explain a failure ("not imported")
22
+ # @param types_only [Boolean] when a simple (one-component) name finds something
23
+ # that is not a message or enum, keep searching outward, as protoc does for
24
+ # type names. Compound names return whatever they reach.
25
+ # @return [Descriptor]
26
+ # @raise [ResolveError]
27
+ def self.resolve(name, scope:, file:, pool: nil, types_only: true)
28
+ if !name.is_a?(String) || name.empty? || name == "."
29
+ raise ResolveError.new("#{name.inspect} is not a valid name", name: name, scope: scope)
30
+ end
31
+
32
+ if name.start_with?(".")
33
+ found = find(name[1..], file)
34
+ return found if found && !found.equal?(PACKAGE)
35
+ raise not_defined(name, scope, file, pool)
36
+ end
37
+
38
+ dot = name.index(".")
39
+ first = dot ? name[0, dot] : name
40
+ prefix = scope
41
+ loop do
42
+ candidate = prefix.empty? ? first : "#{prefix}.#{first}"
43
+ found = find(candidate, file)
44
+ if found
45
+ if dot
46
+ if aggregate?(found)
47
+ full = prefix.empty? ? name : "#{prefix}.#{name}"
48
+ inner = find(full, file)
49
+ return inner if inner && !inner.equal?(PACKAGE)
50
+ raise ResolveError.new(
51
+ "\"#{name}\" is resolved to \"#{full}\", which is not defined. The innermost scope is " \
52
+ "searched first in name resolution. Consider using a leading '.'(i.e., \".#{name}\") " \
53
+ "to start from the outermost scope (scope \"#{scope}\" in #{file.name})",
54
+ name: name, scope: scope
55
+ )
56
+ end
57
+ elsif !found.equal?(PACKAGE) && (!types_only || type?(found))
58
+ return found
59
+ end
60
+ end
61
+ break if prefix.empty?
62
+ cut = prefix.rindex(".")
63
+ prefix = cut ? prefix[0, cut] : ""
64
+ end
65
+ raise not_defined(name, scope, file, pool)
66
+ end
67
+
68
+ # A visible symbol by full name, {PACKAGE} for a visible package, or nil
69
+ # @api private
70
+ def self.find(full_name, file)
71
+ visible = file.visible_files
72
+ visible.each do |f|
73
+ found = f.symbols[full_name]
74
+ return found if found
75
+ end
76
+ visible.each do |f|
77
+ return PACKAGE if in_package?(f.package, full_name)
78
+ end
79
+ nil
80
+ end
81
+
82
+ # @api private
83
+ def self.in_package?(package, name)
84
+ package == name || (package.start_with?(name) && package.getbyte(name.bytesize) == 46) # "."
85
+ end
86
+
87
+ # @api private
88
+ def self.type?(found)
89
+ found.is_a?(Message) || found.is_a?(Enum)
90
+ end
91
+
92
+ # @api private
93
+ def self.aggregate?(found)
94
+ found.equal?(PACKAGE) || found.is_a?(Message) || found.is_a?(Enum) || found.is_a?(Service)
95
+ end
96
+
97
+ def self.not_defined(name, scope, file, pool)
98
+ message = "\"#{name}\" is not defined (scope \"#{scope}\" in #{file.name})"
99
+ if pool
100
+ elsewhere = candidates(name, scope).lazy.map { |c| pool.find_symbol(c) }.find(&:itself)
101
+ if elsewhere
102
+ message = "\"#{elsewhere.full_name}\" seems to be defined in \"#{elsewhere.file.name}\", which is " \
103
+ "not imported by \"#{file.name}\". To use it here, please add the necessary import."
104
+ end
105
+ end
106
+ ResolveError.new(message, name: name, scope: scope)
107
+ end
108
+ private_class_method :not_defined
109
+
110
+ def self.candidates(name, scope)
111
+ return [name[1..]] if name.start_with?(".")
112
+ parts = scope.empty? ? [] : scope.split(".")
113
+ parts.size.downto(0).map { |i| (parts.first(i) + [name]).join(".") }
114
+ end
115
+ private_class_method :candidates
116
+ end
117
+ end
118
+ end