@bufbuild/protobuf 2.12.1 → 2.13.0
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.
- package/README.md +2 -2
- package/dist/commonjs/codegenv1/symbols.d.ts +5 -0
- package/dist/commonjs/codegenv2/enum.d.ts +15 -4
- package/dist/commonjs/codegenv2/enum.js +18 -0
- package/dist/commonjs/codegenv2/symbols.d.ts +10 -0
- package/dist/commonjs/codegenv2/symbols.js +2 -0
- package/dist/commonjs/create.js +3 -3
- package/dist/commonjs/descriptors.d.ts +2 -2
- package/dist/commonjs/index.d.ts +1 -0
- package/dist/commonjs/index.js +1 -0
- package/dist/commonjs/reflect/reflect.js +3 -2
- package/dist/commonjs/reflect/unsafe.js +1 -1
- package/dist/commonjs/registry.js +19 -19
- package/dist/commonjs/to-binary.js +1 -1
- package/dist/commonjs/to-json.js +2 -2
- package/dist/commonjs/txtpb/from-text.d.ts +35 -0
- package/dist/commonjs/txtpb/from-text.js +684 -0
- package/dist/commonjs/txtpb/index.d.ts +4 -0
- package/dist/commonjs/txtpb/index.js +21 -0
- package/dist/commonjs/txtpb/is-group-like.d.ts +19 -0
- package/dist/commonjs/txtpb/is-group-like.js +47 -0
- package/dist/commonjs/txtpb/reader.d.ts +91 -0
- package/dist/commonjs/txtpb/reader.js +481 -0
- package/dist/commonjs/txtpb/to-text.d.ts +34 -0
- package/dist/commonjs/txtpb/to-text.js +358 -0
- package/dist/commonjs/txtpb/writer.d.ts +76 -0
- package/dist/commonjs/txtpb/writer.js +220 -0
- package/dist/commonjs/types.d.ts +7 -0
- package/dist/commonjs/unknown-enum.d.ts +10 -0
- package/dist/commonjs/unknown-enum.js +26 -0
- package/dist/commonjs/wire/size-delimited.d.ts +19 -1
- package/dist/commonjs/wire/size-delimited.js +54 -16
- package/dist/esm/codegenv1/symbols.d.ts +5 -0
- package/dist/esm/codegenv2/enum.d.ts +15 -4
- package/dist/esm/codegenv2/enum.js +17 -0
- package/dist/esm/codegenv2/symbols.d.ts +10 -0
- package/dist/esm/codegenv2/symbols.js +2 -0
- package/dist/esm/create.js +3 -3
- package/dist/esm/descriptors.d.ts +2 -2
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/reflect/reflect.js +3 -2
- package/dist/esm/reflect/unsafe.js +1 -1
- package/dist/esm/registry.js +19 -19
- package/dist/esm/to-binary.js +1 -1
- package/dist/esm/to-json.js +2 -2
- package/dist/esm/txtpb/from-text.d.ts +35 -0
- package/dist/esm/txtpb/from-text.js +680 -0
- package/dist/esm/txtpb/index.d.ts +4 -0
- package/dist/esm/txtpb/index.js +15 -0
- package/dist/esm/txtpb/is-group-like.d.ts +19 -0
- package/dist/esm/txtpb/is-group-like.js +44 -0
- package/dist/esm/txtpb/reader.d.ts +91 -0
- package/dist/esm/txtpb/reader.js +477 -0
- package/dist/esm/txtpb/to-text.d.ts +34 -0
- package/dist/esm/txtpb/to-text.js +355 -0
- package/dist/esm/txtpb/writer.d.ts +76 -0
- package/dist/esm/txtpb/writer.js +214 -0
- package/dist/esm/types.d.ts +7 -0
- package/dist/esm/unknown-enum.d.ts +10 -0
- package/dist/esm/unknown-enum.js +23 -0
- package/dist/esm/wire/size-delimited.d.ts +19 -1
- package/dist/esm/wire/size-delimited.js +54 -16
- package/package.json +16 -3
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
// Copyright 2021-2026 Buf Technologies, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
import { ScalarType, } from "../descriptors.js";
|
|
15
|
+
import { protoInt64 } from "../proto-int64.js";
|
|
16
|
+
import { reflect } from "../reflect/reflect.js";
|
|
17
|
+
import { isFieldError } from "../reflect/error.js";
|
|
18
|
+
import { scalarZeroValue } from "../reflect/scalar.js";
|
|
19
|
+
import { toBinary } from "../to-binary.js";
|
|
20
|
+
import { createExtensionContainer, getExtension, hasExtension, setExtension, } from "../extensions.js";
|
|
21
|
+
import { getTextEncoding } from "../wire/text-encoding.js";
|
|
22
|
+
import { Reader } from "./reader.js";
|
|
23
|
+
import { isGroupLike } from "./is-group-like.js";
|
|
24
|
+
function makeReadContext(reader, options) {
|
|
25
|
+
return Object.assign(Object.assign({ recursionLimit: 100 }, options), { reader, depth: 0 });
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Parse a message from the protobuf text format.
|
|
29
|
+
*
|
|
30
|
+
* Requires BigInt: throws immediately if the environment does not support it.
|
|
31
|
+
*/
|
|
32
|
+
export function fromText(schema, text, options) {
|
|
33
|
+
const msg = reflect(schema);
|
|
34
|
+
parseText(msg, text, options);
|
|
35
|
+
return msg.message;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Parse a message from the protobuf text format, merging into the target.
|
|
39
|
+
*
|
|
40
|
+
* Repeated fields are appended, singular fields are overwritten (last wins),
|
|
41
|
+
* message fields are merged, and map entries are added (overwriting an existing
|
|
42
|
+
* key).
|
|
43
|
+
*
|
|
44
|
+
* Requires BigInt: throws immediately if the environment does not support it.
|
|
45
|
+
*/
|
|
46
|
+
export function mergeFromText(schema, target, text, options) {
|
|
47
|
+
parseText(reflect(schema, target), text, options);
|
|
48
|
+
return target;
|
|
49
|
+
}
|
|
50
|
+
function parseText(msg, text, options) {
|
|
51
|
+
if (!protoInt64.supported) {
|
|
52
|
+
throw new Error("the protobuf text format requires BigInt, which is unavailable in this environment");
|
|
53
|
+
}
|
|
54
|
+
const ctx = makeReadContext(new Reader(text), options);
|
|
55
|
+
try {
|
|
56
|
+
readMessageBody(msg, ctx, "eof");
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
if (isFieldError(e)) {
|
|
60
|
+
throw new Error(`cannot decode ${e.field()} from text format: ${e.message}`,
|
|
61
|
+
// @ts-expect-error we use the ES2022 error CTOR option "cause" for better stack traces
|
|
62
|
+
{ cause: e });
|
|
63
|
+
}
|
|
64
|
+
throw e;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Read a message body (its fields until `close`), guarding nesting depth. The
|
|
69
|
+
* top-level message and every nested "{...}"/"<...>" block go through here, so
|
|
70
|
+
* the recursion limit counts the root too, matching fromJson and fromBinary.
|
|
71
|
+
*/
|
|
72
|
+
function readMessageBody(msg, ctx, close) {
|
|
73
|
+
if (++ctx.depth > ctx.recursionLimit) {
|
|
74
|
+
throw new Error(`recursion limit of ${ctx.recursionLimit} reached decoding ${msg.desc}`);
|
|
75
|
+
}
|
|
76
|
+
readFields(msg, ctx, close);
|
|
77
|
+
ctx.depth--;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Read the fields of a message until `close` (the matching close token, or
|
|
81
|
+
* "eof" for the top-level message).
|
|
82
|
+
*/
|
|
83
|
+
function readFields(msg, ctx, close) {
|
|
84
|
+
const seen = { fields: new Set(), oneofs: new Set() };
|
|
85
|
+
for (;;) {
|
|
86
|
+
const tok = ctx.reader.peek();
|
|
87
|
+
if (tok.type === "eof") {
|
|
88
|
+
if (close !== "eof") {
|
|
89
|
+
throw new Error(`unexpected end of input, expected "${close}"`);
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (close !== "eof" && tok.type === close) {
|
|
94
|
+
ctx.reader.next();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
readField(msg, ctx, seen);
|
|
98
|
+
// A single optional "," or ";" may follow a field. Because the next
|
|
99
|
+
// iteration treats a separator as a field name and rejects it, a doubled
|
|
100
|
+
// separator is an error, while a single trailing one is allowed.
|
|
101
|
+
consumeSeparator(ctx);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function readField(msg, ctx, seen) {
|
|
105
|
+
var _a;
|
|
106
|
+
const nameTok = ctx.reader.next();
|
|
107
|
+
if (nameTok.type === "identifier") {
|
|
108
|
+
const field = fieldByTextName(msg.desc, nameTok.value);
|
|
109
|
+
if (field !== undefined) {
|
|
110
|
+
checkSeen(field, seen);
|
|
111
|
+
readFieldValue(msg, field, ctx);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
// Reserved field names are silently skipped; any other unknown name is an
|
|
115
|
+
// error. This matches protobuf-go.
|
|
116
|
+
if (msg.desc.proto.reservedName.includes(nameTok.value)) {
|
|
117
|
+
skipFieldValue(ctx);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
throw new Error(`unknown field "${nameTok.value}" for ${msg.desc}`);
|
|
121
|
+
}
|
|
122
|
+
if (nameTok.type === "[") {
|
|
123
|
+
const name = ctx.reader.readTypeName();
|
|
124
|
+
// Inside google.protobuf.Any, a bracketed name is always a type URL; in any
|
|
125
|
+
// other message it is an extension name.
|
|
126
|
+
if (msg.desc.typeName === "google.protobuf.Any") {
|
|
127
|
+
readExpandedAny(msg, ctx, name, seen);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const ext = (_a = ctx.registry) === null || _a === void 0 ? void 0 : _a.getExtension(name);
|
|
131
|
+
if (ext !== undefined && ext.extendee.typeName === msg.desc.typeName) {
|
|
132
|
+
checkSeen(ext, seen);
|
|
133
|
+
readExtensionField(msg, ext, ctx);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
throw new Error(`unknown extension "[${name}]" for ${msg.desc}`);
|
|
137
|
+
}
|
|
138
|
+
if (nameTok.type === "int") {
|
|
139
|
+
// Like protobuf-go, a field cannot be addressed by number, so the numbered
|
|
140
|
+
// output of printUnknownFields cannot be read back.
|
|
141
|
+
throw new Error(`cannot specify field by number: ${nameTok.text}`);
|
|
142
|
+
}
|
|
143
|
+
throw new Error(`expected a field name, got ${describe(nameTok)}`);
|
|
144
|
+
}
|
|
145
|
+
// Rejects a repeated occurrence of a singular field, or a second member of the
|
|
146
|
+
// same oneof. Repeated and map fields may appear any number of times.
|
|
147
|
+
function checkSeen(field, seen) {
|
|
148
|
+
if (field.fieldKind === "list" || field.fieldKind === "map") {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (field.oneof !== undefined) {
|
|
152
|
+
if (seen.oneofs.has(field.oneof)) {
|
|
153
|
+
throw new Error(`oneof "${field.oneof.name}" is already set`);
|
|
154
|
+
}
|
|
155
|
+
seen.oneofs.add(field.oneof);
|
|
156
|
+
}
|
|
157
|
+
if (seen.fields.has(field.number)) {
|
|
158
|
+
const what = field.kind === "extension"
|
|
159
|
+
? `extension "[${field.typeName}]"`
|
|
160
|
+
: `field "${field.name}"`;
|
|
161
|
+
throw new Error(`non-repeated ${what} is repeated`);
|
|
162
|
+
}
|
|
163
|
+
seen.fields.add(field.number);
|
|
164
|
+
}
|
|
165
|
+
function readFieldValue(target, field, ctx) {
|
|
166
|
+
// The ":" separator is optional before a message, group, or map value, but
|
|
167
|
+
// required for scalars, enums, and lists of them.
|
|
168
|
+
const hasColon = consumeColon(ctx);
|
|
169
|
+
if (!colonOptional(field) && !hasColon) {
|
|
170
|
+
throw new Error(`expected ":" before value of field "${field.name}"`);
|
|
171
|
+
}
|
|
172
|
+
switch (field.fieldKind) {
|
|
173
|
+
case "scalar":
|
|
174
|
+
target.set(field, readScalarValue(field, field.scalar, ctx));
|
|
175
|
+
break;
|
|
176
|
+
case "enum":
|
|
177
|
+
target.set(field, readEnumValue(field.enum, ctx));
|
|
178
|
+
break;
|
|
179
|
+
case "message": {
|
|
180
|
+
const sub = target.isSet(field)
|
|
181
|
+
? target.get(field)
|
|
182
|
+
: reflect(field.message);
|
|
183
|
+
readMessageValue(sub, ctx);
|
|
184
|
+
target.set(field, sub);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case "list":
|
|
188
|
+
readListField(field, target.get(field), ctx);
|
|
189
|
+
break;
|
|
190
|
+
case "map":
|
|
191
|
+
readMapField(field, target.get(field), ctx);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function readExtensionField(msg, ext, ctx) {
|
|
196
|
+
// Extensions live in the unknown-field set. We read the new value into a
|
|
197
|
+
// container seeded with any existing value, so a repeated extension appends.
|
|
198
|
+
const existing = hasExtension(msg.message, ext)
|
|
199
|
+
? getExtension(msg.message, ext)
|
|
200
|
+
: undefined;
|
|
201
|
+
const [container, field, get] = createExtensionContainer(ext, existing);
|
|
202
|
+
readFieldValue(container, field, ctx);
|
|
203
|
+
setExtension(msg.message, ext, get());
|
|
204
|
+
}
|
|
205
|
+
function colonOptional(field) {
|
|
206
|
+
return (field.fieldKind === "message" ||
|
|
207
|
+
field.fieldKind === "map" ||
|
|
208
|
+
(field.fieldKind === "list" && field.listKind === "message"));
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Read a "{ ... }" or "< ... >" block into the given message.
|
|
212
|
+
*/
|
|
213
|
+
function readMessageValue(msg, ctx) {
|
|
214
|
+
readMessageBody(msg, ctx, readMessageOpen(ctx));
|
|
215
|
+
}
|
|
216
|
+
function readMessageOpen(ctx) {
|
|
217
|
+
const open = ctx.reader.next();
|
|
218
|
+
if (open.type === "{") {
|
|
219
|
+
return "}";
|
|
220
|
+
}
|
|
221
|
+
if (open.type === "<") {
|
|
222
|
+
return ">";
|
|
223
|
+
}
|
|
224
|
+
throw new Error(`expected "{" or "<", got ${describe(open)}`);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Read a repeated value: either a single element, or a bracketed list
|
|
228
|
+
* "[ e, e, ... ]". This is the one place the list grammar lives, so list
|
|
229
|
+
* fields, map fields, and the reserved-skip path cannot drift in how they
|
|
230
|
+
* accept (and reject) separators.
|
|
231
|
+
*/
|
|
232
|
+
function readBracketedList(ctx, readElement) {
|
|
233
|
+
if (ctx.reader.peek().type !== "[") {
|
|
234
|
+
readElement();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
ctx.reader.next(); // "["
|
|
238
|
+
if (ctx.reader.peek().type === "]") {
|
|
239
|
+
ctx.reader.next();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
for (;;) {
|
|
243
|
+
readElement();
|
|
244
|
+
const sep = ctx.reader.next();
|
|
245
|
+
if (sep.type === "]") {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (sep.type !== ",") {
|
|
249
|
+
throw new Error(`expected "," or "]" in list, got ${describe(sep)}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function readListField(field, list, ctx) {
|
|
254
|
+
readBracketedList(ctx, () => list.add(readListItem(field, ctx)));
|
|
255
|
+
}
|
|
256
|
+
function readListItem(field, ctx) {
|
|
257
|
+
switch (field.listKind) {
|
|
258
|
+
case "scalar":
|
|
259
|
+
return readScalarValue(field, field.scalar, ctx);
|
|
260
|
+
case "enum":
|
|
261
|
+
return readEnumValue(field.enum, ctx);
|
|
262
|
+
case "message": {
|
|
263
|
+
const sub = reflect(field.message);
|
|
264
|
+
readMessageValue(sub, ctx);
|
|
265
|
+
return sub;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function readMapField(field, map, ctx) {
|
|
270
|
+
readBracketedList(ctx, () => readMapEntry(field, map, ctx));
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Read a map entry: a "{ key: ... value: ... }" block. A missing key or value
|
|
274
|
+
* defaults to the zero value, like protobuf-go. A duplicate "key" or "value"
|
|
275
|
+
* within one entry is an error; duplicate keys across separate entries are
|
|
276
|
+
* legal, with the last entry winning (handled by the caller's map.set).
|
|
277
|
+
*/
|
|
278
|
+
function readMapEntry(field, map, ctx) {
|
|
279
|
+
if (++ctx.depth > ctx.recursionLimit) {
|
|
280
|
+
throw new Error(`recursion limit of ${ctx.recursionLimit} reached decoding a map entry`);
|
|
281
|
+
}
|
|
282
|
+
const close = readMessageOpen(ctx);
|
|
283
|
+
let key = scalarZeroValue(field.mapKey, false);
|
|
284
|
+
let value = mapValueZero(field);
|
|
285
|
+
let keySeen = false;
|
|
286
|
+
let valueSeen = false;
|
|
287
|
+
for (;;) {
|
|
288
|
+
const tok = ctx.reader.peek();
|
|
289
|
+
if (tok.type === close) {
|
|
290
|
+
ctx.reader.next();
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
if (tok.type === "eof") {
|
|
294
|
+
throw new Error(`unexpected end of input, expected "${close}"`);
|
|
295
|
+
}
|
|
296
|
+
const nameTok = ctx.reader.next();
|
|
297
|
+
if (nameTok.type !== "identifier") {
|
|
298
|
+
throw new Error(`expected "key" or "value", got ${describe(nameTok)}`);
|
|
299
|
+
}
|
|
300
|
+
if (nameTok.value === "key") {
|
|
301
|
+
if (keySeen) {
|
|
302
|
+
throw new Error('map entry "key" is already set');
|
|
303
|
+
}
|
|
304
|
+
keySeen = true;
|
|
305
|
+
requireColon(ctx);
|
|
306
|
+
key = readScalarValue(field, field.mapKey, ctx);
|
|
307
|
+
}
|
|
308
|
+
else if (nameTok.value === "value") {
|
|
309
|
+
if (valueSeen) {
|
|
310
|
+
throw new Error('map entry "value" is already set');
|
|
311
|
+
}
|
|
312
|
+
valueSeen = true;
|
|
313
|
+
value = readMapValue(field, ctx);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
throw new Error(`unknown field "${nameTok.value}" in map entry`);
|
|
317
|
+
}
|
|
318
|
+
consumeSeparator(ctx);
|
|
319
|
+
}
|
|
320
|
+
ctx.depth--;
|
|
321
|
+
map.set(key, value);
|
|
322
|
+
}
|
|
323
|
+
function readMapValue(field, ctx) {
|
|
324
|
+
switch (field.mapKind) {
|
|
325
|
+
case "scalar":
|
|
326
|
+
requireColon(ctx);
|
|
327
|
+
return readScalarValue(field, field.scalar, ctx);
|
|
328
|
+
case "enum":
|
|
329
|
+
requireColon(ctx);
|
|
330
|
+
return readEnumValue(field.enum, ctx);
|
|
331
|
+
case "message": {
|
|
332
|
+
consumeColon(ctx);
|
|
333
|
+
const sub = reflect(field.message);
|
|
334
|
+
readMessageValue(sub, ctx);
|
|
335
|
+
return sub;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function mapValueZero(field) {
|
|
340
|
+
switch (field.mapKind) {
|
|
341
|
+
case "scalar":
|
|
342
|
+
return scalarZeroValue(field.scalar, false);
|
|
343
|
+
case "enum":
|
|
344
|
+
return field.enum.values[0].number;
|
|
345
|
+
case "message":
|
|
346
|
+
return reflect(field.message);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Read `google.protobuf.Any` in its expanded form `[type.url]: { ... }`.
|
|
351
|
+
*
|
|
352
|
+
* The expanded form is mutually exclusive with the raw `type_url` (field 1) and
|
|
353
|
+
* `value` (field 2) fields, and may appear only once. We enforce that through
|
|
354
|
+
* the same seen-set the duplicate-field check uses: the expansion is rejected
|
|
355
|
+
* if either field is already set, and it marks both as set so a following
|
|
356
|
+
* `type_url` or `value` is rejected too.
|
|
357
|
+
*/
|
|
358
|
+
function readExpandedAny(msg, ctx, typeUrl, seen) {
|
|
359
|
+
var _a;
|
|
360
|
+
if (seen.fields.has(1) || seen.fields.has(2)) {
|
|
361
|
+
throw new Error("google.protobuf.Any cannot mix the expanded form with type_url/value");
|
|
362
|
+
}
|
|
363
|
+
const slash = typeUrl.lastIndexOf("/");
|
|
364
|
+
const typeName = slash >= 0 ? typeUrl.substring(slash + 1) : typeUrl;
|
|
365
|
+
const desc = (_a = ctx.registry) === null || _a === void 0 ? void 0 : _a.getMessage(typeName);
|
|
366
|
+
if (desc === undefined) {
|
|
367
|
+
throw new Error(`unable to resolve "${typeUrl}" for google.protobuf.Any`);
|
|
368
|
+
}
|
|
369
|
+
consumeColon(ctx);
|
|
370
|
+
const unpacked = reflect(desc);
|
|
371
|
+
readMessageValue(unpacked, ctx);
|
|
372
|
+
const any = msg.message;
|
|
373
|
+
// Preserve the exact type URL, including any custom domain prefix.
|
|
374
|
+
any.typeUrl = typeUrl;
|
|
375
|
+
any.value = toBinary(desc, unpacked.message);
|
|
376
|
+
seen.fields.add(1);
|
|
377
|
+
seen.fields.add(2);
|
|
378
|
+
}
|
|
379
|
+
// Consume an optional leading "-" sign and report whether one was present.
|
|
380
|
+
// This sees a sign token only before a number (the scanner glues a sign onto an
|
|
381
|
+
// identifier as in "-inf"), and whitespace and comments between the sign and the
|
|
382
|
+
// number are insignificant, so "- 42" means -42, matching protobuf-go.
|
|
383
|
+
function consumeSign(ctx) {
|
|
384
|
+
if (ctx.reader.peek().type === "-") {
|
|
385
|
+
ctx.reader.next();
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
function readScalarValue(field, type, ctx) {
|
|
391
|
+
const negative = consumeSign(ctx);
|
|
392
|
+
const tok = ctx.reader.next();
|
|
393
|
+
switch (type) {
|
|
394
|
+
case ScalarType.STRING:
|
|
395
|
+
case ScalarType.BYTES: {
|
|
396
|
+
if (negative) {
|
|
397
|
+
throw new Error("a string value cannot have a sign");
|
|
398
|
+
}
|
|
399
|
+
if (tok.type !== "string") {
|
|
400
|
+
throw new Error(`expected a string, got ${describe(tok)}`);
|
|
401
|
+
}
|
|
402
|
+
const bytes = concatStrings(tok, ctx);
|
|
403
|
+
if (type === ScalarType.BYTES) {
|
|
404
|
+
return bytes;
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
return getTextEncoding().decodeUtf8(bytes, field.utf8Validation);
|
|
408
|
+
}
|
|
409
|
+
catch (_a) {
|
|
410
|
+
throw new Error("invalid UTF-8 in string");
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
case ScalarType.BOOL:
|
|
414
|
+
return readBoolValue(tok, negative);
|
|
415
|
+
case ScalarType.FLOAT:
|
|
416
|
+
// Round to 32-bit precision: an out-of-range value becomes ±inf, which is
|
|
417
|
+
// what the text format requires for float overflow.
|
|
418
|
+
return Math.fround(readFloatValue(tok, negative));
|
|
419
|
+
case ScalarType.DOUBLE:
|
|
420
|
+
return readFloatValue(tok, negative);
|
|
421
|
+
case ScalarType.UINT32:
|
|
422
|
+
case ScalarType.FIXED32:
|
|
423
|
+
return Number(readUnsignedInt(tok, negative));
|
|
424
|
+
case ScalarType.UINT64:
|
|
425
|
+
case ScalarType.FIXED64:
|
|
426
|
+
return readUnsignedInt(tok, negative);
|
|
427
|
+
case ScalarType.INT64:
|
|
428
|
+
case ScalarType.SINT64:
|
|
429
|
+
case ScalarType.SFIXED64:
|
|
430
|
+
return readSignedInt(tok, negative);
|
|
431
|
+
default:
|
|
432
|
+
// INT32, SINT32, SFIXED32: the reflect layer range-checks the number.
|
|
433
|
+
return Number(readSignedInt(tok, negative));
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
function readEnumValue(descEnum, ctx) {
|
|
437
|
+
const negative = consumeSign(ctx);
|
|
438
|
+
const tok = ctx.reader.next();
|
|
439
|
+
if (tok.type === "identifier") {
|
|
440
|
+
if (negative) {
|
|
441
|
+
throw new Error(`invalid enum value "-${tok.value}" for ${descEnum}`);
|
|
442
|
+
}
|
|
443
|
+
const value = descEnum.values.find((v) => v.name === tok.value);
|
|
444
|
+
if (value === undefined) {
|
|
445
|
+
throw new Error(`unknown enum value "${tok.value}" for ${descEnum}`);
|
|
446
|
+
}
|
|
447
|
+
return value.number;
|
|
448
|
+
}
|
|
449
|
+
if (tok.type === "int") {
|
|
450
|
+
// The reflect layer validates the number: any int32 for open enums, a known
|
|
451
|
+
// value for closed (proto2) enums.
|
|
452
|
+
return Number(readSignedInt(tok, negative));
|
|
453
|
+
}
|
|
454
|
+
throw new Error(`expected an enum value for ${descEnum}, got ${describe(tok)}`);
|
|
455
|
+
}
|
|
456
|
+
function readBoolValue(tok, negative) {
|
|
457
|
+
if (!negative) {
|
|
458
|
+
if (tok.type === "identifier") {
|
|
459
|
+
switch (tok.value) {
|
|
460
|
+
case "true":
|
|
461
|
+
case "True":
|
|
462
|
+
case "t":
|
|
463
|
+
return true;
|
|
464
|
+
case "false":
|
|
465
|
+
case "False":
|
|
466
|
+
case "f":
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (tok.type === "int") {
|
|
471
|
+
const value = intTokenToBigInt(tok);
|
|
472
|
+
if (value === BigInt(0)) {
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
if (value === BigInt(1)) {
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
throw new Error(`expected a bool, got ${negative ? "-" : ""}${describe(tok)}`);
|
|
481
|
+
}
|
|
482
|
+
function readFloatValue(tok, negative) {
|
|
483
|
+
if (tok.type === "identifier") {
|
|
484
|
+
// A separate "-" token (negative) before a float literal is invalid; the
|
|
485
|
+
// only signed literals are "-inf"/"-infinity", which the scanner glues into
|
|
486
|
+
// one identifier token. "-nan" is not a literal, so it falls through to the
|
|
487
|
+
// error below. This matches protobuf-go's identifier-path sign handling.
|
|
488
|
+
if (negative) {
|
|
489
|
+
throw new Error(`invalid float value "-${tok.value}"`);
|
|
490
|
+
}
|
|
491
|
+
switch (tok.value.toLowerCase()) {
|
|
492
|
+
case "inf":
|
|
493
|
+
case "infinity":
|
|
494
|
+
return Number.POSITIVE_INFINITY;
|
|
495
|
+
case "-inf":
|
|
496
|
+
case "-infinity":
|
|
497
|
+
return Number.NEGATIVE_INFINITY;
|
|
498
|
+
case "nan":
|
|
499
|
+
return Number.NaN;
|
|
500
|
+
}
|
|
501
|
+
throw new Error(`invalid float value "${tok.value}"`);
|
|
502
|
+
}
|
|
503
|
+
if (tok.type === "float") {
|
|
504
|
+
const n = Number(tok.text);
|
|
505
|
+
return negative ? -n : n;
|
|
506
|
+
}
|
|
507
|
+
if (tok.type === "int") {
|
|
508
|
+
// Octal and hexadecimal literals are not valid for float and double fields.
|
|
509
|
+
if (tok.base !== 10) {
|
|
510
|
+
throw new Error("octal and hexadecimal are not valid for a float field");
|
|
511
|
+
}
|
|
512
|
+
const n = Number(tok.text);
|
|
513
|
+
return negative ? -n : n;
|
|
514
|
+
}
|
|
515
|
+
throw new Error(`expected a float, got ${describe(tok)}`);
|
|
516
|
+
}
|
|
517
|
+
function readSignedInt(tok, negative) {
|
|
518
|
+
if (tok.type !== "int") {
|
|
519
|
+
throw new Error(`expected an integer, got ${describe(tok)}`);
|
|
520
|
+
}
|
|
521
|
+
const value = intTokenToBigInt(tok);
|
|
522
|
+
return negative ? -value : value;
|
|
523
|
+
}
|
|
524
|
+
function readUnsignedInt(tok, negative) {
|
|
525
|
+
// Reject any sign for an unsigned field, including "-0": the reflect layer
|
|
526
|
+
// would silently accept it as 0.
|
|
527
|
+
if (negative) {
|
|
528
|
+
throw new Error("an unsigned field does not accept a negative value");
|
|
529
|
+
}
|
|
530
|
+
return readSignedInt(tok, false);
|
|
531
|
+
}
|
|
532
|
+
function intTokenToBigInt(tok) {
|
|
533
|
+
// Octal text keeps its leading "0" (e.g. "0755"), which BigInt would read as
|
|
534
|
+
// decimal, so it needs the "0o" prefix. Hex ("0x...") and decimal text are
|
|
535
|
+
// accepted by BigInt as-is.
|
|
536
|
+
return tok.base === 8
|
|
537
|
+
? BigInt("0o" + tok.text.substring(1))
|
|
538
|
+
: BigInt(tok.text);
|
|
539
|
+
}
|
|
540
|
+
// Concatenate adjacent string literals into a single byte string.
|
|
541
|
+
function concatStrings(first, ctx) {
|
|
542
|
+
if (ctx.reader.peek().type !== "string") {
|
|
543
|
+
return first.value;
|
|
544
|
+
}
|
|
545
|
+
const parts = [first.value];
|
|
546
|
+
let length = first.value.length;
|
|
547
|
+
while (ctx.reader.peek().type === "string") {
|
|
548
|
+
const tok = ctx.reader.next();
|
|
549
|
+
parts.push(tok.value);
|
|
550
|
+
length += tok.value.length;
|
|
551
|
+
}
|
|
552
|
+
const bytes = new Uint8Array(length);
|
|
553
|
+
let offset = 0;
|
|
554
|
+
for (const part of parts) {
|
|
555
|
+
bytes.set(part, offset);
|
|
556
|
+
offset += part.length;
|
|
557
|
+
}
|
|
558
|
+
return bytes;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Skip the value of a reserved field. Like every other nested read, the message
|
|
562
|
+
* case is guarded by the recursion limit.
|
|
563
|
+
*/
|
|
564
|
+
function skipFieldValue(ctx) {
|
|
565
|
+
consumeColon(ctx);
|
|
566
|
+
skipValue(ctx);
|
|
567
|
+
}
|
|
568
|
+
function skipValue(ctx) {
|
|
569
|
+
readBracketedList(ctx, () => skipSingleValue(ctx));
|
|
570
|
+
}
|
|
571
|
+
function skipSingleValue(ctx) {
|
|
572
|
+
const tok = ctx.reader.peek();
|
|
573
|
+
if (tok.type === "{" || tok.type === "<") {
|
|
574
|
+
skipMessageBlock(ctx);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
// A leading sign is consumed leniently here: skipping a reserved value should
|
|
578
|
+
// tolerate "- 5".
|
|
579
|
+
consumeSign(ctx);
|
|
580
|
+
const value = ctx.reader.next();
|
|
581
|
+
if (value.type === "string") {
|
|
582
|
+
while (ctx.reader.peek().type === "string") {
|
|
583
|
+
ctx.reader.next();
|
|
584
|
+
}
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
if (value.type !== "identifier" &&
|
|
588
|
+
value.type !== "int" &&
|
|
589
|
+
value.type !== "float") {
|
|
590
|
+
throw new Error(`expected a value, got ${describe(value)}`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
function skipMessageBlock(ctx) {
|
|
594
|
+
if (++ctx.depth > ctx.recursionLimit) {
|
|
595
|
+
throw new Error(`recursion limit of ${ctx.recursionLimit} reached skipping a reserved field`);
|
|
596
|
+
}
|
|
597
|
+
const close = readMessageOpen(ctx);
|
|
598
|
+
for (;;) {
|
|
599
|
+
const tok = ctx.reader.peek();
|
|
600
|
+
if (tok.type === close) {
|
|
601
|
+
ctx.reader.next();
|
|
602
|
+
ctx.depth--;
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
if (tok.type === "eof") {
|
|
606
|
+
throw new Error(`unexpected end of input, expected "${close}"`);
|
|
607
|
+
}
|
|
608
|
+
const nameTok = ctx.reader.next();
|
|
609
|
+
if (nameTok.type === "[") {
|
|
610
|
+
ctx.reader.readTypeName();
|
|
611
|
+
}
|
|
612
|
+
else if (nameTok.type !== "identifier" && nameTok.type !== "int") {
|
|
613
|
+
throw new Error(`expected a field name, got ${describe(nameTok)}`);
|
|
614
|
+
}
|
|
615
|
+
skipFieldValue(ctx);
|
|
616
|
+
consumeSeparator(ctx);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
// Consume an optional "," or ";" that separates fields or list/map elements.
|
|
620
|
+
function consumeSeparator(ctx) {
|
|
621
|
+
const sep = ctx.reader.peek();
|
|
622
|
+
if (sep.type === "," || sep.type === ";") {
|
|
623
|
+
ctx.reader.next();
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
function consumeColon(ctx) {
|
|
627
|
+
if (ctx.reader.peek().type === ":") {
|
|
628
|
+
ctx.reader.next();
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
function requireColon(ctx) {
|
|
634
|
+
if (!consumeColon(ctx)) {
|
|
635
|
+
throw new Error(`expected ":", got ${describe(ctx.reader.peek())}`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
const textFieldCache = new WeakMap();
|
|
639
|
+
/**
|
|
640
|
+
* Resolve a field by its text format name, mirroring protobuf-go's ByTextName:
|
|
641
|
+
* group-like fields are addressed by their message type name, with the
|
|
642
|
+
* lowercase form as an alias; JSON names are not in this table.
|
|
643
|
+
*/
|
|
644
|
+
function fieldByTextName(desc, name) {
|
|
645
|
+
let byText = textFieldCache.get(desc);
|
|
646
|
+
if (byText === undefined) {
|
|
647
|
+
byText = new Map();
|
|
648
|
+
for (const field of desc.fields) {
|
|
649
|
+
if (isGroupLike(field)) {
|
|
650
|
+
setOnce(byText, field.message.name, field);
|
|
651
|
+
setOnce(byText, field.message.name.toLowerCase(), field);
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
setOnce(byText, field.name, field);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
textFieldCache.set(desc, byText);
|
|
658
|
+
}
|
|
659
|
+
return byText.get(name);
|
|
660
|
+
}
|
|
661
|
+
function setOnce(map, key, field) {
|
|
662
|
+
if (!map.has(key)) {
|
|
663
|
+
map.set(key, field);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
function describe(tok) {
|
|
667
|
+
switch (tok.type) {
|
|
668
|
+
case "identifier":
|
|
669
|
+
return `"${tok.value}"`;
|
|
670
|
+
case "int":
|
|
671
|
+
case "float":
|
|
672
|
+
return `"${tok.text}"`;
|
|
673
|
+
case "string":
|
|
674
|
+
return "a string";
|
|
675
|
+
case "eof":
|
|
676
|
+
return "end of input";
|
|
677
|
+
default:
|
|
678
|
+
return `"${tok.type}"`;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Copyright 2021-2026 Buf Technologies, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
export { toText } from "./to-text.js";
|
|
15
|
+
export { fromText, mergeFromText } from "./from-text.js";
|