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,965 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ # Writes a message's wire bytes as a block runs, without building the
5
+ # message. Protobuf merges by concatenation, so a builder needs no object
6
+ # graph: each call writes its field at once.
7
+ #
8
+ # bytes = Protostar::Builder.build(Acme::Person) do |pb|
9
+ # pb.name "Jane" # written now
10
+ # pb.friends(people) { |p| pb.name p.name } # one element per item
11
+ # pb.address { pb.street "x" } # nested, length-prefixed when the block ends
12
+ # pb.attrs("a" => "b") # a map
13
+ # pb.created_at Time.now # a Timestamp
14
+ # pb.extract! user, :email, :phone # fields read from an object
15
+ # pb.cache!("person/#{id}") { pb.bio bio } # bytes cached; a hit appends them
16
+ # end
17
+ # person = Protostar::Builder.message(Acme::Person) { |pb| ... }
18
+ # Protostar::Builder.write(io, Acme::Person) { |pb| ... }
19
+ #
20
+ # A builder has one method per field name of the message and of every
21
+ # message reachable from it; inside a nested block the same builder writes
22
+ # the nested message's fields. Names that are also methods of Object (or of
23
+ # the builder itself), such as `hash`, `format` or `test`, are reached with
24
+ # {#set!}.
25
+ #
26
+ # Only what the wire format needs is held back: a nested message is built
27
+ # into a buffer of its own because its length comes first, and packed
28
+ # repeated scalars are collected per field and written as one run when
29
+ # their message ends. Everything else goes straight to the target.
30
+ #
31
+ # Values are checked against the descriptor as they are written. Besides
32
+ # what a message accepts, a builder takes `Time` and `DateTime` for a
33
+ # `google.protobuf.Timestamp`, a Numeric for a `google.protobuf.Duration`,
34
+ # a scalar for a wrapper type, a Hash or anything with `to_h` for a
35
+ # message, and a message instance (written as its encoded bytes). `nil`
36
+ # writes nothing. Errors raise {Error} with the path of the field.
37
+ #
38
+ # By default a singular field may be written once per message: writing it
39
+ # again (or a second member of a oneof) raises. With `overwrites: true` the
40
+ # later value is written as well and wins when the bytes are read, as
41
+ # protobuf's merge rules say. Such output then relies on its reader
42
+ # applying those rules (every conforming decoder does, protostar's
43
+ # included); a reader promised that no field repeats will see the first
44
+ # value instead.
45
+ class Builder
46
+ # Raised for a value a field cannot take, a field the message does not
47
+ # have, or a field written twice without `overwrites: true`
48
+ class Error < Protostar::Error
49
+ # @return [String] where the problem is, as in `friends[2].address.street`
50
+ attr_reader :path
51
+
52
+ def initialize(message, path: nil)
53
+ @path = path
54
+ super((path.nil? || path.empty?) ? message : "#{path}: #{message}")
55
+ end
56
+ end
57
+
58
+ # Stands for "no value given" in field methods, as nil is a value (it writes nothing)
59
+ NONE = Object.new.freeze
60
+ # A nested message at least this large is handed to a sink as it is
61
+ BIG = Wire::Writer::FLUSH_AT
62
+ EMPTY = [].freeze
63
+ F = Message::FieldInfo
64
+ RANGES = Message::Coercion::RANGES
65
+ ENUM_RANGE = Message::Coercion::ENUM_RANGE
66
+ UTF_8 = Encoding::UTF_8
67
+ BINARY = Encoding::BINARY
68
+ # pack directive of a run of fixed-width values
69
+ PACK = F::FIXED.transform_values { |(_, d)| "#{d}*".freeze }.freeze
70
+ WRAPPERS = %w[
71
+ DoubleValue FloatValue Int64Value UInt64Value Int32Value UInt32Value BoolValue StringValue BytesValue
72
+ ].to_h { |n| ["google.protobuf.#{n}", :wrapper] }
73
+ # Why a second write raises: the start of the message's second half
74
+ NO_OVERWRITES = "this builder promises its reader that no field repeats (overwrites: false, the default), " \
75
+ "which is what lets it stream. Pass overwrites: true to let"
76
+ WELL_KNOWN = WRAPPERS.merge("google.protobuf.Timestamp" => :timestamp, "google.protobuf.Duration" => :duration).freeze
77
+
78
+ class << self
79
+ # @param klass [Class, Schema::Message] the message class, or its descriptor
80
+ # @param options [Hash] see {#initialize}
81
+ # @yieldparam pb [Builder]
82
+ # @return [String] binary, the message's encoding
83
+ def build(klass, **options)
84
+ b = new(klass, nil, **options)
85
+ yield b
86
+ b.target!
87
+ end
88
+
89
+ # Builds the bytes and decodes them
90
+ # @return [Message] an instance of `klass`
91
+ def message(klass, **options, &block)
92
+ klass = resolve(klass)
93
+ klass.decode(build(klass, **options, &block))
94
+ end
95
+
96
+ # Streams the message to a sink as it is built. Top-level fields go out
97
+ # as they complete, through a buffer of `buffer:` bytes (64 KiB unless
98
+ # told otherwise); a nested message goes out once it is complete, or in
99
+ # pieces with `chunk_nested_at:`. See docs/concepts/streaming.md.
100
+ # @param io [#write, #<<, #call, String] the sink (see {#initialize})
101
+ # @return [Object] io, after its `flush` when it has one
102
+ def write(io, klass, **options)
103
+ b = new(klass, io, **options)
104
+ yield b
105
+ b.target!
106
+ end
107
+
108
+ # Writes a sequence of messages, each with its length as a varint
109
+ # before it, as {Message#encode_delimited} does; read them back with
110
+ # `decode_delimited`. Each message goes to the sink once it is complete.
111
+ #
112
+ # Protostar::Builder.each(file, Acme::Event, rows) do |pb, row|
113
+ # pb.name row.name
114
+ # end
115
+ #
116
+ # @param io [#write, #<<, #call, String, nil] the sink; nil for a new String
117
+ # @param collection [#each] one message per element
118
+ # @yieldparam pb [Builder] the same builder for every message
119
+ # @yieldparam element [Object]
120
+ # @return [Object] io, or the String when io is nil
121
+ def each(io, klass, collection, **options, &block)
122
+ raise ArgumentError, "needs a collection (something with #each), got #{collection.class}" unless collection.respond_to?(:each)
123
+ b = new(klass, io, **options)
124
+ b.__send__(:__sequence, collection, &block)
125
+ b.target!
126
+ end
127
+
128
+ # The message's bytes as an Enumerator of binary Strings, made as the
129
+ # Enumerator is iterated: a Rack body, or anything that pulls. Each
130
+ # iteration runs the block again.
131
+ #
132
+ # body = Protostar::Builder.each_chunk(Acme::Person, buffer: 16_384) { |pb| ... }
133
+ # body.each { |bytes| socket.write(bytes) }
134
+ #
135
+ # @return [Enumerator<String>]
136
+ def each_chunk(klass, **options, &block)
137
+ raise ArgumentError, "each_chunk needs a block that writes the message" unless block
138
+ resolve(klass)
139
+ Enumerator.new do |y|
140
+ write(y, klass, **options, &block)
141
+ nil
142
+ end
143
+ end
144
+
145
+ # @param klass [Class, Schema::Message]
146
+ # @param target [String, #write, #<<, #call, nil] where the bytes go: nil for a new String
147
+ # @return [Builder] an instance of the builder class for `klass`
148
+ def new(klass, target = nil, **options)
149
+ return super if superclass.equal?(Builder)
150
+ self.for(klass).new(klass, target, **options)
151
+ end
152
+
153
+ # The builder class for a message class: a subclass of Builder with one
154
+ # method per field name of the message and of every message it reaches.
155
+ # Made on first use and kept on the message class (in the main Ractor;
156
+ # other Ractors make their own).
157
+ # @return [Class]
158
+ def for(klass)
159
+ klass = resolve(klass)
160
+ made = klass.instance_variable_get(:@__builder)
161
+ return made if made
162
+ made = Generator.generate(klass)
163
+ klass.instance_variable_set(:@__builder, made) if Ractor.current.equal?(Ractor.main)
164
+ made
165
+ end
166
+
167
+ # @return [Class] the message class for a class or a descriptor
168
+ def resolve(klass)
169
+ return klass if klass.is_a?(Class) && klass < Message && klass.__fields
170
+ if klass.is_a?(Schema::Message)
171
+ return Types.find(klass.full_name) || raise(ArgumentError, "#{klass.full_name} has no class in Protostar::Types.registry")
172
+ end
173
+ return resolve(klass.msgclass) if klass.respond_to?(:msgclass)
174
+ raise ArgumentError, "#{klass.inspect} is not a protostar message class or descriptor"
175
+ end
176
+ end
177
+
178
+ # @param klass [Class, Schema::Message] the message class, or its descriptor
179
+ # @param target [String, #write, #<<, #call, nil] a String to append to
180
+ # (switched to binary encoding), nil for a new String, or a sink: anything
181
+ # answering `write` (an IO, a socket, a Rack or ActionController::Live
182
+ # stream), `<<` (an Array, an Enumerator::Yielder) or `call` (a Proc).
183
+ # Every chunk a sink gets is a binary String; an IO or StringIO is handed
184
+ # the builder's buffer, anything else a copy it may keep. A sink's `flush`
185
+ # is called by {#target!}. An error the sink raises is re-raised with
186
+ # the field being written added to its message.
187
+ # @param cache_store [#fetch, nil] where {#cache!} keeps fragments, such as
188
+ # `Rails.cache`; without one, {#cache!} just runs its block
189
+ # @param overwrites [Boolean] let a singular field be written again in the
190
+ # same message, the later value winning (see the class notes)
191
+ # @param repeated [Symbol] `:append` (default): each write to a repeated field
192
+ # adds elements. `:replace`: assigning a list (or one value without a block)
193
+ # replaces what earlier assignments in the same message gave; those lists
194
+ # are held until the message ends. Elements built with a block always stream.
195
+ # @param buffer [Integer] bytes collected before a sink is written to.
196
+ # 0 writes each top-level field as it completes, for sinks that buffer
197
+ # on their own. A nested message larger than the buffer is handed to the
198
+ # sink as it is, not copied into the buffer first.
199
+ # @param chunk_nested_at [Integer, nil] write a singular nested message
200
+ # that grows past this many bytes as several occurrences of its field,
201
+ # which readers merge into one, so that memory is bounded by this size
202
+ # rather than by the message. Needs `overwrites: false` and
203
+ # `repeated: :append`. Repeated and map fields are never split.
204
+ def initialize(klass, target = nil, cache_store: nil, overwrites: false, repeated: :append,
205
+ buffer: Wire::Writer::FLUSH_AT, chunk_nested_at: nil)
206
+ raise ArgumentError, "repeated: must be :append or :replace" unless repeated == :append || repeated == :replace
207
+ raise ArgumentError, "buffer: must be an Integer >= 0, got #{buffer.inspect}" unless buffer.is_a?(Integer) && buffer >= 0
208
+ if !chunk_nested_at.nil? && !(chunk_nested_at.is_a?(Integer) && chunk_nested_at > 0)
209
+ raise ArgumentError, "chunk_nested_at: must be a positive Integer or nil, got #{chunk_nested_at.inspect}"
210
+ end
211
+ if chunk_nested_at && (overwrites || repeated == :replace)
212
+ raise ArgumentError, "chunk_nested_at: splits a nested message into pieces that readers merge, which " \
213
+ "is only sound when no field is written twice; it needs overwrites: false and repeated: :append"
214
+ end
215
+ @root = self.class::CLASSES[0]
216
+ @w = @top = (target.nil? || target.is_a?(String)) ? Wire::Writer.new(target) : Sink.new(target, buffer, self)
217
+ @buf = @w.string
218
+ @chunk = chunk_nested_at
219
+ # [own writer or nil, parent writer, field, written out yet] per open nested message
220
+ @open = []
221
+ @capturing = 0
222
+ # with a buffer, write_raw flushes it when full; field ends matter only for these two
223
+ @boundary = (!@top.io.nil? && buffer == 0) || !@chunk.nil?
224
+ @k = 0
225
+ @written = 0
226
+ @pending = nil
227
+ @path = []
228
+ @spare = []
229
+ @store = cache_store
230
+ @overwrites = overwrites
231
+ @replace = repeated == :replace
232
+ end
233
+
234
+ # Finishes the top-level message: writes the packed runs collected so far,
235
+ # in field number order.
236
+ # The builder can keep writing afterwards; call this again to finish again.
237
+ # @return [String, IO] the bytes, or the IO after flushing it
238
+ def target!
239
+ __flush_pending if @pending
240
+ top = @top
241
+ return top.string unless top.io
242
+ top.flush
243
+ io = top.io
244
+ begin
245
+ io.flush if io.respond_to?(:flush)
246
+ rescue => e
247
+ raise top.annotate(e)
248
+ end
249
+ io
250
+ end
251
+
252
+ # @return [Class] the class of the message being written: the top-level
253
+ # one, or the nested message whose block is running
254
+ def message_class
255
+ self.class::CLASSES[@k]
256
+ end
257
+
258
+ # Writes a field by name, for names that have no method of their own.
259
+ #
260
+ # pb.set! :name, "Jane"
261
+ # pb.set!(:address) { pb.street "x" }
262
+ # pb.set! :friends, people, :name, :id # each friend gets name and id from an element
263
+ # pb.set! :address, user, :street, :city # extract! into the nested message
264
+ #
265
+ # @param name [String, Symbol]
266
+ # @param value [Object] the value; a collection with a block or attribute names
267
+ # @param attributes [Array<Symbol>] read from `value` (or from each element) with {#extract!}
268
+ # @return [self]
269
+ def set!(name, value = NONE, *attributes, &block)
270
+ __set(__field(name), value, attributes, &block)
271
+ self
272
+ end
273
+
274
+ # Writes the named fields with the values of the same names read from
275
+ # `object`: method calls, or keys for a Hash (Symbol or String keys).
276
+ # @return [self]
277
+ def extract!(object, *names)
278
+ if object.is_a?(Hash)
279
+ names.each do |n|
280
+ v = object.fetch(n) { object.fetch(n.is_a?(Symbol) ? n.name : n.to_sym, nil) }
281
+ __put(__field(n), v)
282
+ end
283
+ else
284
+ names.each { |n| __put(__field(n), object.public_send(n)) }
285
+ end
286
+ self
287
+ end
288
+
289
+ # Writes every field of a Hash (Symbol or String keys), as {#set!} would
290
+ # @return [self]
291
+ def merge!(hash)
292
+ raise __error(nil, "merge! takes a Hash, got #{hash.class}") unless hash.is_a?(Hash)
293
+ __hash(hash)
294
+ self
295
+ end
296
+
297
+ # Writes messages that are already encoded as the value of a message
298
+ # field: one String for a singular field, a list of them for a repeated
299
+ # one (each String is one element). The bytes are written as they are,
300
+ # unchecked; a group's bytes are its fields, without the group's tags.
301
+ #
302
+ # pb.encoded!(:friends, rendered_partials)
303
+ #
304
+ # @return [self]
305
+ def encoded!(name, bytes)
306
+ info = __field(name)
307
+ raise __error(info, "is not a message field, it cannot take encoded messages") unless info.message? && !info.map
308
+ if info.repeated
309
+ __list(bytes).each_with_index { |b, i| __encoded(info, b, i) }
310
+ else
311
+ __mark(info)
312
+ __encoded(info, bytes, nil)
313
+ end
314
+ self
315
+ end
316
+
317
+ # Writes the bytes the block writes, keeping them in the cache store under
318
+ # `key`; when the store has them, appends them without running the block.
319
+ # Without a cache store, runs the block.
320
+ # @param key [Object] passed to the store as it is
321
+ # @param options [Hash] passed to the store's `fetch`, such as `expires_in:`
322
+ # @yieldparam pb [Builder] this builder
323
+ # @return [self]
324
+ def cache!(key, **options, &block)
325
+ store = @store
326
+ return tap(&block) unless store
327
+ missed = false
328
+ miss = proc do
329
+ missed = true
330
+ __capture(block)
331
+ end
332
+ bytes = options.empty? ? store.fetch(key, &miss) : store.fetch(key, options, &miss)
333
+ raise __error(nil, "the cache store returned a #{bytes.class} for #{key.inspect}, not bytes") unless bytes.is_a?(String)
334
+ __mark_fragment(bytes) unless missed
335
+ @w.write_raw(bytes)
336
+ __boundary if @boundary
337
+ self
338
+ end
339
+
340
+ # {#cache!} when `condition` holds, else runs the block
341
+ # @return [self]
342
+ def cache_if!(condition, key, **options, &block)
343
+ condition ? cache!(key, **options, &block) : tap(&block)
344
+ end
345
+
346
+ def inspect
347
+ path = __path(nil)
348
+ "#<#{Builder.name} #{@root.descriptor.full_name}#{" at #{path}" unless path.empty?}>"
349
+ end
350
+
351
+ private
352
+
353
+ def __field(name)
354
+ self.class::BY_NAME[@k][name] || __no_field(name)
355
+ end
356
+
357
+ def __no_field(name)
358
+ raise __error(nil, "#{self.class::CLASSES[@k].descriptor.full_name} has no field #{name.to_s.inspect}")
359
+ end
360
+
361
+ # Everything a field method or set! can be given. The block is only
362
+ # yielded to, never made into a Proc, which would cost an object per call.
363
+ def __set(info, value, attributes)
364
+ if block_given?
365
+ unless info.message? && !info.map
366
+ raise __error(info, "is not a message field, it cannot take a block")
367
+ end
368
+ if info.repeated
369
+ raise __error(info, "needs a collection to go with the block") if NONE.equal?(value)
370
+ __each(info, value) { |e| yield e }
371
+ else
372
+ __mark(info)
373
+ NONE.equal?(value) ? __nested(info) { yield } : __nested(info) { yield value }
374
+ end
375
+ elsif NONE.equal?(value)
376
+ raise __error(info, "needs a value or a block")
377
+ elsif !attributes.empty?
378
+ raise __error(info, "is not a message field, it cannot extract attributes") unless info.message? && !info.map
379
+ if info.repeated
380
+ __each(info, value) { |e| extract!(e, *attributes) }
381
+ else
382
+ __mark(info)
383
+ __nested(info) { extract!(value, *attributes) }
384
+ end
385
+ else
386
+ __put(info, value)
387
+ end
388
+ end
389
+
390
+ # One value for a field, without a block
391
+ def __put(info, value)
392
+ return if value.nil?
393
+ if info.map
394
+ __map(info, value)
395
+ elsif info.repeated
396
+ __repeated(info, value)
397
+ elsif info.message?
398
+ __mark(info)
399
+ __message(info, value, nil)
400
+ else
401
+ again = __mark(info)
402
+ __scalar(info, value, !again && !info.presence)
403
+ __boundary if @boundary
404
+ end
405
+ end
406
+
407
+ # Records that a singular field was written in the current message.
408
+ # @return [Boolean] whether it had been written before (with overwrites allowed)
409
+ def __mark(info)
410
+ bit = 1 << info.index
411
+ if @written.anybits?(bit)
412
+ return true if @overwrites
413
+ if info.siblings && info.descriptor.real_oneof
414
+ raise __error(info, "oneof #{info.descriptor.real_oneof.name} already has a value; #{NO_OVERWRITES} the last one win")
415
+ end
416
+ raise __error(info, "is already set; #{NO_OVERWRITES} the last value win")
417
+ end
418
+ @written |= bit
419
+ info.siblings&.each { |i| @written |= 1 << i }
420
+ false
421
+ end
422
+
423
+ def __list(value)
424
+ return value if value.is_a?(Array)
425
+ return value.to_ary if value.respond_to?(:to_ary)
426
+ [value]
427
+ end
428
+
429
+ def __repeated(info, value)
430
+ list = __list(value)
431
+ if info.packed
432
+ run = (@pending ||= {}.compare_by_identity)[info]
433
+ if run.nil? || @replace
434
+ # varints are kept as their bytes, a fraction of what an Array of Integers takes
435
+ run = @pending[info] = PACK[info.kind] ? [] : Wire::Writer.new
436
+ end
437
+ if run.is_a?(Array)
438
+ list.each { |e| run << __packed_value(info, e) }
439
+ else
440
+ list.each { |e| run.write_varint(__packed_value(info, e)) }
441
+ end
442
+ elsif @replace
443
+ (@pending ||= {}.compare_by_identity)[info] = list.dup
444
+ else
445
+ __elements(info, list)
446
+ end
447
+ end
448
+
449
+ def __elements(info, list)
450
+ if info.message?
451
+ list.each_with_index { |e, i| __message(info, e, i) }
452
+ elsif @boundary
453
+ list.each do |e|
454
+ __scalar(info, e, false)
455
+ __boundary
456
+ end
457
+ elsif @top.io && info.kind != F::STRING && info.kind != F::BYTES
458
+ # numbers are written without write_raw, which is what flushes a full buffer
459
+ list.each do |e|
460
+ __scalar(info, e, false)
461
+ @w.flush_if_full
462
+ end
463
+ else
464
+ list.each { |e| __scalar(info, e, false) }
465
+ end
466
+ end
467
+
468
+ def __each(info, collection)
469
+ raise __error(info, "needs a collection (something with #each), got #{collection.class}") unless collection.respond_to?(:each)
470
+ i = 0
471
+ collection.each do |e|
472
+ __nested(info, i) { yield e }
473
+ i += 1
474
+ end
475
+ end
476
+
477
+ def __hash(hash)
478
+ by_name = self.class::BY_NAME[@k]
479
+ hash.each do |name, v|
480
+ __put(by_name[name] || __no_field(name), v)
481
+ end
482
+ end
483
+
484
+ # A message value: an instance, a Hash, a well-known type's Ruby form, or anything with to_h
485
+ def __message(info, v, index)
486
+ klass = info.message_class
487
+ if v.instance_of?(klass)
488
+ __encoded(info, v.encode, index)
489
+ elsif v.is_a?(Hash)
490
+ __nested(info, index) { __hash(v) }
491
+ elsif v.is_a?(Message)
492
+ raise __error(info, "expects a #{klass.descriptor.full_name}, got a #{v.class.descriptor.full_name}", index)
493
+ elsif (wk = WELL_KNOWN[klass.descriptor.full_name]) && __well_known(info, wk, v, index)
494
+ nil
495
+ elsif v.respond_to?(:to_h) && !v.is_a?(Array) && !v.is_a?(String)
496
+ __nested(info, index) { __hash(v.to_h) }
497
+ else
498
+ raise __error(info, "expects a #{klass.descriptor.full_name}, a Hash or something with #to_h, got #{__show(v)}", index)
499
+ end
500
+ end
501
+
502
+ def __encoded(info, bytes, index)
503
+ raise __error(info, "expects encoded bytes in a String, got #{__show(bytes)}", index) unless bytes.is_a?(String)
504
+ @buf << info.tag
505
+ group = info.kind == F::GROUP
506
+ __varint(bytes.bytesize) unless group
507
+ @w.write_raw(bytes)
508
+ @buf << info.end_tag if group
509
+ __boundary if @boundary
510
+ end
511
+
512
+ # @return [Boolean] whether `v` was a Ruby form of the well-known type and got written
513
+ def __well_known(info, wk, v, index)
514
+ fields = info.message_class.__fields
515
+ case wk
516
+ when :timestamp
517
+ seconds, nanos = WellKnown::Timestamp.parts(v)
518
+ return false unless seconds
519
+ __nested(info, index) do
520
+ __put(fields[0], seconds)
521
+ __put(fields[1], nanos)
522
+ end
523
+ when :duration
524
+ seconds, nanos = __duration(info, v, index)
525
+ return false unless seconds
526
+ __nested(info, index) do
527
+ __put(fields[0], seconds)
528
+ __put(fields[1], nanos)
529
+ end
530
+ else
531
+ return false if v.respond_to?(:to_h) && !v.is_a?(String)
532
+ __nested(info, index) { __put(fields[0], v) }
533
+ end
534
+ true
535
+ end
536
+
537
+ def __duration(info, v, index)
538
+ WellKnown::Duration.parts(v)
539
+ rescue FloatDomainError
540
+ raise __error(info, "expects a finite duration, got #{__show(v)}", index)
541
+ rescue RangeError
542
+ raise __error(info, "is out of Duration's range: #{__show(v)}", index)
543
+ end
544
+
545
+ # Runs the block with a buffer of its own as the current message, then
546
+ # writes tag, length and the buffer. Groups need no length and are
547
+ # written in place between their tags.
548
+ def __nested(info, index = nil)
549
+ k = self.class::INDEX[info.message_class]
550
+ saved_k = @k
551
+ saved_written = @written
552
+ saved_pending = @pending
553
+ @path << info
554
+ @path << index if index
555
+ if info.kind == F::GROUP
556
+ @buf << info.tag
557
+ # the enclosing buffer holds an open group from here on, so it cannot be split
558
+ @open.push(nil, nil, info, false) if @chunk
559
+ begin
560
+ @k = k
561
+ @written = 0
562
+ @pending = nil
563
+ yield
564
+ __flush_pending if @pending
565
+ ensure
566
+ @k = saved_k
567
+ @written = saved_written
568
+ @pending = saved_pending
569
+ @path.pop
570
+ @path.pop if index
571
+ @open.pop(4) if @chunk
572
+ end
573
+ @buf << info.end_tag
574
+ __boundary if @boundary
575
+ return
576
+ end
577
+ saved_w = @w
578
+ saved_buf = @buf
579
+ child = @spare.pop || Wire::Writer.new
580
+ if @chunk
581
+ # map values and repeated elements must stay whole; so must anything cached
582
+ own = (info.index && !info.repeated && @capturing == 0) ? child : nil
583
+ @open.push(own, saved_w, info, false)
584
+ end
585
+ begin
586
+ @w = child
587
+ @buf = child.string
588
+ @k = k
589
+ @written = 0
590
+ @pending = nil
591
+ yield
592
+ __flush_pending if @pending
593
+ @w = saved_w
594
+ @buf = saved_buf
595
+ # still inside the path, so that a sink failing here names this field
596
+ __emit(saved_w, info, child.string) unless @chunk && @open[-1] && child.string.empty?
597
+ ensure
598
+ @w = saved_w
599
+ @buf = saved_buf
600
+ @k = saved_k
601
+ @written = saved_written
602
+ @pending = saved_pending
603
+ @path.pop
604
+ @path.pop if index
605
+ @open.pop(4) if @chunk
606
+ end
607
+ @spare << child
608
+ __boundary if @boundary
609
+ end
610
+
611
+ # Writes tag, length and `bytes` (then cleared) into `w`. Bytes larger
612
+ # than a sink's buffer go to the sink as they are, not through the buffer.
613
+ def __emit(w, info, bytes)
614
+ w.string << info.tag
615
+ size = bytes.bytesize
616
+ if size < 0x80
617
+ w.string << size
618
+ else
619
+ w.write_varint(size)
620
+ end
621
+ if w.io && size >= BIG && size >= w.flush_at
622
+ w.write_through(bytes)
623
+ else
624
+ w.write_raw(bytes)
625
+ end
626
+ bytes.clear
627
+ end
628
+
629
+ # After a field of the current message is complete: a sink's buffer is
630
+ # written when full, and with chunk_nested_at a nested message past the
631
+ # size is written out as one occurrence of its field
632
+ def __boundary
633
+ if @w.equal?(@top)
634
+ @top.flush_if_full
635
+ elsif @chunk && @open[-4].equal?(@w) && @buf.bytesize >= @chunk
636
+ __chunk_out
637
+ end
638
+ end
639
+
640
+ # Writes the current nested message's bytes so far into its parent, and
641
+ # so on outwards while each parent is also past the size and may be split
642
+ def __chunk_out
643
+ __flush_pending if @pending
644
+ open = @open
645
+ chunk = @chunk
646
+ i = open.size - 4
647
+ parent = nil
648
+ while i >= 0
649
+ own = open[i]
650
+ parent = open[i + 1]
651
+ __emit(parent, open[i + 2], own.string)
652
+ open[i + 3] = true
653
+ i -= 4
654
+ break unless i >= 0 && open[i].equal?(parent) && parent.string.bytesize >= chunk
655
+ end
656
+ @top.flush_if_full if parent.equal?(@top)
657
+ end
658
+
659
+ # One length-prefixed message per element, each the top-level message
660
+ def __sequence(collection)
661
+ top = @top
662
+ child = Wire::Writer.new
663
+ @open.push(nil, nil, nil, false) if @chunk
664
+ i = 0
665
+ collection.each do |e|
666
+ @path << i
667
+ begin
668
+ @w = child
669
+ @buf = child.string
670
+ @written = 0
671
+ @pending = nil
672
+ yield self, e
673
+ __flush_pending if @pending
674
+ @w = top
675
+ @buf = top.string
676
+ bytes = child.string
677
+ top.write_varint(bytes.bytesize)
678
+ if top.io && bytes.bytesize >= BIG && bytes.bytesize >= top.flush_at
679
+ top.write_through(bytes)
680
+ else
681
+ top.write_raw(bytes)
682
+ end
683
+ bytes.clear
684
+ top.flush_if_full
685
+ ensure
686
+ @w = top
687
+ @buf = top.string
688
+ @path.pop
689
+ end
690
+ i += 1
691
+ end
692
+ @open.pop(4) if @chunk
693
+ end
694
+
695
+ def __map(info, value)
696
+ unless value.is_a?(Hash)
697
+ raise __error(info, "is a map, it takes a Hash, got #{__show(value)}") unless value.respond_to?(:to_hash)
698
+ value = value.to_hash
699
+ end
700
+ key = info.key
701
+ val = info.value
702
+ saved_w = @w
703
+ saved_buf = @buf
704
+ value.each do |k, v|
705
+ child = @spare.pop || Wire::Writer.new
706
+ @path << info << k
707
+ begin
708
+ @w = child
709
+ @buf = child.string
710
+ __scalar(key, k, false)
711
+ raise __error(nil, "a map value cannot be nil") if v.nil?
712
+ val.message? ? __message(val, v, nil) : __scalar(val, v, false)
713
+ ensure
714
+ @w = saved_w
715
+ @buf = saved_buf
716
+ @path.pop(2)
717
+ end
718
+ bytes = child.string
719
+ @buf << info.tag
720
+ __varint(bytes.bytesize)
721
+ @w.write_raw(bytes)
722
+ bytes.clear
723
+ @spare << child
724
+ __boundary if @boundary
725
+ end
726
+ end
727
+
728
+ def __varint(n)
729
+ if n < 0x80 && n >= 0
730
+ @buf << n
731
+ else
732
+ @w.write_varint(n)
733
+ end
734
+ end
735
+
736
+ # Writes tag and value of a scalar; with `skip_zero`, writes nothing for the type's zero value
737
+ def __scalar(info, v, skip_zero)
738
+ buf = @buf
739
+ case info.kind
740
+ when F::STRING
741
+ v = __utf8(info, v)
742
+ return if skip_zero && v.empty?
743
+ buf << info.tag
744
+ __varint(v.bytesize)
745
+ @w.write_raw(v)
746
+ when F::BYTES
747
+ raise __error(info, "expects a String, got #{__show(v)}") unless v.is_a?(String)
748
+ return if skip_zero && v.empty?
749
+ buf << info.tag
750
+ __varint(v.bytesize)
751
+ @w.write_raw(v)
752
+ when F::INT32, F::INT64, F::UINT32, F::UINT64
753
+ __int(info, v)
754
+ return if skip_zero && v == 0
755
+ buf << info.tag
756
+ __varint(v)
757
+ when F::SINT32, F::SINT64
758
+ __int(info, v)
759
+ return if skip_zero && v == 0
760
+ buf << info.tag
761
+ __varint((v << 1) ^ (v >> 63))
762
+ when F::BOOL
763
+ raise __error(info, "expects true or false, got #{__show(v)}") unless v == true || v == false
764
+ return if skip_zero && !v
765
+ buf << info.tag << (v ? 1 : 0)
766
+ when F::ENUM
767
+ n = __enum(info, v)
768
+ return if skip_zero && n == 0
769
+ buf << info.tag
770
+ __varint(n)
771
+ when F::DOUBLE, F::FLOAT
772
+ f = __float(info, v)
773
+ return if skip_zero && f == 0 && (1.0 / f) > 0
774
+ buf << info.tag
775
+ if info.kind == F::DOUBLE
776
+ [f].pack("E", buffer: buf)
777
+ elsif f.nan?
778
+ buf << Wire.float_nan(f)
779
+ else
780
+ [f].pack("e", buffer: buf)
781
+ end
782
+ else
783
+ __int(info, v)
784
+ return if skip_zero && v == 0
785
+ buf << info.tag
786
+ [v].pack(F::FIXED.fetch(info.kind)[1], buffer: buf)
787
+ end
788
+ end
789
+
790
+ # A packed element as it goes into the run: an Integer (enum numbers
791
+ # resolved, sint zigzagged), a Float, or 1 or 0 for bools
792
+ def __packed_value(info, v)
793
+ case info.kind
794
+ when F::ENUM then __enum(info, v)
795
+ when F::BOOL
796
+ raise __error(info, "expects true or false, got #{__show(v)}") unless v == true || v == false
797
+ v ? 1 : 0
798
+ when F::DOUBLE, F::FLOAT then __float(info, v)
799
+ when F::SINT32, F::SINT64
800
+ __int(info, v)
801
+ (v << 1) ^ (v >> 63)
802
+ else
803
+ __int(info, v)
804
+ v
805
+ end
806
+ end
807
+
808
+ def __flush_pending
809
+ pending = @pending
810
+ @pending = nil
811
+ fields = pending.keys
812
+ fields.sort_by!(&:number) if fields.size > 1
813
+ fields.each do |info|
814
+ list = pending[info]
815
+ if info.packed
816
+ __packed(info, list) unless list.is_a?(Array) ? list.empty? : list.string.empty?
817
+ else
818
+ __elements(info, list)
819
+ end
820
+ end
821
+ end
822
+
823
+ # A packed run: an Array of fixed-width values, or a writer holding varints
824
+ def __packed(info, run)
825
+ @buf << info.packed_tag
826
+ if run.is_a?(Wire::Writer)
827
+ bytes = run.string
828
+ __varint(bytes.bytesize)
829
+ if @w.io && bytes.bytesize >= BIG && bytes.bytesize >= @w.flush_at
830
+ @w.write_through(bytes)
831
+ else
832
+ @w.write_raw(bytes)
833
+ end
834
+ else
835
+ __varint(F::FIXED.fetch(info.kind)[0] * run.size)
836
+ if info.kind == F::FLOAT
837
+ @buf << Wire.pack_floats(run)
838
+ else
839
+ run.pack(PACK[info.kind], buffer: @buf)
840
+ end
841
+ @w.flush_if_full
842
+ end
843
+ end
844
+
845
+ def __int(info, v)
846
+ return if v.is_a?(Integer) && RANGES.fetch(info.kind).cover?(v)
847
+ raise __error(info, "expects an Integer in #{info.descriptor.type} range, got #{__show(v)}")
848
+ end
849
+
850
+ def __float(info, v)
851
+ raise __error(info, "expects a Numeric, got #{__show(v)}") unless v.is_a?(Numeric)
852
+ f = begin
853
+ v.to_f
854
+ rescue RangeError
855
+ raise __error(info, "expects a real number, got #{__show(v)}")
856
+ end
857
+ (info.kind == F::FLOAT) ? Message::Coercion.float32(f) : f
858
+ end
859
+
860
+ def __utf8(info, v)
861
+ raise __error(info, "expects a String, got #{__show(v)}") unless v.is_a?(String)
862
+ enc = v.encoding
863
+ if enc == UTF_8
864
+ return v if v.valid_encoding?
865
+ elsif enc == BINARY || enc == Encoding::US_ASCII
866
+ return v if v.ascii_only? || v.dup.force_encoding(UTF_8).valid_encoding?
867
+ else
868
+ begin
869
+ return v.encode(UTF_8)
870
+ rescue EncodingError
871
+ raise __error(info, "expects a String convertible to UTF-8, got #{__show(v)}")
872
+ end
873
+ end
874
+ raise __error(info, "expects valid UTF-8, got #{__show(v)}")
875
+ end
876
+
877
+ def __enum(info, v)
878
+ n = case v
879
+ when Symbol then info.enum_numbers[v]
880
+ when String then info.enum_numbers[v.to_sym]
881
+ when Integer
882
+ v if info.enum_names.key?(v) || (!info.closed_enum && ENUM_RANGE.cover?(v))
883
+ end
884
+ n || raise(__error(info, "expects a value of #{info.enum_module.descriptor.full_name}, got #{__show(v)}"))
885
+ end
886
+
887
+ # Runs a cache! block into a buffer of its own, in the current message
888
+ # @return [String] the bytes, frozen
889
+ def __capture(block)
890
+ saved_w = @w
891
+ saved_buf = @buf
892
+ saved_pending = @pending
893
+ child = @spare.pop || Wire::Writer.new
894
+ @capturing += 1
895
+ begin
896
+ @w = child
897
+ @buf = child.string
898
+ @pending = nil
899
+ block.call(self)
900
+ __flush_pending if @pending
901
+ ensure
902
+ @w = saved_w
903
+ @buf = saved_buf
904
+ @pending = saved_pending
905
+ @capturing -= 1
906
+ end
907
+ bytes = child.string.dup.freeze
908
+ child.string.clear
909
+ @spare << child
910
+ bytes
911
+ end
912
+
913
+ # A cached fragment's singular fields count as written, so that writing
914
+ # one of them again is caught (or, with overwrites, written even when zero)
915
+ def __mark_fragment(bytes)
916
+ by_number = self.class::CLASSES[@k].__by_number
917
+ r = Wire::Reader.new(bytes)
918
+ until r.eof?
919
+ number, wire_type = r.read_tag
920
+ r.skip_field(number, wire_type)
921
+ info = by_number[number]
922
+ __mark(info) if info && !info.repeated && !info.map
923
+ end
924
+ rescue DecodeError => e
925
+ raise __error(nil, "a cached fragment is not valid wire data (#{e.message})")
926
+ end
927
+
928
+ def __show(v)
929
+ s = v.inspect
930
+ (s.length > 64) ? "#{s[0, 60]}..." : s
931
+ end
932
+
933
+ # Where the builder is, for an error from the sink
934
+ def __where
935
+ path = __path(nil)
936
+ path.empty? ? "the top level of #{@root.descriptor.full_name}" : path
937
+ end
938
+
939
+ def __error(info, message, index = nil)
940
+ Error.new(message, path: __path(info, index))
941
+ end
942
+
943
+ def __path(info, index = nil)
944
+ out = +""
945
+ segments = @path
946
+ segments += [info] if info
947
+ segments += [index] if index
948
+ segments.each do |s|
949
+ if s.is_a?(F)
950
+ next unless s.index # a map's key and value
951
+ out << "." unless out.empty?
952
+ out << s.name
953
+ elsif s.is_a?(Integer)
954
+ out << "[#{s}]"
955
+ else
956
+ out << "[#{s.inspect}]"
957
+ end
958
+ end
959
+ out
960
+ end
961
+ end
962
+ end
963
+
964
+ require_relative "builder/generator"
965
+ require_relative "builder/sink"