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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +60 -0
- data/LICENSE.txt +21 -0
- data/README.md +647 -0
- data/THIRD_PARTY_NOTICES.md +61 -0
- data/exe/protoc-gen-protostar +7 -0
- data/exe/protostar +7 -0
- data/lib/protostar/builder/generator.rb +71 -0
- data/lib/protostar/builder/sink.rb +35 -0
- data/lib/protostar/builder.rb +965 -0
- data/lib/protostar/cli.rb +267 -0
- data/lib/protostar/codec/decode_generator.rb +199 -0
- data/lib/protostar/codec/decoder.rb +296 -0
- data/lib/protostar/codec/encode_generator.rb +224 -0
- data/lib/protostar/codec/encoder.rb +121 -0
- data/lib/protostar/codec/sizer.rb +91 -0
- data/lib/protostar/codec.rb +15 -0
- data/lib/protostar/compiler/ast.rb +193 -0
- data/lib/protostar/compiler/emitters/descriptor_set.rb +57 -0
- data/lib/protostar/compiler/emitters/google_ruby.rb +103 -0
- data/lib/protostar/compiler/emitters/grpc.rb +175 -0
- data/lib/protostar/compiler/emitters/protostar_rbs.rb +252 -0
- data/lib/protostar/compiler/emitters/rbs.rb +394 -0
- data/lib/protostar/compiler/emitters/ruby.rb +503 -0
- data/lib/protostar/compiler/emitters.rb +268 -0
- data/lib/protostar/compiler/include/LICENSE +32 -0
- data/lib/protostar/compiler/include/google/protobuf/any.proto +106 -0
- data/lib/protostar/compiler/include/google/protobuf/api.proto +229 -0
- data/lib/protostar/compiler/include/google/protobuf/compiler/plugin.proto +180 -0
- data/lib/protostar/compiler/include/google/protobuf/cpp_features.proto +87 -0
- data/lib/protostar/compiler/include/google/protobuf/descriptor.proto +1532 -0
- data/lib/protostar/compiler/include/google/protobuf/duration.proto +115 -0
- data/lib/protostar/compiler/include/google/protobuf/empty.proto +51 -0
- data/lib/protostar/compiler/include/google/protobuf/field_mask.proto +243 -0
- data/lib/protostar/compiler/include/google/protobuf/java_features.proto +132 -0
- data/lib/protostar/compiler/include/google/protobuf/source_context.proto +48 -0
- data/lib/protostar/compiler/include/google/protobuf/struct.proto +111 -0
- data/lib/protostar/compiler/include/google/protobuf/timestamp.proto +145 -0
- data/lib/protostar/compiler/include/google/protobuf/type.proto +217 -0
- data/lib/protostar/compiler/include/google/protobuf/wrappers.proto +157 -0
- data/lib/protostar/compiler/lexer.rb +659 -0
- data/lib/protostar/compiler/linker.rb +894 -0
- data/lib/protostar/compiler/option_interpreter.rb +363 -0
- data/lib/protostar/compiler/parser.rb +1696 -0
- data/lib/protostar/compiler/retention.rb +104 -0
- data/lib/protostar/compiler/run.rb +184 -0
- data/lib/protostar/compiler/source_info.rb +161 -0
- data/lib/protostar/compiler/symbol_table.rb +223 -0
- data/lib/protostar/compiler/text_format.rb +340 -0
- data/lib/protostar/compiler/validator.rb +944 -0
- data/lib/protostar/compiler.rb +304 -0
- data/lib/protostar/diagnostics/codes.rb +1307 -0
- data/lib/protostar/diagnostics/formats.rb +64 -0
- data/lib/protostar/diagnostics/renderer.rb +240 -0
- data/lib/protostar/diagnostics/source.rb +153 -0
- data/lib/protostar/diagnostics.rb +117 -0
- data/lib/protostar/dsl.rb +367 -0
- data/lib/protostar/enum.rb +70 -0
- data/lib/protostar/errors.rb +23 -0
- data/lib/protostar/google/protobuf/any_pb.rb +90 -0
- data/lib/protostar/google/protobuf/api_pb.rb +199 -0
- data/lib/protostar/google/protobuf/compiler/plugin_pb.rb +168 -0
- data/lib/protostar/google/protobuf/descriptor_pb.rb +1239 -0
- data/lib/protostar/google/protobuf/duration_pb.rb +99 -0
- data/lib/protostar/google/protobuf/empty_pb.rb +36 -0
- data/lib/protostar/google/protobuf/field_mask_pb.rb +228 -0
- data/lib/protostar/google/protobuf/source_context_pb.rb +33 -0
- data/lib/protostar/google/protobuf/struct_pb.rb +91 -0
- data/lib/protostar/google/protobuf/timestamp_pb.rb +129 -0
- data/lib/protostar/google/protobuf/type_pb.rb +202 -0
- data/lib/protostar/google/protobuf/wrappers_pb.rb +125 -0
- data/lib/protostar/google_protobuf_shim.rb +24 -0
- data/lib/protostar/json/encoder.rb +340 -0
- data/lib/protostar/json/errors.rb +34 -0
- data/lib/protostar/json/parser.rb +437 -0
- data/lib/protostar/json/path.rb +39 -0
- data/lib/protostar/json.rb +95 -0
- data/lib/protostar/lint/command.rb +197 -0
- data/lib/protostar/lint/config.rb +98 -0
- data/lib/protostar/lint/context.rb +126 -0
- data/lib/protostar/lint/directives.rb +56 -0
- data/lib/protostar/lint/fixer.rb +165 -0
- data/lib/protostar/lint/naming.rb +54 -0
- data/lib/protostar/lint/rule.rb +120 -0
- data/lib/protostar/lint/rules/comment_enum.rb +31 -0
- data/lib/protostar/lint/rules/comment_enum_value.rb +32 -0
- data/lib/protostar/lint/rules/comment_field.rb +33 -0
- data/lib/protostar/lint/rules/comment_message.rb +31 -0
- data/lib/protostar/lint/rules/comment_rpc.rb +31 -0
- data/lib/protostar/lint/rules/comment_service.rb +31 -0
- data/lib/protostar/lint/rules/edition_recommended.rb +31 -0
- data/lib/protostar/lint/rules/enum_first_value_zero.rb +34 -0
- data/lib/protostar/lint/rules/enum_pascal_case.rb +25 -0
- data/lib/protostar/lint/rules/enum_value_prefix.rb +38 -0
- data/lib/protostar/lint/rules/enum_value_upper_snake_case.rb +28 -0
- data/lib/protostar/lint/rules/enum_zero_value_suffix.rb +41 -0
- data/lib/protostar/lint/rules/field_lower_snake_case.rb +34 -0
- data/lib/protostar/lint/rules/field_not_required.rb +39 -0
- data/lib/protostar/lint/rules/field_number_reuse_risk.rb +45 -0
- data/lib/protostar/lint/rules/field_numbers_hot_range.rb +44 -0
- data/lib/protostar/lint/rules/file_lower_snake_case.rb +27 -0
- data/lib/protostar/lint/rules/import_no_public.rb +29 -0
- data/lib/protostar/lint/rules/import_no_weak.rb +28 -0
- data/lib/protostar/lint/rules/import_used.rb +33 -0
- data/lib/protostar/lint/rules/message_pascal_case.rb +27 -0
- data/lib/protostar/lint/rules/no_deprecated_features.rb +50 -0
- data/lib/protostar/lint/rules/no_groups.rb +34 -0
- data/lib/protostar/lint/rules/oneof_lower_snake_case.rb +27 -0
- data/lib/protostar/lint/rules/package_defined.rb +28 -0
- data/lib/protostar/lint/rules/package_directory_match.rb +33 -0
- data/lib/protostar/lint/rules/package_lower_snake_case.rb +28 -0
- data/lib/protostar/lint/rules/package_same_directory.rb +39 -0
- data/lib/protostar/lint/rules/package_version_suffix.rb +31 -0
- data/lib/protostar/lint/rules/reserved_names_with_numbers.rb +44 -0
- data/lib/protostar/lint/rules/rpc_pascal_case.rb +25 -0
- data/lib/protostar/lint/rules/rpc_request_response_unique.rb +40 -0
- data/lib/protostar/lint/rules/rpc_request_standard_name.rb +32 -0
- data/lib/protostar/lint/rules/rpc_response_standard_name.rb +31 -0
- data/lib/protostar/lint/rules/service_pascal_case.rb +25 -0
- data/lib/protostar/lint/rules/service_suffix.rb +28 -0
- data/lib/protostar/lint/rules/syntax_specified.rb +35 -0
- data/lib/protostar/lint/runner.rb +135 -0
- data/lib/protostar/lint.rb +122 -0
- data/lib/protostar/message/accessors.rb +86 -0
- data/lib/protostar/message/coercion.rb +171 -0
- data/lib/protostar/message/field_info.rb +145 -0
- data/lib/protostar/message/reflection.rb +149 -0
- data/lib/protostar/message.rb +572 -0
- data/lib/protostar/plugin.rb +94 -0
- data/lib/protostar/schema/default_value.rb +113 -0
- data/lib/protostar/schema/descriptor.rb +41 -0
- data/lib/protostar/schema/draft/copy.rb +88 -0
- data/lib/protostar/schema/draft/resolution.rb +177 -0
- data/lib/protostar/schema/draft.rb +779 -0
- data/lib/protostar/schema/enum.rb +106 -0
- data/lib/protostar/schema/extension_range.rb +37 -0
- data/lib/protostar/schema/features.rb +153 -0
- data/lib/protostar/schema/field.rb +201 -0
- data/lib/protostar/schema/file.rb +217 -0
- data/lib/protostar/schema/freezer.rb +61 -0
- data/lib/protostar/schema/message.rb +104 -0
- data/lib/protostar/schema/oneof.rb +38 -0
- data/lib/protostar/schema/options.rb +367 -0
- data/lib/protostar/schema/pool.rb +179 -0
- data/lib/protostar/schema/proto.rb +576 -0
- data/lib/protostar/schema/resolver.rb +118 -0
- data/lib/protostar/schema/service.rb +89 -0
- data/lib/protostar/schema/source_info.rb +53 -0
- data/lib/protostar/schema.rb +65 -0
- data/lib/protostar/short_names/any.rb +7 -0
- data/lib/protostar/short_names/duration.rb +7 -0
- data/lib/protostar/short_names/empty.rb +7 -0
- data/lib/protostar/short_names/field_mask.rb +7 -0
- data/lib/protostar/short_names/struct.rb +9 -0
- data/lib/protostar/short_names/timestamp.rb +7 -0
- data/lib/protostar/short_names/wrappers.rb +15 -0
- data/lib/protostar/text/extensions.rb +114 -0
- data/lib/protostar/text/parser.rb +439 -0
- data/lib/protostar/text/printer.rb +304 -0
- data/lib/protostar/text/tokenizer.rb +116 -0
- data/lib/protostar/text.rb +67 -0
- data/lib/protostar/types.rb +152 -0
- data/lib/protostar/unknown_field.rb +58 -0
- data/lib/protostar/version.rb +5 -0
- data/lib/protostar/view/generator.rb +298 -0
- data/lib/protostar/view/map.rb +194 -0
- data/lib/protostar/view/repeated.rb +143 -0
- data/lib/protostar/view/scan.rb +183 -0
- data/lib/protostar/view.rb +283 -0
- data/lib/protostar/well_known/any.rb +52 -0
- data/lib/protostar/well_known/duration.rb +53 -0
- data/lib/protostar/well_known/list_value.rb +63 -0
- data/lib/protostar/well_known/struct.rb +55 -0
- data/lib/protostar/well_known/timestamp.rb +64 -0
- data/lib/protostar/well_known/value.rb +60 -0
- data/lib/protostar/well_known.rb +38 -0
- data/lib/protostar/wire/io_source.rb +40 -0
- data/lib/protostar/wire/reader.rb +254 -0
- data/lib/protostar/wire/writer.rb +201 -0
- data/lib/protostar/wire.rb +96 -0
- data/lib/protostar.rb +95 -0
- data/shims/google-protobuf/lib/google/protobuf/any_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/api_pb.rb +22 -0
- data/shims/google-protobuf/lib/google/protobuf/compiler/plugin_pb.rb +25 -0
- data/shims/google-protobuf/lib/google/protobuf/descriptor_pb.rb +72 -0
- data/shims/google-protobuf/lib/google/protobuf/descriptor_pool.rb +164 -0
- data/shims/google-protobuf/lib/google/protobuf/descriptors.rb +553 -0
- data/shims/google-protobuf/lib/google/protobuf/duration_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/empty_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/field_mask_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/convert.rb +180 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/extensions.rb +154 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/json.rb +104 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/well_known.rb +140 -0
- data/shims/google-protobuf/lib/google/protobuf/map.rb +216 -0
- data/shims/google-protobuf/lib/google/protobuf/message.rb +544 -0
- data/shims/google-protobuf/lib/google/protobuf/message_exts.rb +41 -0
- data/shims/google-protobuf/lib/google/protobuf/plugin_pb.rb +4 -0
- data/shims/google-protobuf/lib/google/protobuf/repeated_field.rb +366 -0
- data/shims/google-protobuf/lib/google/protobuf/source_context_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/struct_pb.rb +20 -0
- data/shims/google-protobuf/lib/google/protobuf/timestamp_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/type_pb.rb +27 -0
- data/shims/google-protobuf/lib/google/protobuf/well_known_types.rb +37 -0
- data/shims/google-protobuf/lib/google/protobuf/wrappers_pb.rb +25 -0
- data/shims/google-protobuf/lib/google/protobuf.rb +107 -0
- data/shims/google-protobuf/lib/google/protobuf_ffi.rb +5 -0
- data/shims/google-protobuf/lib/google/protobuf_native.rb +5 -0
- data/sig/protostar/google/protobuf/any_pb.rbs +77 -0
- data/sig/protostar/google/protobuf/api_pb.rbs +242 -0
- data/sig/protostar/google/protobuf/compiler/plugin_pb.rbs +264 -0
- data/sig/protostar/google/protobuf/descriptor_pb.rbs +2220 -0
- data/sig/protostar/google/protobuf/duration_pb.rbs +82 -0
- data/sig/protostar/google/protobuf/empty_pb.rbs +18 -0
- data/sig/protostar/google/protobuf/field_mask_pb.rbs +170 -0
- data/sig/protostar/google/protobuf/source_context_pb.rbs +24 -0
- data/sig/protostar/google/protobuf/struct_pb.rbs +135 -0
- data/sig/protostar/google/protobuf/timestamp_pb.rbs +102 -0
- data/sig/protostar/google/protobuf/type_pb.rbs +304 -0
- data/sig/protostar/google/protobuf/wrappers_pb.rbs +169 -0
- data/sig/protostar/lint.rbs +216 -0
- data/sig/protostar.rbs +326 -0
- 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"
|