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,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
class View
|
|
5
|
+
# What the generated index builders and the lazy containers share: the
|
|
6
|
+
# rare paths of the scan, and turning indexed positions into values.
|
|
7
|
+
# Every check mirrors the eager decoder's, with the same messages and offsets.
|
|
8
|
+
# @api private
|
|
9
|
+
module Scan
|
|
10
|
+
F = Message::FieldInfo
|
|
11
|
+
UTF_8 = Encoding::UTF_8
|
|
12
|
+
BINARY = Encoding::BINARY
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# A tag the class has no branch for: an end-group tag, a malformed tag,
|
|
17
|
+
# an unknown field or a known one with the wrong wire type. The field is
|
|
18
|
+
# skipped and its range kept for {View#unknown_fields}.
|
|
19
|
+
def other(c, tag, at, ix, u, depth)
|
|
20
|
+
number = tag >> 3
|
|
21
|
+
wt = tag & 7
|
|
22
|
+
raise DecodeError.new("unmatched end of group #{number}", offset: at) if wt == Wire::END_GROUP
|
|
23
|
+
Wire::Reader.check_tag(tag, at)
|
|
24
|
+
start = c.pos
|
|
25
|
+
c.skip_field(number, wt, depth)
|
|
26
|
+
(ix[u] ||= []) << number << wt << start << c.pos
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Skips a group's contents and its end tag
|
|
30
|
+
# @return [Integer] the offset of the end tag, where the contents stop
|
|
31
|
+
def group_end(c, number, depth)
|
|
32
|
+
loop do
|
|
33
|
+
raise DecodeError.new("missing end of group #{number}", offset: c.pos) if c.pos >= c.limit
|
|
34
|
+
at = c.pos
|
|
35
|
+
tag = c.read_varint
|
|
36
|
+
n = tag >> 3
|
|
37
|
+
wt = tag & 7
|
|
38
|
+
if wt == Wire::END_GROUP
|
|
39
|
+
return at if n == number
|
|
40
|
+
raise DecodeError.new("unmatched end of group #{n}", offset: at)
|
|
41
|
+
end
|
|
42
|
+
Wire::Reader.check_tag(tag, at)
|
|
43
|
+
c.skip_field(n, wt, depth)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Checks that a packed run is whole varints, as reading it would
|
|
48
|
+
def check_varints(buf, pos, limit)
|
|
49
|
+
while pos < limit
|
|
50
|
+
start = pos
|
|
51
|
+
shift = 0
|
|
52
|
+
loop do
|
|
53
|
+
raise DecodeError.new("truncated varint", offset: start) if pos >= limit
|
|
54
|
+
byte = buf.getbyte(pos)
|
|
55
|
+
pos += 1
|
|
56
|
+
break if byte < 0x80
|
|
57
|
+
shift += 7
|
|
58
|
+
raise DecodeError.new("varint longer than 10 bytes", offset: start) if shift >= 70
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# A packed run of a closed enum: numbers without a name become unknown fields
|
|
64
|
+
def packed_closed_enum(c, pos, limit, ix, u, number, names)
|
|
65
|
+
c.pos = pos
|
|
66
|
+
old = c.push_limit(limit - pos)
|
|
67
|
+
while c.pos < limit
|
|
68
|
+
start = c.pos
|
|
69
|
+
n = c.read_int32
|
|
70
|
+
(ix[u] ||= []) << number << Wire::VARINT << start << c.pos unless names.key?(n)
|
|
71
|
+
end
|
|
72
|
+
c.pop_limit(old)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @return [Boolean] whether a map entry's value is a closed enum's
|
|
76
|
+
# unknown number, which sends the whole entry to the unknown fields
|
|
77
|
+
def map_unknown_enum?(buf, pos, length, info, depth)
|
|
78
|
+
Map.entry(buf, pos, length, info, depth)[2]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def string(buf, offset, length, info)
|
|
82
|
+
s = buf.byteslice(offset, length).force_encoding(UTF_8)
|
|
83
|
+
if info.utf8_validation && !s.valid_encoding?
|
|
84
|
+
raise DecodeError.new("invalid UTF-8 in #{info.descriptor.full_name}", offset: offset)
|
|
85
|
+
end
|
|
86
|
+
s.freeze
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def bytes(buf, offset, length)
|
|
90
|
+
buf.byteslice(offset, length).force_encoding(BINARY).freeze
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# The value of a varint-encoded kind from the raw unsigned varint;
|
|
94
|
+
# enums arrive already narrowed to int32
|
|
95
|
+
def varint_value(kind, v, info)
|
|
96
|
+
case kind
|
|
97
|
+
when F::INT32
|
|
98
|
+
v &= Wire::MASK32
|
|
99
|
+
(v >= 0x8000_0000) ? v - 0x1_0000_0000 : v
|
|
100
|
+
when F::INT64 then (v >= 0x8000_0000_0000_0000) ? v - 0x1_0000_0000_0000_0000 : v
|
|
101
|
+
when F::UINT32 then v & Wire::MASK32
|
|
102
|
+
when F::UINT64 then v
|
|
103
|
+
when F::SINT32
|
|
104
|
+
v &= Wire::MASK32
|
|
105
|
+
(v >> 1) ^ -(v & 1)
|
|
106
|
+
when F::SINT64 then (v >> 1) ^ -(v & 1)
|
|
107
|
+
when F::BOOL then v != 0
|
|
108
|
+
when F::ENUM then info.enum_names[v] || v
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Every element of a repeated scalar field. `entries` holds pairs: a
|
|
113
|
+
# value (varint kinds) or an offset (fixed kinds) and -1 for one
|
|
114
|
+
# element, or an offset and a length for a packed run.
|
|
115
|
+
# @return [Array] frozen
|
|
116
|
+
def scalars(buf, info, entries)
|
|
117
|
+
out = []
|
|
118
|
+
kind = info.kind
|
|
119
|
+
k = 0
|
|
120
|
+
size = entries.size
|
|
121
|
+
if (fixed = F::FIXED[kind])
|
|
122
|
+
width, directive = fixed
|
|
123
|
+
while k < size
|
|
124
|
+
a = entries[k]
|
|
125
|
+
b = entries[k + 1]
|
|
126
|
+
if b < 0
|
|
127
|
+
out << buf.unpack1(directive, offset: a)
|
|
128
|
+
else
|
|
129
|
+
stop = a + b
|
|
130
|
+
while a < stop
|
|
131
|
+
out << buf.unpack1(directive, offset: a)
|
|
132
|
+
a += width
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
k += 2
|
|
136
|
+
end
|
|
137
|
+
else
|
|
138
|
+
c = nil
|
|
139
|
+
enum = kind == F::ENUM
|
|
140
|
+
names = info.enum_names
|
|
141
|
+
while k < size
|
|
142
|
+
a = entries[k]
|
|
143
|
+
b = entries[k + 1]
|
|
144
|
+
if b < 0
|
|
145
|
+
out << varint_value(kind, a, info)
|
|
146
|
+
else
|
|
147
|
+
c ||= Wire::Reader.new(buf, 0, buf.bytesize)
|
|
148
|
+
c.pos = a
|
|
149
|
+
old = c.push_limit(b)
|
|
150
|
+
stop = a + b
|
|
151
|
+
while c.pos < stop
|
|
152
|
+
if enum
|
|
153
|
+
n = c.read_int32
|
|
154
|
+
if (name = names[n])
|
|
155
|
+
out << name
|
|
156
|
+
elsif !info.closed_enum
|
|
157
|
+
out << n
|
|
158
|
+
end
|
|
159
|
+
else
|
|
160
|
+
out << varint_value(kind, c.read_varint, info)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
c.pop_limit(old)
|
|
164
|
+
end
|
|
165
|
+
k += 2
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
out.freeze
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# A nested view over the segments of a message value
|
|
172
|
+
def nested(info, buf, segments, depth)
|
|
173
|
+
raise DecodeError.new("recursion limit exceeded", offset: segments[0]) if depth <= 0
|
|
174
|
+
klass = info.message_class.__view_class
|
|
175
|
+
case segments.size
|
|
176
|
+
when 0 then klass.__new(buf, 0, 0, nil, depth - 1)
|
|
177
|
+
when 2 then klass.__new(buf, segments[0], segments[1], nil, depth - 1)
|
|
178
|
+
else klass.__new(buf, segments[0], segments[1], segments.freeze, depth - 1)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
# A read-only message over the bytes it was encoded in. Making a view
|
|
5
|
+
# scans the top level once and keeps where each field is; nothing is
|
|
6
|
+
# decoded until a reader asks, and what a reader decodes is kept. Nested
|
|
7
|
+
# messages are views over slices of the same buffer, so reading one
|
|
8
|
+
# field of a large message copies nothing but that field's value.
|
|
9
|
+
#
|
|
10
|
+
# view = Person.view(bytes) # a String or an IO::Buffer
|
|
11
|
+
# view.name # => "Ada"
|
|
12
|
+
# view.friends[2].name # a nested view, same buffer
|
|
13
|
+
# view.encode.equal?(bytes) # => true
|
|
14
|
+
# view.to_message # the mutable message, decoded eagerly
|
|
15
|
+
#
|
|
16
|
+
# Every message class has a view class, `Person.view_class`, with the
|
|
17
|
+
# same readers as the message: scalars, enums, `has_`, `which_`,
|
|
18
|
+
# repeated fields as {Repeated}, maps as {Map}. Views are frozen; writers
|
|
19
|
+
# raise FrozenError. A view reads the buffer it was given for as long as
|
|
20
|
+
# it lives, so that buffer must not change underneath it.
|
|
21
|
+
#
|
|
22
|
+
# Malformed input raises {DecodeError} when the view is made, for the top
|
|
23
|
+
# level; a nested message is checked when it is first read, and a string
|
|
24
|
+
# with invalid UTF-8 when the string is read.
|
|
25
|
+
class View
|
|
26
|
+
class << self
|
|
27
|
+
# @return [Class] the message class this view class reads
|
|
28
|
+
attr_reader :message_class
|
|
29
|
+
|
|
30
|
+
# @api private
|
|
31
|
+
attr_reader :__fields, :__by_name, :__readers, :__unknown_slot
|
|
32
|
+
|
|
33
|
+
# @return [Schema::Message]
|
|
34
|
+
def descriptor
|
|
35
|
+
@message_class.descriptor
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def inspect
|
|
39
|
+
name || "#<Class #{@message_class ? "#{@message_class.descriptor.full_name}::View" : "Protostar::View"}>"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @api private
|
|
43
|
+
def __define(message_class, fields)
|
|
44
|
+
@message_class = message_class
|
|
45
|
+
@__fields = message_class.__fields
|
|
46
|
+
@__by_name = message_class.__by_name
|
|
47
|
+
@__readers = Ractor.make_shareable(fields.map { |f| :"__f#{f.index}" })
|
|
48
|
+
@__unknown_slot = 3 * fields.size
|
|
49
|
+
self
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @api private
|
|
53
|
+
def __new(buf, offset, length, segments, depth)
|
|
54
|
+
new(buf, offset, length, segments, depth)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @api private
|
|
58
|
+
def __open(bytes, offset, length, depth)
|
|
59
|
+
raise NotImplementedError, "#{self} is abstract; views come from message classes" unless @message_class
|
|
60
|
+
buf = if bytes.is_a?(String)
|
|
61
|
+
bytes
|
|
62
|
+
elsif defined?(IO::Buffer) && bytes.is_a?(IO::Buffer)
|
|
63
|
+
Wire::IOSource.new(bytes)
|
|
64
|
+
else
|
|
65
|
+
raise ::TypeError, "expected a String or IO::Buffer, got #{bytes.class}"
|
|
66
|
+
end
|
|
67
|
+
size = buf.bytesize
|
|
68
|
+
length ||= size - offset
|
|
69
|
+
if offset < 0 || length < 0 || offset + length > size
|
|
70
|
+
raise ArgumentError, "offset #{offset} and length #{length} do not fit a buffer of #{size} bytes"
|
|
71
|
+
end
|
|
72
|
+
new(buf, offset, length, nil, depth)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private :new
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @return [Schema::Message]
|
|
79
|
+
def descriptor
|
|
80
|
+
self.class.descriptor
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @param name [String, Symbol]
|
|
84
|
+
# @return [Object] what the field's reader returns
|
|
85
|
+
# @raise [ArgumentError] when there is no such field
|
|
86
|
+
def [](name)
|
|
87
|
+
info = self.class.__by_name[name] || raise(ArgumentError, "#{descriptor.full_name} has no field #{name.inspect}")
|
|
88
|
+
__send__(self.class.__readers[info.index])
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @raise [FrozenError] always
|
|
92
|
+
def []=(_name, _value)
|
|
93
|
+
__frozen!
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @raise [FrozenError] always
|
|
97
|
+
def merge!(_other)
|
|
98
|
+
__frozen!
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @raise [FrozenError] always
|
|
102
|
+
def clear!
|
|
103
|
+
__frozen!
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @return [Array<UnknownField>] frozen, in the order they occur
|
|
107
|
+
def unknown_fields
|
|
108
|
+
ix = @ix
|
|
109
|
+
u = self.class.__unknown_slot
|
|
110
|
+
cached = ix[u + 1]
|
|
111
|
+
return cached if cached
|
|
112
|
+
ranges = ix[u]
|
|
113
|
+
return Message::EMPTY_ARRAY if ranges.nil?
|
|
114
|
+
list = []
|
|
115
|
+
k = 0
|
|
116
|
+
while k < ranges.size
|
|
117
|
+
start = ranges[k + 2]
|
|
118
|
+
bytes = @buf.byteslice(start, ranges[k + 3] - start).force_encoding(Encoding::BINARY).freeze
|
|
119
|
+
list << UnknownField.new(ranges[k], ranges[k + 1], bytes)
|
|
120
|
+
k += 4
|
|
121
|
+
end
|
|
122
|
+
ix[u + 1] = list.freeze
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# @return [String] the bytes this view reads: the very String it was
|
|
126
|
+
# made from when it covers all of it, otherwise a copy of its part
|
|
127
|
+
def encode
|
|
128
|
+
buf = @buf
|
|
129
|
+
if @segs.nil?
|
|
130
|
+
return buf if @off == 0 && buf.is_a?(String) && @len == buf.bytesize
|
|
131
|
+
return buf.byteslice(@off, @len).force_encoding(Encoding::BINARY)
|
|
132
|
+
end
|
|
133
|
+
out = (+"").force_encoding(Encoding::BINARY)
|
|
134
|
+
__each_segment { |offset, length| out << buf.byteslice(offset, length) }
|
|
135
|
+
out
|
|
136
|
+
end
|
|
137
|
+
alias_method :to_proto, :encode
|
|
138
|
+
|
|
139
|
+
# Decodes the view's bytes into a message, as {Message.decode} would
|
|
140
|
+
# @param frozen [Boolean] return a deep-frozen, Ractor-shareable message
|
|
141
|
+
# @return [Message]
|
|
142
|
+
def to_message(frozen: false)
|
|
143
|
+
msg = self.class.message_class.__empty
|
|
144
|
+
buf = @buf
|
|
145
|
+
depth = @depth
|
|
146
|
+
__each_segment do |offset, length|
|
|
147
|
+
c = Wire::Reader.new(buf, offset, offset + length)
|
|
148
|
+
msg.__decode_fields(c, depth, 0, frozen)
|
|
149
|
+
end
|
|
150
|
+
msg.freeze if frozen
|
|
151
|
+
msg
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @return [Hash] what {Message#to_h} gives for the same bytes
|
|
155
|
+
def to_h
|
|
156
|
+
__to_h
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# @api private
|
|
160
|
+
def __to_h
|
|
161
|
+
h = {}
|
|
162
|
+
ix = @ix
|
|
163
|
+
readers = self.class.__readers
|
|
164
|
+
self.class.__fields.each do |info|
|
|
165
|
+
next if ix[info.index].nil?
|
|
166
|
+
v = __send__(readers[info.index])
|
|
167
|
+
if info.map
|
|
168
|
+
next if v.empty?
|
|
169
|
+
h[info.symbol] = info.value.message? ? v.to_h.transform_values(&:__to_h) : v.to_h.dup
|
|
170
|
+
elsif info.repeated
|
|
171
|
+
next if v.empty?
|
|
172
|
+
h[info.symbol] = info.message? ? v.map(&:__to_h) : v.to_a.dup
|
|
173
|
+
elsif info.message?
|
|
174
|
+
h[info.symbol] = v.__to_h
|
|
175
|
+
elsif info.presence || !info.zero?(v)
|
|
176
|
+
h[info.symbol] = v
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
h
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Equal to a message of the view's class, or a view of it, with the
|
|
183
|
+
# same field values (unknown fields aside), as {Message#==} compares
|
|
184
|
+
def ==(other)
|
|
185
|
+
return true if equal?(other)
|
|
186
|
+
case other
|
|
187
|
+
when View then other.instance_of?(self.class) && to_message == other.to_message
|
|
188
|
+
when Message then other.instance_of?(self.class.message_class) && to_message == other
|
|
189
|
+
else false
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
alias_method :eql?, :==
|
|
193
|
+
|
|
194
|
+
def hash
|
|
195
|
+
to_message.hash
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Reads every field, nested views and containers included, and freezes
|
|
199
|
+
# what was read. A view over a frozen String is then Ractor-shareable.
|
|
200
|
+
# @return [self]
|
|
201
|
+
def freeze_index
|
|
202
|
+
ix = @ix
|
|
203
|
+
return self if ix.frozen?
|
|
204
|
+
readers = self.class.__readers
|
|
205
|
+
self.class.__fields.each do |info|
|
|
206
|
+
next if ix[info.index].nil?
|
|
207
|
+
v = __send__(readers[info.index])
|
|
208
|
+
v.freeze_index if v.is_a?(View) || v.is_a?(Repeated) || v.is_a?(Map)
|
|
209
|
+
end
|
|
210
|
+
unknown_fields
|
|
211
|
+
ix.each { |e| e.freeze if e.is_a?(Array) }
|
|
212
|
+
@segs&.freeze
|
|
213
|
+
ix.freeze
|
|
214
|
+
self
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def inspect
|
|
218
|
+
parts = __shown.map { |name, v| "#{name}: #{v.inspect}" }
|
|
219
|
+
name = "view #{descriptor.full_name}"
|
|
220
|
+
parts.empty? ? "#<#{name}>" : "#<#{name} #{parts.join(", ")}>"
|
|
221
|
+
end
|
|
222
|
+
alias_method :to_s, :inspect
|
|
223
|
+
|
|
224
|
+
# For `pp`, laid out as {Message#pretty_print} lays out messages
|
|
225
|
+
def pretty_print(q)
|
|
226
|
+
Message.__pretty_print(q, "view #{descriptor.full_name}", __shown)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# @return [Array<Array(String, Object)>] the fields `inspect` shows
|
|
230
|
+
# @api private
|
|
231
|
+
def __shown
|
|
232
|
+
shown = []
|
|
233
|
+
ix = @ix
|
|
234
|
+
readers = self.class.__readers
|
|
235
|
+
self.class.__fields.each do |info|
|
|
236
|
+
next if ix[info.index].nil?
|
|
237
|
+
v = __send__(readers[info.index])
|
|
238
|
+
next if (info.repeated || info.map) && v.empty?
|
|
239
|
+
next if !info.presence && !info.repeated && !info.map && info.zero?(v)
|
|
240
|
+
shown << [info.name, v]
|
|
241
|
+
end
|
|
242
|
+
shown
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# @return [Array<Array(Integer, Integer)>] offset and length of each
|
|
246
|
+
# piece of the buffer this view reads, in order
|
|
247
|
+
# @api private
|
|
248
|
+
def __segments
|
|
249
|
+
list = []
|
|
250
|
+
__each_segment { |offset, length| list << [offset, length] }
|
|
251
|
+
list
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
private
|
|
255
|
+
|
|
256
|
+
def __each_segment
|
|
257
|
+
segs = @segs
|
|
258
|
+
if segs.nil?
|
|
259
|
+
yield @off, @len
|
|
260
|
+
else
|
|
261
|
+
k = 0
|
|
262
|
+
while k < segs.size
|
|
263
|
+
yield segs[k], segs[k + 1]
|
|
264
|
+
k += 2
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def __nested(i, o, n)
|
|
270
|
+
info = self.class.__fields[i]
|
|
271
|
+
o.is_a?(Integer) ? Scan.nested(info, @buf, [o, n], @depth) : Scan.nested(info, @buf, o, @depth)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def __frozen!
|
|
275
|
+
raise FrozenError.new("can't modify a view of #{descriptor.full_name}", receiver: self)
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
require_relative "view/scan"
|
|
281
|
+
require_relative "view/repeated"
|
|
282
|
+
require_relative "view/map"
|
|
283
|
+
require_relative "view/generator"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
module WellKnown
|
|
5
|
+
# Mixed into google.protobuf.Any
|
|
6
|
+
module Any
|
|
7
|
+
DEFAULT_PREFIX = "type.googleapis.com/"
|
|
8
|
+
|
|
9
|
+
def self.included(base)
|
|
10
|
+
base.extend(ClassMethods)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module ClassMethods
|
|
14
|
+
# @param message [Message]
|
|
15
|
+
# @param type_url_prefix [String]
|
|
16
|
+
# @return [Message] a new Any holding the message
|
|
17
|
+
def pack(message, type_url_prefix = DEFAULT_PREFIX)
|
|
18
|
+
new.pack(message, type_url_prefix)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Stores the message's bytes and a type URL of the prefix and its full
|
|
23
|
+
# name; a `/` is put between them when the prefix lacks one
|
|
24
|
+
# @param message [Message]
|
|
25
|
+
# @param type_url_prefix [String]
|
|
26
|
+
# @return [self]
|
|
27
|
+
def pack(message, type_url_prefix = DEFAULT_PREFIX)
|
|
28
|
+
name = message.class.descriptor.full_name
|
|
29
|
+
self.type_url = type_url_prefix.end_with?("/") ? "#{type_url_prefix}#{name}" : "#{type_url_prefix}/#{name}"
|
|
30
|
+
self.value = message.encode
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @param klass [Class] the message class expected inside
|
|
35
|
+
# @return [Message, nil] the decoded message, or nil when the Any holds another type
|
|
36
|
+
def unpack(klass)
|
|
37
|
+
klass.decode(value) if is(klass)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @return [String] the full name at the end of the type URL
|
|
41
|
+
def type_name
|
|
42
|
+
type_url.split("/")[-1]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @param klass [Class]
|
|
46
|
+
# @return [Boolean] whether the Any holds a message of that class
|
|
47
|
+
def is(klass)
|
|
48
|
+
type_name == klass.descriptor.full_name
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
module WellKnown
|
|
5
|
+
# Mixed into google.protobuf.Duration
|
|
6
|
+
module Duration
|
|
7
|
+
# Duration's range in seconds, about 10,000 years
|
|
8
|
+
MAX_SECONDS = 315_576_000_000
|
|
9
|
+
NANOS = 1_000_000_000
|
|
10
|
+
|
|
11
|
+
def self.included(base)
|
|
12
|
+
base.extend(ClassMethods)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Splits a number of seconds into whole seconds and nanoseconds of the
|
|
16
|
+
# same sign, rounding to the nearest nanosecond
|
|
17
|
+
# @param value [Object]
|
|
18
|
+
# @return [Array(Integer, Integer), nil] nil when the value is not a Numeric
|
|
19
|
+
# @raise [FloatDomainError] for NaN and Infinity
|
|
20
|
+
# @raise [RangeError] beyond {MAX_SECONDS}
|
|
21
|
+
def self.parts(value)
|
|
22
|
+
return nil unless value.is_a?(Numeric)
|
|
23
|
+
r = value.to_r
|
|
24
|
+
seconds = r.truncate
|
|
25
|
+
nanos = ((r - seconds) * NANOS).round
|
|
26
|
+
if nanos.abs >= NANOS
|
|
27
|
+
seconds += nanos <=> 0
|
|
28
|
+
nanos -= (nanos <=> 0) * NANOS
|
|
29
|
+
end
|
|
30
|
+
raise RangeError, "#{value.inspect} is out of Duration's range" if seconds.abs > MAX_SECONDS
|
|
31
|
+
[seconds, nanos]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
module ClassMethods
|
|
35
|
+
# What a Duration field makes of a value that is neither a Duration
|
|
36
|
+
# nor a Hash: a Numeric of seconds (Integer, Float, Rational) becomes
|
|
37
|
+
# a Duration
|
|
38
|
+
# @return [Message, nil]
|
|
39
|
+
# @raise [RangeError] for NaN, Infinity, or beyond Duration's range
|
|
40
|
+
# @api private
|
|
41
|
+
def __coerce(value)
|
|
42
|
+
seconds, nanos = Duration.parts(value)
|
|
43
|
+
new(seconds: seconds, nanos: nanos) if seconds
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [Float] seconds
|
|
48
|
+
def to_f
|
|
49
|
+
seconds + nanos.to_f / 1_000_000_000
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
module WellKnown
|
|
5
|
+
# Mixed into google.protobuf.ListValue: an Array of plain Ruby values
|
|
6
|
+
module ListValue
|
|
7
|
+
include Enumerable
|
|
8
|
+
|
|
9
|
+
def self.included(base)
|
|
10
|
+
base.extend(ClassMethods)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module ClassMethods
|
|
14
|
+
# @param array [Array]
|
|
15
|
+
# @return [Message] a new ListValue
|
|
16
|
+
# @raise [UnexpectedStructType]
|
|
17
|
+
def from_a(array)
|
|
18
|
+
list = new
|
|
19
|
+
array.each { |v| list << v }
|
|
20
|
+
list
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @api private
|
|
24
|
+
def __value_class
|
|
25
|
+
__by_name.fetch("values").message_class
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @return [Integer]
|
|
30
|
+
def length
|
|
31
|
+
values.length
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @return [Object] the element as Ruby, Structs and ListValues left as they are
|
|
35
|
+
def [](index)
|
|
36
|
+
values[index].to_ruby
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def []=(index, value)
|
|
40
|
+
values[index].from_ruby(value)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Appends a Ruby value
|
|
44
|
+
# @return [self]
|
|
45
|
+
def <<(value)
|
|
46
|
+
values << self.class.__value_class.new.from_ruby(value)
|
|
47
|
+
self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Yields each element as Ruby, Structs and ListValues left as they are
|
|
51
|
+
def each
|
|
52
|
+
return enum_for(:each) { length } unless block_given?
|
|
53
|
+
values.each { |v| yield v.to_ruby }
|
|
54
|
+
self
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [Array] with nested Structs and ListValues converted too
|
|
58
|
+
def to_a
|
|
59
|
+
values.map { |v| v.to_ruby(true) }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
module WellKnown
|
|
5
|
+
# Mixed into google.protobuf.Struct: a Hash of String keys to plain Ruby
|
|
6
|
+
# values (nil, Numeric, String, true, false, Hash, Array)
|
|
7
|
+
module Struct
|
|
8
|
+
def self.included(base)
|
|
9
|
+
base.extend(ClassMethods)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module ClassMethods
|
|
13
|
+
# @param hash [Hash{String => Object}]
|
|
14
|
+
# @return [Message] a new Struct
|
|
15
|
+
# @raise [UnexpectedStructType]
|
|
16
|
+
def from_hash(hash)
|
|
17
|
+
struct = new
|
|
18
|
+
hash.each { |key, value| struct[key] = value }
|
|
19
|
+
struct
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @api private
|
|
23
|
+
def __value_class
|
|
24
|
+
__by_name.fetch("fields").value.message_class
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @param key [String]
|
|
29
|
+
# @return [Object, nil] the value as Ruby, Structs and ListValues
|
|
30
|
+
# left as they are; nil for a missing key
|
|
31
|
+
def [](key)
|
|
32
|
+
fields[key]&.to_ruby
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @param key [String]
|
|
36
|
+
# @param value [Object]
|
|
37
|
+
# @raise [UnexpectedStructType] for a key that is not a String or a value Struct cannot hold
|
|
38
|
+
def []=(key, value)
|
|
39
|
+
raise UnexpectedStructType, "Struct keys must be strings." unless key.is_a?(String)
|
|
40
|
+
(fields[key] ||= self.class.__value_class.new).from_ruby(value)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @return [Hash{String => Object}] with nested Structs and ListValues
|
|
44
|
+
# converted too
|
|
45
|
+
def to_h
|
|
46
|
+
fields.transform_values { |v| v.to_ruby(true) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @param key [String]
|
|
50
|
+
def has_key?(key)
|
|
51
|
+
fields.key?(key)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|