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,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