Erster Commit

This commit is contained in:
2026-08-22 08:40:29 +02:00
commit 875477d425
1961 changed files with 930336 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
/**
* Assert that condition is truthy or throw error (with message)
*/
export declare function assert(condition: unknown, msg?: string): asserts condition;
/**
* Assert a valid signed protobuf 32-bit integer.
*/
export declare function assertInt32(arg: unknown): asserts arg is number;
/**
* Assert a valid unsigned protobuf 32-bit integer.
*/
export declare function assertUInt32(arg: unknown): asserts arg is number;
/**
* Assert a valid protobuf float value.
*/
export declare function assertFloat32(arg: unknown): asserts arg is number;
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* Assert that condition is truthy or throw error (with message)
*/
export function assert(condition, msg) {
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions -- we want the implicit conversion to boolean
if (!condition) {
throw new Error(msg);
}
}
const FLOAT32_MAX = 3.4028234663852886e38, FLOAT32_MIN = -3.4028234663852886e38, UINT32_MAX = 0xffffffff, INT32_MAX = 0x7fffffff, INT32_MIN = -0x80000000;
/**
* Assert a valid signed protobuf 32-bit integer.
*/
export function assertInt32(arg) {
if (typeof arg !== "number")
throw new Error("invalid int 32: " + typeof arg);
if (!Number.isInteger(arg) || arg > INT32_MAX || arg < INT32_MIN)
throw new Error("invalid int 32: " + arg); // eslint-disable-line @typescript-eslint/restrict-plus-operands -- we want the implicit conversion to string
}
/**
* Assert a valid unsigned protobuf 32-bit integer.
*/
export function assertUInt32(arg) {
if (typeof arg !== "number")
throw new Error("invalid uint 32: " + typeof arg);
if (!Number.isInteger(arg) || arg > UINT32_MAX || arg < 0)
throw new Error("invalid uint 32: " + arg); // eslint-disable-line @typescript-eslint/restrict-plus-operands -- we want the implicit conversion to string
}
/**
* Assert a valid protobuf float value.
*/
export function assertFloat32(arg) {
if (typeof arg !== "number")
throw new Error("invalid float 32: " + typeof arg);
if (!Number.isFinite(arg))
return;
if (arg > FLOAT32_MAX || arg < FLOAT32_MIN)
throw new Error("invalid float 32: " + arg); // eslint-disable-line @typescript-eslint/restrict-plus-operands -- we want the implicit conversion to string
}
+7
View File
@@ -0,0 +1,7 @@
import type { IBinaryWriter } from "../binary-encoding.js";
import type { BinaryFormat, BinaryWriteOptions } from "../binary-format.js";
import type { FieldInfo } from "../field.js";
export declare function makeBinaryFormat(): BinaryFormat;
export declare function writeMapEntry(writer: IBinaryWriter, options: BinaryWriteOptions, field: FieldInfo & {
kind: "map";
}, key: string, value: any): void;
+427
View File
@@ -0,0 +1,427 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { BinaryReader, BinaryWriter, WireType } from "../binary-encoding.js";
import { Message } from "../message.js";
import { wrapField } from "./field-wrapper.js";
import { scalarZeroValue } from "./scalars.js";
import { assert } from "./assert.js";
import { isFieldSet } from "./reflect.js";
import { LongType, ScalarType } from "../scalar.js";
import { isMessage } from "../is-message.js";
/* eslint-disable prefer-const,no-case-declarations,@typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return */
const unknownFieldsSymbol = Symbol("@bufbuild/protobuf/unknown-fields");
// Default options for parsing binary data.
const readDefaults = {
readUnknownFields: true,
readerFactory: (bytes) => new BinaryReader(bytes),
};
// Default options for serializing binary data.
const writeDefaults = {
writeUnknownFields: true,
writerFactory: () => new BinaryWriter(),
};
function makeReadOptions(options) {
return options ? Object.assign(Object.assign({}, readDefaults), options) : readDefaults;
}
function makeWriteOptions(options) {
return options ? Object.assign(Object.assign({}, writeDefaults), options) : writeDefaults;
}
export function makeBinaryFormat() {
return {
makeReadOptions,
makeWriteOptions,
listUnknownFields(message) {
var _a;
return (_a = message[unknownFieldsSymbol]) !== null && _a !== void 0 ? _a : [];
},
discardUnknownFields(message) {
delete message[unknownFieldsSymbol];
},
writeUnknownFields(message, writer) {
const m = message;
const c = m[unknownFieldsSymbol];
if (c) {
for (const f of c) {
writer.tag(f.no, f.wireType).raw(f.data);
}
}
},
onUnknownField(message, no, wireType, data) {
const m = message;
if (!Array.isArray(m[unknownFieldsSymbol])) {
m[unknownFieldsSymbol] = [];
}
m[unknownFieldsSymbol].push({ no, wireType, data });
},
readMessage(message, reader, lengthOrEndTagFieldNo, options, delimitedMessageEncoding) {
const type = message.getType();
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
const end = delimitedMessageEncoding
? reader.len
: reader.pos + lengthOrEndTagFieldNo;
let fieldNo, wireType;
while (reader.pos < end) {
[fieldNo, wireType] = reader.tag();
if (delimitedMessageEncoding === true &&
wireType == WireType.EndGroup) {
break;
}
const field = type.fields.find(fieldNo);
if (!field) {
const data = reader.skip(wireType, fieldNo);
if (options.readUnknownFields) {
this.onUnknownField(message, fieldNo, wireType, data);
}
continue;
}
readField(message, reader, field, wireType, options);
}
if (delimitedMessageEncoding && // eslint-disable-line @typescript-eslint/strict-boolean-expressions
(wireType != WireType.EndGroup || fieldNo !== lengthOrEndTagFieldNo)) {
throw new Error(`invalid end group tag`);
}
},
readField,
writeMessage(message, writer, options) {
const type = message.getType();
for (const field of type.fields.byNumber()) {
if (!isFieldSet(field, message)) {
if (field.req) {
throw new Error(`cannot encode field ${type.typeName}.${field.name} to binary: required field not set`);
}
continue;
}
const value = field.oneof
? message[field.oneof.localName].value
: message[field.localName];
writeField(field, value, writer, options);
}
if (options.writeUnknownFields) {
this.writeUnknownFields(message, writer);
}
return writer;
},
writeField(field, value, writer, options) {
// The behavior of our internal function has changed, it does no longer
// accept `undefined` values for singular scalar and map.
// For backwards-compatibility, we support the old form that is part of
// the public API through the interface BinaryFormat.
if (value === undefined) {
return undefined;
}
writeField(field, value, writer, options);
},
};
}
function readField(target, // eslint-disable-line @typescript-eslint/no-explicit-any -- `any` is the best choice for dynamic access
reader, field, wireType, options) {
let { repeated, localName } = field;
if (field.oneof) {
target = target[field.oneof.localName];
if (target.case != localName) {
delete target.value;
}
target.case = localName;
localName = "value";
}
switch (field.kind) {
case "scalar":
case "enum":
const scalarType = field.kind == "enum" ? ScalarType.INT32 : field.T;
let read = readScalar;
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison -- acceptable since it's covered by tests
if (field.kind == "scalar" && field.L > 0) {
read = readScalarLTString;
}
if (repeated) {
let arr = target[localName]; // safe to assume presence of array, oneof cannot contain repeated values
const isPacked = wireType == WireType.LengthDelimited &&
scalarType != ScalarType.STRING &&
scalarType != ScalarType.BYTES;
if (isPacked) {
let e = reader.uint32() + reader.pos;
while (reader.pos < e) {
arr.push(read(reader, scalarType));
}
}
else {
arr.push(read(reader, scalarType));
}
}
else {
target[localName] = read(reader, scalarType);
}
break;
case "message":
const messageType = field.T;
if (repeated) {
// safe to assume presence of array, oneof cannot contain repeated values
target[localName].push(readMessageField(reader, new messageType(), options, field));
}
else {
if (isMessage(target[localName])) {
readMessageField(reader, target[localName], options, field);
}
else {
target[localName] = readMessageField(reader, new messageType(), options, field);
if (messageType.fieldWrapper && !field.oneof && !field.repeated) {
target[localName] = messageType.fieldWrapper.unwrapField(target[localName]);
}
}
}
break;
case "map":
let [mapKey, mapVal] = readMapEntry(field, reader, options);
// safe to assume presence of map object, oneof cannot contain repeated values
target[localName][mapKey] = mapVal;
break;
}
}
// Read a message, avoiding MessageType.fromBinary() to re-use the
// BinaryReadOptions and the IBinaryReader.
function readMessageField(reader, message, options, field) {
const format = message.getType().runtime.bin;
const delimited = field === null || field === void 0 ? void 0 : field.delimited;
format.readMessage(message, reader, delimited ? field.no : reader.uint32(), // eslint-disable-line @typescript-eslint/strict-boolean-expressions
options, delimited);
return message;
}
// Read a map field, expecting key field = 1, value field = 2
function readMapEntry(field, reader, options) {
const length = reader.uint32(), end = reader.pos + length;
let key, val;
while (reader.pos < end) {
const [fieldNo] = reader.tag();
switch (fieldNo) {
case 1:
key = readScalar(reader, field.K);
break;
case 2:
switch (field.V.kind) {
case "scalar":
val = readScalar(reader, field.V.T);
break;
case "enum":
val = reader.int32();
break;
case "message":
val = readMessageField(reader, new field.V.T(), options, undefined);
break;
}
break;
}
}
if (key === undefined) {
key = scalarZeroValue(field.K, LongType.BIGINT);
}
if (typeof key != "string" && typeof key != "number") {
key = key.toString();
}
if (val === undefined) {
switch (field.V.kind) {
case "scalar":
val = scalarZeroValue(field.V.T, LongType.BIGINT);
break;
case "enum":
val = field.V.T.values[0].no;
break;
case "message":
val = new field.V.T();
break;
}
}
return [key, val];
}
// Read a scalar value, but return 64 bit integral types (int64, uint64,
// sint64, fixed64, sfixed64) as string instead of bigint.
function readScalarLTString(reader, type) {
const v = readScalar(reader, type);
return typeof v == "bigint" ? v.toString() : v;
}
// Does not use scalarTypeInfo() for better performance.
function readScalar(reader, type) {
switch (type) {
case ScalarType.STRING:
return reader.string();
case ScalarType.BOOL:
return reader.bool();
case ScalarType.DOUBLE:
return reader.double();
case ScalarType.FLOAT:
return reader.float();
case ScalarType.INT32:
return reader.int32();
case ScalarType.INT64:
return reader.int64();
case ScalarType.UINT64:
return reader.uint64();
case ScalarType.FIXED64:
return reader.fixed64();
case ScalarType.BYTES:
return reader.bytes();
case ScalarType.FIXED32:
return reader.fixed32();
case ScalarType.SFIXED32:
return reader.sfixed32();
case ScalarType.SFIXED64:
return reader.sfixed64();
case ScalarType.SINT64:
return reader.sint64();
case ScalarType.UINT32:
return reader.uint32();
case ScalarType.SINT32:
return reader.sint32();
}
}
function writeField(field, value, writer, options) {
assert(value !== undefined);
const repeated = field.repeated;
switch (field.kind) {
case "scalar":
case "enum":
let scalarType = field.kind == "enum" ? ScalarType.INT32 : field.T;
if (repeated) {
assert(Array.isArray(value));
if (field.packed) {
writePacked(writer, scalarType, field.no, value);
}
else {
for (const item of value) {
writeScalar(writer, scalarType, field.no, item);
}
}
}
else {
writeScalar(writer, scalarType, field.no, value);
}
break;
case "message":
if (repeated) {
assert(Array.isArray(value));
for (const item of value) {
writeMessageField(writer, options, field, item);
}
}
else {
writeMessageField(writer, options, field, value);
}
break;
case "map":
assert(typeof value == "object" && value != null);
for (const [key, val] of Object.entries(value)) {
writeMapEntry(writer, options, field, key, val);
}
break;
}
}
export function writeMapEntry(writer, options, field, key, value) {
writer.tag(field.no, WireType.LengthDelimited);
writer.fork();
// javascript only allows number or string for object properties
// we convert from our representation to the protobuf type
let keyValue = key;
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- we deliberately handle just the special cases for map keys
switch (field.K) {
case ScalarType.INT32:
case ScalarType.FIXED32:
case ScalarType.UINT32:
case ScalarType.SFIXED32:
case ScalarType.SINT32:
keyValue = Number.parseInt(key);
break;
case ScalarType.BOOL:
assert(key == "true" || key == "false");
keyValue = key == "true";
break;
}
// write key, expecting key field number = 1
writeScalar(writer, field.K, 1, keyValue);
// write value, expecting value field number = 2
switch (field.V.kind) {
case "scalar":
writeScalar(writer, field.V.T, 2, value);
break;
case "enum":
writeScalar(writer, ScalarType.INT32, 2, value);
break;
case "message":
assert(value !== undefined);
writer.tag(2, WireType.LengthDelimited).bytes(value.toBinary(options));
break;
}
writer.join();
}
// Value must not be undefined
function writeMessageField(writer, options, field, value) {
const message = wrapField(field.T, value);
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (field.delimited)
writer
.tag(field.no, WireType.StartGroup)
.raw(message.toBinary(options))
.tag(field.no, WireType.EndGroup);
else
writer
.tag(field.no, WireType.LengthDelimited)
.bytes(message.toBinary(options));
}
function writeScalar(writer, type, fieldNo, value) {
assert(value !== undefined);
let [wireType, method] = scalarTypeInfo(type);
writer.tag(fieldNo, wireType)[method](value);
}
function writePacked(writer, type, fieldNo, value) {
if (!value.length) {
return;
}
writer.tag(fieldNo, WireType.LengthDelimited).fork();
let [, method] = scalarTypeInfo(type);
for (let i = 0; i < value.length; i++) {
writer[method](value[i]);
}
writer.join();
}
/**
* Get information for writing a scalar value.
*
* Returns tuple:
* [0]: appropriate WireType
* [1]: name of the appropriate method of IBinaryWriter
* [2]: whether the given value is a default value for proto3 semantics
*
* If argument `value` is omitted, [2] is always false.
*/
// TODO replace call-sites writeScalar() and writePacked(), then remove
function scalarTypeInfo(type) {
let wireType = WireType.Varint;
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- INT32, UINT32, SINT32 are covered by the defaults
switch (type) {
case ScalarType.BYTES:
case ScalarType.STRING:
wireType = WireType.LengthDelimited;
break;
case ScalarType.DOUBLE:
case ScalarType.FIXED64:
case ScalarType.SFIXED64:
wireType = WireType.Bit64;
break;
case ScalarType.FIXED32:
case ScalarType.SFIXED32:
case ScalarType.FLOAT:
wireType = WireType.Bit32;
break;
}
const method = ScalarType[type].toLowerCase();
return [wireType, method];
}
+27
View File
@@ -0,0 +1,27 @@
import type { EnumType, EnumValueInfo } from "../enum.js";
/**
* Represents a generated enum.
*/
export interface EnumObject {
[key: number]: string;
[k: string]: number | string;
}
/**
* Get reflection information from a generated enum.
* If this function is called on something other than a generated
* enum, it raises an error.
*/
export declare function getEnumType(enumObject: EnumObject): EnumType;
/**
* Sets reflection information on a generated enum.
*/
export declare function setEnumType(enumObject: EnumObject, typeName: string, values: Omit<EnumValueInfo, "localName">[], opt?: {}): void;
/**
* Create a new EnumType with the given values.
*/
export declare function makeEnumType(typeName: string, values: (EnumValueInfo | Omit<EnumValueInfo, "localName">)[], _opt?: {}): EnumType;
/**
* Create a new enum object with the given values.
* Sets reflection information.
*/
export declare function makeEnum(typeName: string, values: (EnumValueInfo | Omit<EnumValueInfo, "localName">)[], opt?: {}): EnumObject;
+87
View File
@@ -0,0 +1,87 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { assert } from "./assert.js";
const enumTypeSymbol = Symbol("@bufbuild/protobuf/enum-type");
/**
* Get reflection information from a generated enum.
* If this function is called on something other than a generated
* enum, it raises an error.
*/
export function getEnumType(enumObject) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-explicit-any
const t = enumObject[enumTypeSymbol];
assert(t, "missing enum type on enum object");
return t; // eslint-disable-line @typescript-eslint/no-unsafe-return
}
/**
* Sets reflection information on a generated enum.
*/
export function setEnumType(enumObject, typeName, values, opt) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
enumObject[enumTypeSymbol] = makeEnumType(typeName, values.map((v) => ({
no: v.no,
name: v.name,
localName: enumObject[v.no],
})), opt);
}
/**
* Create a new EnumType with the given values.
*/
export function makeEnumType(typeName, values,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_opt) {
const names = Object.create(null);
const numbers = Object.create(null);
const normalValues = [];
for (const value of values) {
// We do not surface options at this time
// const value: EnumValueInfo = {...v, options: v.options ?? emptyReadonlyObject};
const n = normalizeEnumValue(value);
normalValues.push(n);
names[value.name] = n;
numbers[value.no] = n;
}
return {
typeName,
values: normalValues,
// We do not surface options at this time
// options: opt?.options ?? Object.create(null),
findName(name) {
return names[name];
},
findNumber(no) {
return numbers[no];
},
};
}
/**
* Create a new enum object with the given values.
* Sets reflection information.
*/
export function makeEnum(typeName, values, opt) {
const enumObject = {};
for (const value of values) {
const n = normalizeEnumValue(value);
enumObject[n.localName] = n.no;
enumObject[n.no] = n.localName;
}
setEnumType(enumObject, typeName, values, opt);
return enumObject;
}
function normalizeEnumValue(value) {
if ("localName" in value) {
return value;
}
return Object.assign(Object.assign({}, value), { localName: value.name });
}
+34
View File
@@ -0,0 +1,34 @@
import type { Extension } from "../extension.js";
import type { AnyMessage, Message } from "../message.js";
import type { FieldInfo, OneofInfo, PartialFieldInfo } from "../field.js";
import { WireType } from "../binary-encoding.js";
import type { ProtoRuntime } from "./proto-runtime.js";
import type { MessageType } from "../message-type.js";
export type ExtensionFieldSource = extensionFieldRules<FieldInfo> | extensionFieldRules<PartialFieldInfo> | (() => extensionFieldRules<FieldInfo>) | (() => extensionFieldRules<PartialFieldInfo>);
type extensionFieldRules<T extends FieldInfo | PartialFieldInfo> = T extends {
kind: "map";
} ? never : T extends {
oneof: string;
} ? never : T extends {
oneof: OneofInfo;
} ? never : Omit<T, "name"> & Partial<Pick<T, "name">>;
/**
* Create a new extension using the given runtime.
*/
export declare function makeExtension<E extends Message<E> = AnyMessage, V = unknown>(runtime: ProtoRuntime, typeName: string, extendee: MessageType<E>, field: ExtensionFieldSource): Extension<E, V>;
/**
* Create a container that allows us to read extension fields into it with the
* same logic as regular fields.
*/
export declare function createExtensionContainer<E extends Message<E> = AnyMessage, V = unknown>(extension: Extension<E, V>): [Record<string, V>, () => V];
type UnknownField = {
no: number;
wireType: WireType;
data: Uint8Array;
};
type UnknownFields = ReadonlyArray<UnknownField>;
/**
* Helper to filter unknown fields, optimized based on field type.
*/
export declare function filterUnknownFields(unknownFields: UnknownFields, field: Pick<FieldInfo, "no" | "kind" | "repeated">): UnknownField[];
export {};
+81
View File
@@ -0,0 +1,81 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { scalarZeroValue } from "./scalars.js";
import { WireType } from "../binary-encoding.js";
/**
* Create a new extension using the given runtime.
*/
export function makeExtension(runtime, typeName, extendee, field) {
let fi;
return {
typeName,
extendee,
get field() {
if (!fi) {
const i = (typeof field == "function" ? field() : field);
i.name = typeName.split(".").pop();
i.jsonName = `[${typeName}]`;
fi = runtime.util.newFieldList([i]).list()[0];
}
return fi;
},
runtime,
};
}
/**
* Create a container that allows us to read extension fields into it with the
* same logic as regular fields.
*/
export function createExtensionContainer(extension) {
const localName = extension.field.localName;
const container = Object.create(null);
container[localName] = initExtensionField(extension);
return [container, () => container[localName]];
}
function initExtensionField(ext) {
const field = ext.field;
if (field.repeated) {
return [];
}
if (field.default !== undefined) {
return field.default;
}
switch (field.kind) {
case "enum":
return field.T.values[0].no;
case "scalar":
return scalarZeroValue(field.T, field.L);
case "message":
// eslint-disable-next-line no-case-declarations
const T = field.T, value = new T();
return T.fieldWrapper ? T.fieldWrapper.unwrapField(value) : value;
case "map":
throw "map fields are not allowed to be extensions";
}
}
/**
* Helper to filter unknown fields, optimized based on field type.
*/
export function filterUnknownFields(unknownFields, field) {
if (!field.repeated && (field.kind == "enum" || field.kind == "scalar")) {
// singular scalar fields do not merge, we pick the last
for (let i = unknownFields.length - 1; i >= 0; --i) {
if (unknownFields[i].no == field.no) {
return [unknownFields[i]];
}
}
return [];
}
return unknownFields.filter((uf) => uf.no === field.no);
}
+19
View File
@@ -0,0 +1,19 @@
import { Edition, FeatureSet, FeatureSetDefaults } from "../google/protobuf/descriptor_pb.js";
import type { BinaryReadOptions, BinaryWriteOptions } from "../binary-format.js";
/**
* A merged google.protobuf.FeaturesSet, with all fields guaranteed to be set.
*/
export type MergedFeatureSet = FeatureSet & Required<FeatureSet>;
/**
* A function that resolves features.
*
* If no feature set is provided, the default feature set for the edition is
* returned. If features are provided, they are merged into the edition default
* features.
*/
export type FeatureResolverFn = (a?: FeatureSet, b?: FeatureSet) => MergedFeatureSet;
/**
* Create an edition feature resolver with the given feature set defaults, or
* the feature set defaults supported by @bufbuild/protobuf.
*/
export declare function createFeatureResolver(edition: Edition, compiledFeatureSetDefaults?: FeatureSetDefaults, serializationOptions?: Partial<BinaryReadOptions & BinaryWriteOptions>): FeatureResolverFn;
+116
View File
@@ -0,0 +1,116 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Edition, FeatureSet, FeatureSetDefaults, } from "../google/protobuf/descriptor_pb.js";
import { protoBase64 } from "../proto-base64.js";
/**
* Return the edition feature defaults supported by @bufbuild/protobuf.
*/
function getFeatureSetDefaults(options) {
return FeatureSetDefaults.fromBinary(protoBase64.dec(
/*upstream-inject-feature-defaults-start*/ "ChMY5gciACoMCAEQAhgCIAMoATACChMY5wciACoMCAIQARgBIAIoATABChMY6AciDAgBEAEYASACKAEwASoAIOYHKOgH" /*upstream-inject-feature-defaults-end*/), options);
}
/**
* Create an edition feature resolver with the given feature set defaults, or
* the feature set defaults supported by @bufbuild/protobuf.
*/
export function createFeatureResolver(edition, compiledFeatureSetDefaults, serializationOptions) {
var _a;
const fds = compiledFeatureSetDefaults !== null && compiledFeatureSetDefaults !== void 0 ? compiledFeatureSetDefaults : getFeatureSetDefaults(serializationOptions);
const min = fds.minimumEdition;
const max = fds.maximumEdition;
if (min === undefined ||
max === undefined ||
fds.defaults.some((d) => d.edition === undefined)) {
throw new Error("Invalid FeatureSetDefaults");
}
if (edition < min) {
throw new Error(`Edition ${Edition[edition]} is earlier than the minimum supported edition ${Edition[min]}`);
}
if (max < edition) {
throw new Error(`Edition ${Edition[edition]} is later than the maximum supported edition ${Edition[max]}`);
}
let highestMatch = undefined;
for (const c of fds.defaults) {
const e = (_a = c.edition) !== null && _a !== void 0 ? _a : 0;
if (e > edition) {
continue;
}
if (highestMatch !== undefined && highestMatch.e > e) {
continue;
}
let f;
if (c.fixedFeatures && c.overridableFeatures) {
f = c.fixedFeatures;
f.fromBinary(c.overridableFeatures.toBinary());
}
else if (c.fixedFeatures) {
f = c.fixedFeatures;
}
else if (c.overridableFeatures) {
f = c.overridableFeatures;
}
else {
f = new FeatureSet();
}
highestMatch = {
e,
f,
};
}
if (highestMatch === undefined) {
throw new Error(`No valid default found for edition ${Edition[edition]}`);
}
const featureSetBin = highestMatch.f.toBinary(serializationOptions);
return (...rest) => {
const f = FeatureSet.fromBinary(featureSetBin, serializationOptions);
for (const c of rest) {
if (c !== undefined) {
f.fromBinary(c.toBinary(serializationOptions), serializationOptions);
}
}
if (!validateMergedFeatures(f)) {
throw new Error(`Invalid FeatureSet for edition ${Edition[edition]}`);
}
return f;
};
}
// When protoc generates google.protobuf.FeatureSetDefaults, it ensures that
// fields are not repeated or required, do not use oneof, and have a default
// value.
//
// When features for an element are resolved, features of the element and its
// parents are merged into the default FeatureSet for the edition. Because unset
// fields in the FeatureSet of an element do not unset the default FeatureSet
// values, a resolved FeatureSet is guaranteed to have all fields set. This is
// also the case for extensions to FeatureSet that a user might provide, and for
// features from the future.
//
// We cannot exhaustively validate correctness of FeatureSetDefaults at runtime
// without knowing the schema: If no value for a feature is provided, we do not
// know that it exists at all.
//
// As a sanity check, we validate that all fields known to our version of
// FeatureSet are set.
function validateMergedFeatures(featureSet) {
for (const fi of FeatureSet.fields.list()) {
const v = featureSet[fi.localName];
if (v === undefined) {
return false;
}
if (fi.kind == "enum" && v === 0) {
return false;
}
}
return true;
}
+18
View File
@@ -0,0 +1,18 @@
import type { FieldInfo, OneofInfo, PartialFieldInfo } from "../field.js";
import type { FieldList } from "../field-list.js";
export type FieldListSource = readonly PartialFieldInfo[] | readonly FieldInfo[] | (() => readonly PartialFieldInfo[]) | (() => readonly FieldInfo[]);
export declare class InternalFieldList implements FieldList {
private readonly _fields;
private readonly _normalizer;
private all?;
private numbersAsc?;
private jsonNames?;
private numbers?;
private members?;
constructor(fields: FieldListSource, normalizer: (p: FieldListSource) => FieldInfo[]);
findJsonName(jsonName: string): FieldInfo | undefined;
find(fieldNo: number): FieldInfo | undefined;
list(): readonly FieldInfo[];
byNumber(): readonly FieldInfo[];
byMember(): readonly (FieldInfo | OneofInfo)[];
}
+72
View File
@@ -0,0 +1,72 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
export class InternalFieldList {
constructor(fields, normalizer) {
this._fields = fields;
this._normalizer = normalizer;
}
findJsonName(jsonName) {
if (!this.jsonNames) {
const t = {};
for (const f of this.list()) {
t[f.jsonName] = t[f.name] = f;
}
this.jsonNames = t;
}
return this.jsonNames[jsonName];
}
find(fieldNo) {
if (!this.numbers) {
const t = {};
for (const f of this.list()) {
t[f.no] = f;
}
this.numbers = t;
}
return this.numbers[fieldNo];
}
list() {
if (!this.all) {
this.all = this._normalizer(this._fields);
}
return this.all;
}
byNumber() {
if (!this.numbersAsc) {
this.numbersAsc = this.list()
.concat()
.sort((a, b) => a.no - b.no);
}
return this.numbersAsc;
}
byMember() {
if (!this.members) {
this.members = [];
const a = this.members;
let o;
for (const f of this.list()) {
if (f.oneof) {
if (f.oneof !== o) {
o = f.oneof;
a.push(o);
}
}
else {
a.push(f);
}
}
}
return this.members;
}
}
@@ -0,0 +1,9 @@
import type { FieldListSource } from "./field-list.js";
import type { FieldInfo } from "../field.js";
/**
* Convert a collection of field info to an array of normalized FieldInfo.
*
* The argument `packedByDefault` specifies whether fields that do not specify
* `packed` should be packed (proto3) or unpacked (proto2).
*/
export declare function normalizeFieldInfos(fieldInfos: FieldListSource, packedByDefault: boolean): FieldInfo[];
+65
View File
@@ -0,0 +1,65 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { InternalOneofInfo } from "./field.js";
import { fieldJsonName, localFieldName } from "./names.js";
import { LongType, ScalarType } from "../scalar.js";
/**
* Convert a collection of field info to an array of normalized FieldInfo.
*
* The argument `packedByDefault` specifies whether fields that do not specify
* `packed` should be packed (proto3) or unpacked (proto2).
*/
export function normalizeFieldInfos(fieldInfos, packedByDefault) {
var _a, _b, _c, _d, _e, _f;
const r = [];
let o;
for (const field of typeof fieldInfos == "function"
? fieldInfos()
: fieldInfos) {
const f = field;
f.localName = localFieldName(field.name, field.oneof !== undefined);
f.jsonName = (_a = field.jsonName) !== null && _a !== void 0 ? _a : fieldJsonName(field.name);
f.repeated = (_b = field.repeated) !== null && _b !== void 0 ? _b : false;
if (field.kind == "scalar") {
f.L = (_c = field.L) !== null && _c !== void 0 ? _c : LongType.BIGINT;
}
f.delimited = (_d = field.delimited) !== null && _d !== void 0 ? _d : false;
f.req = (_e = field.req) !== null && _e !== void 0 ? _e : false;
f.opt = (_f = field.opt) !== null && _f !== void 0 ? _f : false;
if (field.packed === undefined) {
if (packedByDefault) {
f.packed =
field.kind == "enum" ||
(field.kind == "scalar" &&
field.T != ScalarType.BYTES &&
field.T != ScalarType.STRING);
}
else {
f.packed = false;
}
}
// We do not surface options at this time
// f.options = field.options ?? emptyReadonlyObject;
if (field.oneof !== undefined) {
const ooname = typeof field.oneof == "string" ? field.oneof : field.oneof.name;
if (!o || o.name != ooname) {
o = new InternalOneofInfo(ooname);
}
f.oneof = o;
o.addField(f);
}
r.push(f);
}
return r;
}
+25
View File
@@ -0,0 +1,25 @@
import { Message } from "../message.js";
import type { MessageType } from "../message-type.js";
import type { DescExtension, DescField } from "../descriptor-set.js";
import { ScalarType } from "../scalar.js";
/**
* A field wrapper unwraps a message to a primitive value that is more
* ergonomic for use as a message field.
*
* Note that this feature exists for google/protobuf/wrappers.proto
* and cannot be used to arbitrarily modify types in generated code.
*/
export interface FieldWrapper<T extends Message<T> = any, U = any> {
wrapField(value: U): T;
unwrapField(value: T): U;
}
/**
* Wrap a primitive message field value in its corresponding wrapper
* message. This function is idempotent.
*/
export declare function wrapField<T extends Message<T>>(type: MessageType<T>, value: any): T;
/**
* If the given field uses one of the well-known wrapper types, return
* the primitive type it wraps.
*/
export declare function getUnwrappedFieldType(field: DescField | DescExtension): ScalarType | undefined;
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Message } from "../message.js";
import { ScalarType } from "../scalar.js";
import { isMessage } from "../is-message.js";
/**
* Wrap a primitive message field value in its corresponding wrapper
* message. This function is idempotent.
*/
export function wrapField(type, value) {
if (isMessage(value) || !type.fieldWrapper) {
return value;
}
return type.fieldWrapper.wrapField(value);
}
/**
* If the given field uses one of the well-known wrapper types, return
* the primitive type it wraps.
*/
export function getUnwrappedFieldType(field) {
if (field.fieldKind !== "message") {
return undefined;
}
if (field.repeated) {
return undefined;
}
if (field.oneof != undefined) {
return undefined;
}
return wktWrapperToScalarType[field.message.typeName];
}
const wktWrapperToScalarType = {
"google.protobuf.DoubleValue": ScalarType.DOUBLE,
"google.protobuf.FloatValue": ScalarType.FLOAT,
"google.protobuf.Int64Value": ScalarType.INT64,
"google.protobuf.UInt64Value": ScalarType.UINT64,
"google.protobuf.Int32Value": ScalarType.INT32,
"google.protobuf.UInt32Value": ScalarType.UINT32,
"google.protobuf.BoolValue": ScalarType.BOOL,
"google.protobuf.StringValue": ScalarType.STRING,
"google.protobuf.BytesValue": ScalarType.BYTES,
};
+16
View File
@@ -0,0 +1,16 @@
import type { FieldInfo, OneofInfo } from "../field.js";
export declare class InternalOneofInfo implements OneofInfo {
readonly kind = "oneof";
readonly name: string;
readonly localName: string;
readonly repeated = false;
readonly packed = false;
readonly opt = false;
readonly req = false;
readonly default: undefined;
readonly fields: FieldInfo[];
private _lookup?;
constructor(name: string);
addField(field: FieldInfo): void;
findField(localName: string): FieldInfo | undefined;
}
+41
View File
@@ -0,0 +1,41 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { localOneofName } from "./names.js";
import { assert } from "./assert.js";
export class InternalOneofInfo {
constructor(name) {
this.kind = "oneof";
this.repeated = false;
this.packed = false;
this.opt = false;
this.req = false;
this.default = undefined;
this.fields = [];
this.name = name;
this.localName = localOneofName(name);
}
addField(field) {
assert(field.oneof === this, `field ${field.name} not one of ${this.name}`);
this.fields.push(field);
}
findField(localName) {
if (!this._lookup) {
this._lookup = Object.create(null);
for (let i = 0; i < this.fields.length; i++) {
this._lookup[this.fields[i].localName] = this.fields[i];
}
}
return this._lookup[localName];
}
}
+2
View File
@@ -0,0 +1,2 @@
import type { JsonFormat } from "../json-format.js";
export declare function makeJsonFormat(): JsonFormat;
+623
View File
@@ -0,0 +1,623 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Message } from "../message.js";
import { assert, assertFloat32, assertInt32, assertUInt32 } from "./assert.js";
import { protoInt64 } from "../proto-int64.js";
import { protoBase64 } from "../proto-base64.js";
import { createExtensionContainer } from "./extensions.js";
import { getExtension, hasExtension, setExtension, } from "../extension-accessor.js";
import { clearField, isFieldSet } from "./reflect.js";
import { wrapField } from "./field-wrapper.js";
import { scalarZeroValue } from "./scalars.js";
import { isScalarZeroValue } from "./scalars.js";
import { LongType, ScalarType } from "../scalar.js";
import { isMessage } from "../is-message.js";
/* eslint-disable no-case-declarations,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call */
// Default options for parsing JSON.
const jsonReadDefaults = {
ignoreUnknownFields: false,
};
// Default options for serializing to JSON.
const jsonWriteDefaults = {
emitDefaultValues: false,
enumAsInteger: false,
useProtoFieldName: false,
prettySpaces: 0,
};
function makeReadOptions(options) {
return options ? Object.assign(Object.assign({}, jsonReadDefaults), options) : jsonReadDefaults;
}
function makeWriteOptions(options) {
return options ? Object.assign(Object.assign({}, jsonWriteDefaults), options) : jsonWriteDefaults;
}
const tokenNull = Symbol();
const tokenIgnoredUnknownEnum = Symbol();
export function makeJsonFormat() {
return {
makeReadOptions,
makeWriteOptions,
readMessage(type, json, options, message) {
if (json == null || Array.isArray(json) || typeof json != "object") {
throw new Error(`cannot decode message ${type.typeName} from JSON: ${debugJsonValue(json)}`);
}
message = message !== null && message !== void 0 ? message : new type();
const oneofSeen = new Map();
const registry = options.typeRegistry;
for (const [jsonKey, jsonValue] of Object.entries(json)) {
const field = type.fields.findJsonName(jsonKey);
if (field) {
if (field.oneof) {
if (jsonValue === null && field.kind == "scalar") {
// see conformance test Required.Proto3.JsonInput.OneofFieldNull{First,Second}
continue;
}
const seen = oneofSeen.get(field.oneof);
if (seen !== undefined) {
throw new Error(`cannot decode message ${type.typeName} from JSON: multiple keys for oneof "${field.oneof.name}" present: "${seen}", "${jsonKey}"`);
}
oneofSeen.set(field.oneof, jsonKey);
}
readField(message, jsonValue, field, options, type);
}
else {
let found = false;
if ((registry === null || registry === void 0 ? void 0 : registry.findExtension) &&
jsonKey.startsWith("[") &&
jsonKey.endsWith("]")) {
const ext = registry.findExtension(jsonKey.substring(1, jsonKey.length - 1));
if (ext && ext.extendee.typeName == type.typeName) {
found = true;
const [container, get] = createExtensionContainer(ext);
readField(container, jsonValue, ext.field, options, ext);
// We pass on the options as BinaryReadOptions/BinaryWriteOptions,
// so that users can bring their own binary reader and writer factories
// if necessary.
setExtension(message, ext, get(), options);
}
}
if (!found && !options.ignoreUnknownFields) {
throw new Error(`cannot decode message ${type.typeName} from JSON: key "${jsonKey}" is unknown`);
}
}
}
return message;
},
writeMessage(message, options) {
const type = message.getType();
const json = {};
let field;
try {
for (field of type.fields.byNumber()) {
if (!isFieldSet(field, message)) {
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (field.req) {
throw `required field not set`;
}
if (!options.emitDefaultValues) {
continue;
}
if (!canEmitFieldDefaultValue(field)) {
continue;
}
}
const value = field.oneof
? message[field.oneof.localName].value
: message[field.localName];
const jsonValue = writeField(field, value, options);
if (jsonValue !== undefined) {
json[options.useProtoFieldName ? field.name : field.jsonName] =
jsonValue;
}
}
const registry = options.typeRegistry;
if (registry === null || registry === void 0 ? void 0 : registry.findExtensionFor) {
for (const uf of type.runtime.bin.listUnknownFields(message)) {
const ext = registry.findExtensionFor(type.typeName, uf.no);
if (ext && hasExtension(message, ext)) {
// We pass on the options as BinaryReadOptions, so that users can bring their own
// binary reader factory if necessary.
const value = getExtension(message, ext, options);
const jsonValue = writeField(ext.field, value, options);
if (jsonValue !== undefined) {
json[ext.field.jsonName] = jsonValue;
}
}
}
}
}
catch (e) {
const m = field
? `cannot encode field ${type.typeName}.${field.name} to JSON`
: `cannot encode message ${type.typeName} to JSON`;
const r = e instanceof Error ? e.message : String(e);
throw new Error(m + (r.length > 0 ? `: ${r}` : ""));
}
return json;
},
readScalar(type, json, longType) {
// The signature of our internal function has changed. For backwards-
// compatibility, we support the old form that is part of the public API
// through the interface JsonFormat.
return readScalar(type, json, longType !== null && longType !== void 0 ? longType : LongType.BIGINT, true);
},
writeScalar(type, value, emitDefaultValues) {
// The signature of our internal function has changed. For backwards-
// compatibility, we support the old form that is part of the public API
// through the interface JsonFormat.
if (value === undefined) {
return undefined;
}
if (emitDefaultValues || isScalarZeroValue(type, value)) {
return writeScalar(type, value);
}
return undefined;
},
debug: debugJsonValue,
};
}
function debugJsonValue(json) {
if (json === null) {
return "null";
}
switch (typeof json) {
case "object":
return Array.isArray(json) ? "array" : "object";
case "string":
return json.length > 100 ? "string" : `"${json.split('"').join('\\"')}"`;
default:
return String(json);
}
}
// Read a JSON value for a field.
// The "parentType" argument is only used to provide context in errors.
function readField(target, jsonValue, field, options, parentType) {
let localName = field.localName;
if (field.repeated) {
assert(field.kind != "map");
if (jsonValue === null) {
return;
}
if (!Array.isArray(jsonValue)) {
throw new Error(`cannot decode field ${parentType.typeName}.${field.name} from JSON: ${debugJsonValue(jsonValue)}`);
}
const targetArray = target[localName];
for (const jsonItem of jsonValue) {
if (jsonItem === null) {
throw new Error(`cannot decode field ${parentType.typeName}.${field.name} from JSON: ${debugJsonValue(jsonItem)}`);
}
switch (field.kind) {
case "message":
targetArray.push(field.T.fromJson(jsonItem, options));
break;
case "enum":
const enumValue = readEnum(field.T, jsonItem, options.ignoreUnknownFields, true);
if (enumValue !== tokenIgnoredUnknownEnum) {
targetArray.push(enumValue);
}
break;
case "scalar":
try {
targetArray.push(readScalar(field.T, jsonItem, field.L, true));
}
catch (e) {
let m = `cannot decode field ${parentType.typeName}.${field.name} from JSON: ${debugJsonValue(jsonItem)}`;
if (e instanceof Error && e.message.length > 0) {
m += `: ${e.message}`;
}
throw new Error(m);
}
break;
}
}
}
else if (field.kind == "map") {
if (jsonValue === null) {
return;
}
if (typeof jsonValue != "object" || Array.isArray(jsonValue)) {
throw new Error(`cannot decode field ${parentType.typeName}.${field.name} from JSON: ${debugJsonValue(jsonValue)}`);
}
const targetMap = target[localName];
for (const [jsonMapKey, jsonMapValue] of Object.entries(jsonValue)) {
if (jsonMapValue === null) {
throw new Error(`cannot decode field ${parentType.typeName}.${field.name} from JSON: map value null`);
}
let key;
try {
key = readMapKey(field.K, jsonMapKey);
}
catch (e) {
let m = `cannot decode map key for field ${parentType.typeName}.${field.name} from JSON: ${debugJsonValue(jsonValue)}`;
if (e instanceof Error && e.message.length > 0) {
m += `: ${e.message}`;
}
throw new Error(m);
}
switch (field.V.kind) {
case "message":
targetMap[key] = field.V.T.fromJson(jsonMapValue, options);
break;
case "enum":
const enumValue = readEnum(field.V.T, jsonMapValue, options.ignoreUnknownFields, true);
if (enumValue !== tokenIgnoredUnknownEnum) {
targetMap[key] = enumValue;
}
break;
case "scalar":
try {
targetMap[key] = readScalar(field.V.T, jsonMapValue, LongType.BIGINT, true);
}
catch (e) {
let m = `cannot decode map value for field ${parentType.typeName}.${field.name} from JSON: ${debugJsonValue(jsonValue)}`;
if (e instanceof Error && e.message.length > 0) {
m += `: ${e.message}`;
}
throw new Error(m);
}
break;
}
}
}
else {
if (field.oneof) {
target = target[field.oneof.localName] = { case: localName };
localName = "value";
}
switch (field.kind) {
case "message":
const messageType = field.T;
if (jsonValue === null &&
messageType.typeName != "google.protobuf.Value") {
return;
}
let currentValue = target[localName];
if (isMessage(currentValue)) {
currentValue.fromJson(jsonValue, options);
}
else {
target[localName] = currentValue = messageType.fromJson(jsonValue, options);
if (messageType.fieldWrapper && !field.oneof) {
target[localName] =
messageType.fieldWrapper.unwrapField(currentValue);
}
}
break;
case "enum":
const enumValue = readEnum(field.T, jsonValue, options.ignoreUnknownFields, false);
switch (enumValue) {
case tokenNull:
clearField(field, target);
break;
case tokenIgnoredUnknownEnum:
break;
default:
target[localName] = enumValue;
break;
}
break;
case "scalar":
try {
const scalarValue = readScalar(field.T, jsonValue, field.L, false);
switch (scalarValue) {
case tokenNull:
clearField(field, target);
break;
default:
target[localName] = scalarValue;
break;
}
}
catch (e) {
let m = `cannot decode field ${parentType.typeName}.${field.name} from JSON: ${debugJsonValue(jsonValue)}`;
if (e instanceof Error && e.message.length > 0) {
m += `: ${e.message}`;
}
throw new Error(m);
}
break;
}
}
}
function readMapKey(type, json) {
if (type === ScalarType.BOOL) {
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
switch (json) {
case "true":
json = true;
break;
case "false":
json = false;
break;
}
}
return readScalar(type, json, LongType.BIGINT, true).toString();
}
function readScalar(type, json, longType, nullAsZeroValue) {
if (json === null) {
if (nullAsZeroValue) {
return scalarZeroValue(type, longType);
}
return tokenNull;
}
// every valid case in the switch below returns, and every fall
// through is regarded as a failure.
switch (type) {
// float, double: JSON value will be a number or one of the special string values "NaN", "Infinity", and "-Infinity".
// Either numbers or strings are accepted. Exponent notation is also accepted.
case ScalarType.DOUBLE:
case ScalarType.FLOAT:
if (json === "NaN")
return Number.NaN;
if (json === "Infinity")
return Number.POSITIVE_INFINITY;
if (json === "-Infinity")
return Number.NEGATIVE_INFINITY;
if (json === "") {
// empty string is not a number
break;
}
if (typeof json == "string" && json.trim().length !== json.length) {
// extra whitespace
break;
}
if (typeof json != "string" && typeof json != "number") {
break;
}
const float = Number(json);
if (Number.isNaN(float)) {
// not a number
break;
}
if (!Number.isFinite(float)) {
// infinity and -infinity are handled by string representation above, so this is an error
break;
}
if (type == ScalarType.FLOAT)
assertFloat32(float);
return float;
// int32, fixed32, uint32: JSON value will be a decimal number. Either numbers or strings are accepted.
case ScalarType.INT32:
case ScalarType.FIXED32:
case ScalarType.SFIXED32:
case ScalarType.SINT32:
case ScalarType.UINT32:
let int32;
if (typeof json == "number")
int32 = json;
else if (typeof json == "string" && json.length > 0) {
if (json.trim().length === json.length)
int32 = Number(json);
}
if (int32 === undefined)
break;
if (type == ScalarType.UINT32 || type == ScalarType.FIXED32)
assertUInt32(int32);
else
assertInt32(int32);
return int32;
// int64, fixed64, uint64: JSON value will be a decimal string. Either numbers or strings are accepted.
case ScalarType.INT64:
case ScalarType.SFIXED64:
case ScalarType.SINT64:
if (typeof json != "number" && typeof json != "string")
break;
const long = protoInt64.parse(json);
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
return longType ? long.toString() : long;
case ScalarType.FIXED64:
case ScalarType.UINT64:
if (typeof json != "number" && typeof json != "string")
break;
const uLong = protoInt64.uParse(json);
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
return longType ? uLong.toString() : uLong;
// bool:
case ScalarType.BOOL:
if (typeof json !== "boolean")
break;
return json;
// string:
case ScalarType.STRING:
if (typeof json !== "string") {
break;
}
// A string must always contain UTF-8 encoded or 7-bit ASCII.
// We validate with encodeURIComponent, which appears to be the fastest widely available option.
try {
encodeURIComponent(json);
}
catch (e) {
throw new Error("invalid UTF8");
}
return json;
// bytes: JSON value will be the data encoded as a string using standard base64 encoding with paddings.
// Either standard or URL-safe base64 encoding with/without paddings are accepted.
case ScalarType.BYTES:
if (json === "")
return new Uint8Array(0);
if (typeof json !== "string")
break;
return protoBase64.dec(json);
}
throw new Error();
}
function readEnum(type, json, ignoreUnknownFields, nullAsZeroValue) {
if (json === null) {
if (type.typeName == "google.protobuf.NullValue") {
return 0; // google.protobuf.NullValue.NULL_VALUE = 0
}
return nullAsZeroValue ? type.values[0].no : tokenNull;
}
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
switch (typeof json) {
case "number":
if (Number.isInteger(json)) {
return json;
}
break;
case "string":
const value = type.findName(json);
if (value !== undefined) {
return value.no;
}
if (ignoreUnknownFields) {
return tokenIgnoredUnknownEnum;
}
break;
}
throw new Error(`cannot decode enum ${type.typeName} from JSON: ${debugJsonValue(json)}`);
}
// Decide whether an unset field should be emitted with JSON write option `emitDefaultValues`
function canEmitFieldDefaultValue(field) {
if (field.repeated || field.kind == "map") {
// maps are {}, repeated fields are []
return true;
}
if (field.oneof) {
// oneof fields are never emitted
return false;
}
if (field.kind == "message") {
// singular message field are allowed to emit JSON null, but we do not
return false;
}
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (field.opt || field.req) {
// the field uses explicit presence, so we cannot emit a zero value
return false;
}
return true;
}
function writeField(field, value, options) {
if (field.kind == "map") {
assert(typeof value == "object" && value != null);
const jsonObj = {};
const entries = Object.entries(value);
switch (field.V.kind) {
case "scalar":
for (const [entryKey, entryValue] of entries) {
jsonObj[entryKey.toString()] = writeScalar(field.V.T, entryValue); // JSON standard allows only (double quoted) string as property key
}
break;
case "message":
for (const [entryKey, entryValue] of entries) {
// JSON standard allows only (double quoted) string as property key
jsonObj[entryKey.toString()] = entryValue.toJson(options);
}
break;
case "enum":
const enumType = field.V.T;
for (const [entryKey, entryValue] of entries) {
// JSON standard allows only (double quoted) string as property key
jsonObj[entryKey.toString()] = writeEnum(enumType, entryValue, options.enumAsInteger);
}
break;
}
return options.emitDefaultValues || entries.length > 0
? jsonObj
: undefined;
}
if (field.repeated) {
assert(Array.isArray(value));
const jsonArr = [];
switch (field.kind) {
case "scalar":
for (let i = 0; i < value.length; i++) {
jsonArr.push(writeScalar(field.T, value[i]));
}
break;
case "enum":
for (let i = 0; i < value.length; i++) {
jsonArr.push(writeEnum(field.T, value[i], options.enumAsInteger));
}
break;
case "message":
for (let i = 0; i < value.length; i++) {
jsonArr.push(value[i].toJson(options));
}
break;
}
return options.emitDefaultValues || jsonArr.length > 0
? jsonArr
: undefined;
}
switch (field.kind) {
case "scalar":
return writeScalar(field.T, value);
case "enum":
return writeEnum(field.T, value, options.enumAsInteger);
case "message":
return wrapField(field.T, value).toJson(options);
}
}
function writeEnum(type, value, enumAsInteger) {
var _a;
assert(typeof value == "number");
if (type.typeName == "google.protobuf.NullValue") {
return null;
}
if (enumAsInteger) {
return value;
}
const val = type.findNumber(value);
return (_a = val === null || val === void 0 ? void 0 : val.name) !== null && _a !== void 0 ? _a : value; // if we don't know the enum value, just return the number
}
function writeScalar(type, value) {
switch (type) {
// int32, fixed32, uint32: JSON value will be a decimal number. Either numbers or strings are accepted.
case ScalarType.INT32:
case ScalarType.SFIXED32:
case ScalarType.SINT32:
case ScalarType.FIXED32:
case ScalarType.UINT32:
assert(typeof value == "number");
return value;
// float, double: JSON value will be a number or one of the special string values "NaN", "Infinity", and "-Infinity".
// Either numbers or strings are accepted. Exponent notation is also accepted.
case ScalarType.FLOAT:
// assertFloat32(value);
case ScalarType.DOUBLE: // eslint-disable-line no-fallthrough
assert(typeof value == "number");
if (Number.isNaN(value))
return "NaN";
if (value === Number.POSITIVE_INFINITY)
return "Infinity";
if (value === Number.NEGATIVE_INFINITY)
return "-Infinity";
return value;
// string:
case ScalarType.STRING:
assert(typeof value == "string");
return value;
// bool:
case ScalarType.BOOL:
assert(typeof value == "boolean");
return value;
// JSON value will be a decimal string. Either numbers or strings are accepted.
case ScalarType.UINT64:
case ScalarType.FIXED64:
case ScalarType.INT64:
case ScalarType.SFIXED64:
case ScalarType.SINT64:
assert(typeof value == "bigint" ||
typeof value == "string" ||
typeof value == "number");
return value.toString();
// bytes: JSON value will be the data encoded as a string using standard base64 encoding with paddings.
// Either standard or URL-safe base64 encoding with/without paddings are accepted.
case ScalarType.BYTES:
assert(value instanceof Uint8Array);
return protoBase64.enc(value);
}
}
+18
View File
@@ -0,0 +1,18 @@
import { Message } from "../message.js";
import type { AnyMessage } from "../message.js";
import type { FieldListSource } from "./field-list.js";
import type { MessageType } from "../message-type.js";
import type { ProtoRuntime } from "./proto-runtime.js";
/**
* Create a new message type using the given runtime.
*/
export declare function makeMessageType<T extends Message<T> = AnyMessage>(runtime: ProtoRuntime, typeName: string, fields: FieldListSource, opt?: {
/**
* localName is the "name" property of the constructed function.
* It is useful in stack traces, debuggers and test frameworks,
* but has no other implications.
*
* If omitted, the last part of the typeName is used.
*/
localName?: string;
}): MessageType<T>;
+46
View File
@@ -0,0 +1,46 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Message } from "../message.js";
/**
* Create a new message type using the given runtime.
*/
export function makeMessageType(runtime, typeName, fields, opt) {
var _a;
const localName = (_a = opt === null || opt === void 0 ? void 0 : opt.localName) !== null && _a !== void 0 ? _a : typeName.substring(typeName.lastIndexOf(".") + 1);
const type = {
[localName]: function (data) {
runtime.util.initFields(this);
runtime.util.initPartial(data, this);
},
}[localName];
Object.setPrototypeOf(type.prototype, new Message());
Object.assign(type, {
runtime,
typeName,
fields: runtime.util.newFieldList(fields),
fromBinary(bytes, options) {
return new type().fromBinary(bytes, options);
},
fromJson(jsonValue, options) {
return new type().fromJson(jsonValue, options);
},
fromJsonString(jsonString, options) {
return new type().fromJsonString(jsonString, options);
},
equals(a, b) {
return runtime.util.equals(type, a, b);
},
});
return type;
}
+43
View File
@@ -0,0 +1,43 @@
import type { DescEnum, DescEnumValue, DescExtension, DescField, DescMessage, DescService } from "../descriptor-set.js";
import type { DescMethod, DescOneof } from "../descriptor-set.js";
/**
* Returns the name of a protobuf element in generated code.
*
* Field names - including oneofs - are converted to lowerCamelCase. For
* messages, enumerations and services, the package name is stripped from
* the type name. For nested messages and enumerations, the names are joined
* with an underscore. For methods, the first character is made lowercase.
*/
export declare function localName(desc: DescEnum | DescEnumValue | DescMessage | DescExtension | DescOneof | DescField | DescService | DescMethod): string;
/**
* Returns the name of a field in generated code.
*/
export declare function localFieldName(protoName: string, inOneof: boolean): string;
/**
* Returns the name of a oneof group in generated code.
*/
export declare function localOneofName(protoName: string): string;
/**
* Returns the JSON name for a protobuf field, exactly like protoc does.
*/
export declare const fieldJsonName: typeof protoCamelCase;
/**
* Finds a prefix shared by enum values, for example `MY_ENUM_` for
* `enum MyEnum {MY_ENUM_A=0; MY_ENUM_B=1;}`.
*/
export declare function findEnumSharedPrefix(enumName: string, valueNames: string[]): string | undefined;
/**
* Converts snake_case to protoCamelCase according to the convention
* used by protoc to convert a field name to a JSON name.
*/
declare function protoCamelCase(snakeCase: string): string;
/**
* Names that cannot be used for object properties because they are reserved
* by built-in JavaScript properties.
*/
export declare const safeObjectProperty: (name: string) => string;
/**
* Names that can be used for identifiers or class properties
*/
export declare const safeIdentifier: (name: string) => string;
export {};
+269
View File
@@ -0,0 +1,269 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* Returns the name of a protobuf element in generated code.
*
* Field names - including oneofs - are converted to lowerCamelCase. For
* messages, enumerations and services, the package name is stripped from
* the type name. For nested messages and enumerations, the names are joined
* with an underscore. For methods, the first character is made lowercase.
*/
export function localName(desc) {
switch (desc.kind) {
case "field":
return localFieldName(desc.name, desc.oneof !== undefined);
case "oneof":
return localOneofName(desc.name);
case "enum":
case "message":
case "service":
case "extension": {
const pkg = desc.file.proto.package;
const offset = pkg === undefined ? 0 : pkg.length + 1;
const name = desc.typeName.substring(offset).replace(/\./g, "_");
// For services, we only care about safe identifiers, not safe object properties,
// but we have shipped v1 with a bug that respected object properties, and we
// do not want to introduce a breaking change, so we continue to escape for
// safe object properties.
// See https://github.com/bufbuild/protobuf-es/pull/391
return safeObjectProperty(safeIdentifier(name));
}
case "enum_value": {
let name = desc.name;
const sharedPrefix = desc.parent.sharedPrefix;
if (sharedPrefix !== undefined) {
name = name.substring(sharedPrefix.length);
}
return safeObjectProperty(name);
}
case "rpc": {
let name = desc.name;
if (name.length == 0) {
return name;
}
name = name[0].toLowerCase() + name.substring(1);
return safeObjectProperty(name);
}
}
}
/**
* Returns the name of a field in generated code.
*/
export function localFieldName(protoName, inOneof) {
const name = protoCamelCase(protoName);
if (inOneof) {
// oneof member names are not properties, but values of the `case` property.
return name;
}
return safeObjectProperty(safeMessageProperty(name));
}
/**
* Returns the name of a oneof group in generated code.
*/
export function localOneofName(protoName) {
return localFieldName(protoName, false);
}
/**
* Returns the JSON name for a protobuf field, exactly like protoc does.
*/
export const fieldJsonName = protoCamelCase;
/**
* Finds a prefix shared by enum values, for example `MY_ENUM_` for
* `enum MyEnum {MY_ENUM_A=0; MY_ENUM_B=1;}`.
*/
export function findEnumSharedPrefix(enumName, valueNames) {
const prefix = camelToSnakeCase(enumName) + "_";
for (const name of valueNames) {
if (!name.toLowerCase().startsWith(prefix)) {
return undefined;
}
const shortName = name.substring(prefix.length);
if (shortName.length == 0) {
return undefined;
}
if (/^\d/.test(shortName)) {
// identifiers must not start with numbers
return undefined;
}
}
return prefix;
}
/**
* Converts lowerCamelCase or UpperCamelCase into lower_snake_case.
* This is used to find shared prefixes in an enum.
*/
function camelToSnakeCase(camel) {
return (camel.substring(0, 1) + camel.substring(1).replace(/[A-Z]/g, (c) => "_" + c)).toLowerCase();
}
/**
* Converts snake_case to protoCamelCase according to the convention
* used by protoc to convert a field name to a JSON name.
*/
function protoCamelCase(snakeCase) {
let capNext = false;
const b = [];
for (let i = 0; i < snakeCase.length; i++) {
let c = snakeCase.charAt(i);
switch (c) {
case "_":
capNext = true;
break;
case "0":
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
case "9":
b.push(c);
capNext = false;
break;
default:
if (capNext) {
capNext = false;
c = c.toUpperCase();
}
b.push(c);
break;
}
}
return b.join("");
}
/**
* Names that cannot be used for identifiers, such as class names,
* but _can_ be used for object properties.
*/
const reservedIdentifiers = new Set([
// ECMAScript 2015 keywords
"break",
"case",
"catch",
"class",
"const",
"continue",
"debugger",
"default",
"delete",
"do",
"else",
"export",
"extends",
"false",
"finally",
"for",
"function",
"if",
"import",
"in",
"instanceof",
"new",
"null",
"return",
"super",
"switch",
"this",
"throw",
"true",
"try",
"typeof",
"var",
"void",
"while",
"with",
"yield",
// ECMAScript 2015 future reserved keywords
"enum",
"implements",
"interface",
"let",
"package",
"private",
"protected",
"public",
"static",
// Class name cannot be 'Object' when targeting ES5 with module CommonJS
"Object",
// TypeScript keywords that cannot be used for types (as opposed to variables)
"bigint",
"number",
"boolean",
"string",
"object",
// Identifiers reserved for the runtime, so we can generate legible code
"globalThis",
"Uint8Array",
"Partial",
]);
/**
* Names that cannot be used for object properties because they are reserved
* by built-in JavaScript properties.
*/
const reservedObjectProperties = new Set([
// names reserved by JavaScript
"constructor",
"toString",
"toJSON",
"valueOf",
]);
/**
* Names that cannot be used for object properties because they are reserved
* by the runtime.
*/
const reservedMessageProperties = new Set([
// names reserved by the runtime
"getType",
"clone",
"equals",
"fromBinary",
"fromJson",
"fromJsonString",
"toBinary",
"toJson",
"toJsonString",
// names reserved by the runtime for the future
"toObject",
]);
const fallback = (name) => `${name}$`;
/**
* Will wrap names that are Object prototype properties or names reserved
* for `Message`s.
*/
const safeMessageProperty = (name) => {
if (reservedMessageProperties.has(name)) {
return fallback(name);
}
return name;
};
/**
* Names that cannot be used for object properties because they are reserved
* by built-in JavaScript properties.
*/
export const safeObjectProperty = (name) => {
if (reservedObjectProperties.has(name)) {
return fallback(name);
}
return name;
};
/**
* Names that can be used for identifiers or class properties
*/
export const safeIdentifier = (name) => {
if (reservedIdentifiers.has(name)) {
return fallback(name);
}
return name;
};
+7
View File
@@ -0,0 +1,7 @@
import type { JsonValue } from "../json-format.js";
/**
*
*/
export type OptionsMap = {
readonly [extensionName: string]: JsonValue;
};
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
export {};
+53
View File
@@ -0,0 +1,53 @@
import type { JsonFormat } from "../json-format.js";
import type { BinaryFormat } from "../binary-format.js";
import type { AnyMessage } from "../message.js";
import type { Message } from "../message.js";
import type { EnumType, EnumValueInfo } from "../enum.js";
import type { MessageType } from "../message-type.js";
import type { FieldListSource } from "./field-list.js";
import type { EnumObject } from "./enum.js";
import type { Util } from "./util.js";
import type { Extension } from "../extension.js";
import type { ExtensionFieldSource } from "./extensions.js";
/**
* A facade that provides serialization and other internal functionality.
*/
export interface ProtoRuntime {
readonly syntax: string;
readonly json: JsonFormat;
readonly bin: BinaryFormat;
readonly util: Util;
/**
* Create a message type at runtime, without generating code.
*/
makeMessageType<T extends Message<T> = AnyMessage>(typeName: string, fields: FieldListSource, opt?: {
localName?: string;
}): MessageType<T>;
/**
* Create an enum object at runtime, without generating code.
*
* The object conforms to TypeScript enums, and comes with
* mapping from name to value, and from value to name.
*
* The type name and other reflection information is accessible
* via getEnumType().
*/
makeEnum(typeName: string, values: (EnumValueInfo | Omit<EnumValueInfo, "localName">)[], opt?: {}): EnumObject;
/**
* Create an enum type at runtime, without generating code.
* Note that this only creates the reflection information, not an
* actual enum object.
*/
makeEnumType(typeName: string, values: (EnumValueInfo | Omit<EnumValueInfo, "localName">)[], opt?: {}): EnumType;
/**
* Get reflection information - the EnumType - from an enum object.
* If this function is called on something other than a generated
* enum, or an enum constructed with makeEnum(), it raises an error.
*/
getEnumType(enumObject: EnumObject): EnumType;
/**
* Create an extension at runtime, without generating code.
*/
makeExtension<E extends Message<E> = AnyMessage, V = unknown>(typeName: string, extendee: MessageType<E>, field: ExtensionFieldSource): Extension<E, V>;
}
export declare function makeProtoRuntime(syntax: string, newFieldList: Util["newFieldList"], initFields: Util["initFields"]): ProtoRuntime;
+37
View File
@@ -0,0 +1,37 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { getEnumType, makeEnum, makeEnumType } from "./enum.js";
import { makeMessageType } from "./message-type.js";
import { makeExtension } from "./extensions.js";
import { makeJsonFormat } from "./json-format.js";
import { makeBinaryFormat } from "./binary-format.js";
import { makeUtilCommon } from "./util-common.js";
export function makeProtoRuntime(syntax, newFieldList, initFields) {
return {
syntax,
json: makeJsonFormat(),
bin: makeBinaryFormat(),
util: Object.assign(Object.assign({}, makeUtilCommon()), { newFieldList,
initFields }),
makeMessageType(typeName, fields, opt) {
return makeMessageType(this, typeName, fields, opt);
},
makeEnum,
makeEnumType,
getEnumType,
makeExtension(typeName, extendee, field) {
return makeExtension(this, typeName, extendee, field);
},
};
}
+9
View File
@@ -0,0 +1,9 @@
import type { FieldInfo } from "../field.js";
/**
* Returns true if the field is set.
*/
export declare function isFieldSet(field: FieldInfo, target: Record<string, any>): boolean;
/**
* Resets the field, so that isFieldSet() will return false.
*/
export declare function clearField(field: FieldInfo, target: Record<string, any>): void;
+74
View File
@@ -0,0 +1,74 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { isScalarZeroValue, scalarZeroValue } from "./scalars.js";
/**
* Returns true if the field is set.
*/
export function isFieldSet(field, target) {
const localName = field.localName;
if (field.repeated) {
return target[localName].length > 0;
}
if (field.oneof) {
return target[field.oneof.localName].case === localName; // eslint-disable-line @typescript-eslint/no-unsafe-member-access
}
switch (field.kind) {
case "enum":
case "scalar":
if (field.opt || field.req) {
// explicit presence
return target[localName] !== undefined;
}
// implicit presence
if (field.kind == "enum") {
return target[localName] !== field.T.values[0].no;
}
return !isScalarZeroValue(field.T, target[localName]);
case "message":
return target[localName] !== undefined;
case "map":
return Object.keys(target[localName]).length > 0; // eslint-disable-line @typescript-eslint/no-unsafe-argument
}
}
/**
* Resets the field, so that isFieldSet() will return false.
*/
export function clearField(field, target) {
const localName = field.localName;
const implicitPresence = !field.opt && !field.req;
if (field.repeated) {
target[localName] = [];
}
else if (field.oneof) {
target[field.oneof.localName] = { case: undefined };
}
else {
switch (field.kind) {
case "map":
target[localName] = {};
break;
case "enum":
target[localName] = implicitPresence ? field.T.values[0].no : undefined;
break;
case "scalar":
target[localName] = implicitPresence
? scalarZeroValue(field.T, field.L)
: undefined;
break;
case "message":
target[localName] = undefined;
break;
}
}
}
+102
View File
@@ -0,0 +1,102 @@
import type { DescField, DescMessage, DescOneof } from "../descriptor-set.js";
type DescWkt = {
typeName: "google.protobuf.Any";
typeUrl: DescField;
value: DescField;
} | {
typeName: "google.protobuf.Timestamp";
seconds: DescField;
nanos: DescField;
} | {
typeName: "google.protobuf.Duration";
seconds: DescField;
nanos: DescField;
} | {
typeName: "google.protobuf.Struct";
fields: DescField & {
fieldKind: "map";
};
} | {
typeName: "google.protobuf.Value";
kind: DescOneof;
nullValue: DescField & {
fieldKind: "enum";
};
numberValue: DescField;
stringValue: DescField;
boolValue: DescField;
structValue: DescField & {
fieldKind: "message";
};
listValue: DescField & {
fieldKind: "message";
};
} | {
typeName: "google.protobuf.ListValue";
values: DescField & {
fieldKind: "message";
};
} | {
typeName: "google.protobuf.FieldMask";
paths: DescField;
} | {
typeName: "google.protobuf.DoubleValue";
value: DescField & {
fieldKind: "scalar";
};
} | {
typeName: "google.protobuf.FloatValue";
value: DescField & {
fieldKind: "scalar";
};
} | {
typeName: "google.protobuf.Int64Value";
value: DescField & {
fieldKind: "scalar";
};
} | {
typeName: "google.protobuf.UInt64Value";
value: DescField & {
fieldKind: "scalar";
};
} | {
typeName: "google.protobuf.Int32Value";
value: DescField & {
fieldKind: "scalar";
};
} | {
typeName: "google.protobuf.UInt32Value";
value: DescField & {
fieldKind: "scalar";
};
} | {
typeName: "google.protobuf.BoolValue";
value: DescField & {
fieldKind: "scalar";
};
} | {
typeName: "google.protobuf.StringValue";
value: DescField & {
fieldKind: "scalar";
};
} | {
typeName: "google.protobuf.BytesValue";
value: DescField & {
fieldKind: "scalar";
};
};
/**
* @deprecated please use reifyWkt from @bufbuild/protoplugin/ecmascript instead
*
* Reifies a given DescMessage into a more concrete object representing its
* respective well-known type. The returned object will contain properties
* representing the WKT's defined fields.
*
* Useful during code generation when immediate access to a particular field
* is needed without having to search the object's typename and DescField list.
*
* Returns undefined if the WKT cannot be completely constructed via the
* DescMessage.
*/
export declare function reifyWkt(message: DescMessage): DescWkt | undefined;
export {};
+168
View File
@@ -0,0 +1,168 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { ScalarType } from "../scalar.js";
/**
* @deprecated please use reifyWkt from @bufbuild/protoplugin/ecmascript instead
*
* Reifies a given DescMessage into a more concrete object representing its
* respective well-known type. The returned object will contain properties
* representing the WKT's defined fields.
*
* Useful during code generation when immediate access to a particular field
* is needed without having to search the object's typename and DescField list.
*
* Returns undefined if the WKT cannot be completely constructed via the
* DescMessage.
*/
export function reifyWkt(message) {
switch (message.typeName) {
case "google.protobuf.Any": {
const typeUrl = message.fields.find((f) => f.number == 1 &&
f.fieldKind == "scalar" &&
f.scalar === ScalarType.STRING);
const value = message.fields.find((f) => f.number == 2 &&
f.fieldKind == "scalar" &&
f.scalar === ScalarType.BYTES);
if (typeUrl && value) {
return {
typeName: message.typeName,
typeUrl,
value,
};
}
break;
}
case "google.protobuf.Timestamp": {
const seconds = message.fields.find((f) => f.number == 1 &&
f.fieldKind == "scalar" &&
f.scalar === ScalarType.INT64);
const nanos = message.fields.find((f) => f.number == 2 &&
f.fieldKind == "scalar" &&
f.scalar === ScalarType.INT32);
if (seconds && nanos) {
return {
typeName: message.typeName,
seconds,
nanos,
};
}
break;
}
case "google.protobuf.Duration": {
const seconds = message.fields.find((f) => f.number == 1 &&
f.fieldKind == "scalar" &&
f.scalar === ScalarType.INT64);
const nanos = message.fields.find((f) => f.number == 2 &&
f.fieldKind == "scalar" &&
f.scalar === ScalarType.INT32);
if (seconds && nanos) {
return {
typeName: message.typeName,
seconds,
nanos,
};
}
break;
}
case "google.protobuf.Struct": {
const fields = message.fields.find((f) => f.number == 1 && !f.repeated);
if ((fields === null || fields === void 0 ? void 0 : fields.fieldKind) !== "map" ||
fields.mapValue.kind !== "message" ||
fields.mapValue.message.typeName !== "google.protobuf.Value") {
break;
}
return { typeName: message.typeName, fields };
}
case "google.protobuf.Value": {
const kind = message.oneofs.find((o) => o.name === "kind");
const nullValue = message.fields.find((f) => f.number == 1 && f.oneof === kind);
if ((nullValue === null || nullValue === void 0 ? void 0 : nullValue.fieldKind) !== "enum" ||
nullValue.enum.typeName !== "google.protobuf.NullValue") {
return undefined;
}
const numberValue = message.fields.find((f) => f.number == 2 &&
f.fieldKind == "scalar" &&
f.scalar === ScalarType.DOUBLE &&
f.oneof === kind);
const stringValue = message.fields.find((f) => f.number == 3 &&
f.fieldKind == "scalar" &&
f.scalar === ScalarType.STRING &&
f.oneof === kind);
const boolValue = message.fields.find((f) => f.number == 4 &&
f.fieldKind == "scalar" &&
f.scalar === ScalarType.BOOL &&
f.oneof === kind);
const structValue = message.fields.find((f) => f.number == 5 && f.oneof === kind);
if ((structValue === null || structValue === void 0 ? void 0 : structValue.fieldKind) !== "message" ||
structValue.message.typeName !== "google.protobuf.Struct") {
return undefined;
}
const listValue = message.fields.find((f) => f.number == 6 && f.oneof === kind);
if ((listValue === null || listValue === void 0 ? void 0 : listValue.fieldKind) !== "message" ||
listValue.message.typeName !== "google.protobuf.ListValue") {
return undefined;
}
if (kind && numberValue && stringValue && boolValue) {
return {
typeName: message.typeName,
kind,
nullValue,
numberValue,
stringValue,
boolValue,
structValue,
listValue,
};
}
break;
}
case "google.protobuf.ListValue": {
const values = message.fields.find((f) => f.number == 1 && f.repeated);
if ((values === null || values === void 0 ? void 0 : values.fieldKind) != "message" ||
values.message.typeName !== "google.protobuf.Value") {
break;
}
return { typeName: message.typeName, values };
}
case "google.protobuf.FieldMask": {
const paths = message.fields.find((f) => f.number == 1 &&
f.fieldKind == "scalar" &&
f.scalar === ScalarType.STRING &&
f.repeated);
if (paths) {
return { typeName: message.typeName, paths };
}
break;
}
case "google.protobuf.DoubleValue":
case "google.protobuf.FloatValue":
case "google.protobuf.Int64Value":
case "google.protobuf.UInt64Value":
case "google.protobuf.Int32Value":
case "google.protobuf.UInt32Value":
case "google.protobuf.BoolValue":
case "google.protobuf.StringValue":
case "google.protobuf.BytesValue": {
const value = message.fields.find((f) => f.number == 1 && f.name == "value");
if (!value) {
break;
}
if (value.fieldKind !== "scalar") {
break;
}
return { typeName: message.typeName, value };
}
}
return undefined;
}
+18
View File
@@ -0,0 +1,18 @@
import { LongType, ScalarType } from "../scalar.js";
import type { ScalarValue } from "../scalar.js";
/**
* Returns true if both scalar values are equal.
*/
export declare function scalarEquals(type: ScalarType, a: string | boolean | number | bigint | Uint8Array | undefined, b: string | boolean | number | bigint | Uint8Array | undefined): boolean;
/**
* Returns the zero value for the given scalar type.
*/
export declare function scalarZeroValue<T extends ScalarType, L extends LongType>(type: T, longType: L): ScalarValue<T, L>;
/**
* Returns true for a zero-value. For example, an integer has the zero-value `0`,
* a boolean is `false`, a string is `""`, and bytes is an empty Uint8Array.
*
* In proto3, zero-values are not written to the wire, unless the field is
* optional or repeated.
*/
export declare function isScalarZeroValue(type: ScalarType, value: unknown): boolean;
+99
View File
@@ -0,0 +1,99 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { protoInt64 } from "../proto-int64.js";
import { LongType, ScalarType } from "../scalar.js";
/**
* Returns true if both scalar values are equal.
*/
export function scalarEquals(type, a, b) {
if (a === b) {
// This correctly matches equal values except BYTES and (possibly) 64-bit integers.
return true;
}
// Special case BYTES - we need to compare each byte individually
if (type == ScalarType.BYTES) {
if (!(a instanceof Uint8Array) || !(b instanceof Uint8Array)) {
return false;
}
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
// Special case 64-bit integers - we support number, string and bigint representation.
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
switch (type) {
case ScalarType.UINT64:
case ScalarType.FIXED64:
case ScalarType.INT64:
case ScalarType.SFIXED64:
case ScalarType.SINT64:
// Loose comparison will match between 0n, 0 and "0".
return a == b;
}
// Anything that hasn't been caught by strict comparison or special cased
// BYTES and 64-bit integers is not equal.
return false;
}
/**
* Returns the zero value for the given scalar type.
*/
export function scalarZeroValue(type, longType) {
switch (type) {
case ScalarType.BOOL:
return false;
case ScalarType.UINT64:
case ScalarType.FIXED64:
case ScalarType.INT64:
case ScalarType.SFIXED64:
case ScalarType.SINT64:
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison -- acceptable since it's covered by tests
return (longType == 0 ? protoInt64.zero : "0");
case ScalarType.DOUBLE:
case ScalarType.FLOAT:
return 0.0;
case ScalarType.BYTES:
return new Uint8Array(0);
case ScalarType.STRING:
return "";
default:
// Handles INT32, UINT32, SINT32, FIXED32, SFIXED32.
// We do not use individual cases to save a few bytes code size.
return 0;
}
}
/**
* Returns true for a zero-value. For example, an integer has the zero-value `0`,
* a boolean is `false`, a string is `""`, and bytes is an empty Uint8Array.
*
* In proto3, zero-values are not written to the wire, unless the field is
* optional or repeated.
*/
export function isScalarZeroValue(type, value) {
switch (type) {
case ScalarType.BOOL:
return value === false;
case ScalarType.STRING:
return value === "";
case ScalarType.BYTES:
return value instanceof Uint8Array && !value.byteLength;
default:
return value == 0; // Loose comparison matches 0n, 0 and "0"
}
}
+4
View File
@@ -0,0 +1,4 @@
import type { DescEnum } from "../descriptor-set.js";
import { ScalarType } from "../scalar.js";
export declare function parseTextFormatEnumValue(descEnum: DescEnum, value: string): number;
export declare function parseTextFormatScalarValue(type: ScalarType, value: string): number | boolean | string | bigint | Uint8Array;
+184
View File
@@ -0,0 +1,184 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { assert } from "./assert.js";
import { protoInt64 } from "../proto-int64.js";
import { ScalarType } from "../scalar.js";
export function parseTextFormatEnumValue(descEnum, value) {
const enumValue = descEnum.values.find((v) => v.name === value);
assert(enumValue, `cannot parse ${descEnum.name} default value: ${value}`);
return enumValue.number;
}
export function parseTextFormatScalarValue(type, value) {
switch (type) {
case ScalarType.STRING:
return value;
case ScalarType.BYTES: {
const u = unescapeBytesDefaultValue(value);
if (u === false) {
throw new Error(`cannot parse ${ScalarType[type]} default value: ${value}`);
}
return u;
}
case ScalarType.INT64:
case ScalarType.SFIXED64:
case ScalarType.SINT64:
return protoInt64.parse(value);
case ScalarType.UINT64:
case ScalarType.FIXED64:
return protoInt64.uParse(value);
case ScalarType.DOUBLE:
case ScalarType.FLOAT:
switch (value) {
case "inf":
return Number.POSITIVE_INFINITY;
case "-inf":
return Number.NEGATIVE_INFINITY;
case "nan":
return Number.NaN;
default:
return parseFloat(value);
}
case ScalarType.BOOL:
return value === "true";
case ScalarType.INT32:
case ScalarType.UINT32:
case ScalarType.SINT32:
case ScalarType.FIXED32:
case ScalarType.SFIXED32:
return parseInt(value, 10);
}
}
/**
* Parses a text-encoded default value (proto2) of a BYTES field.
*/
function unescapeBytesDefaultValue(str) {
const b = [];
const input = {
tail: str,
c: "",
next() {
if (this.tail.length == 0) {
return false;
}
this.c = this.tail[0];
this.tail = this.tail.substring(1);
return true;
},
take(n) {
if (this.tail.length >= n) {
const r = this.tail.substring(0, n);
this.tail = this.tail.substring(n);
return r;
}
return false;
},
};
while (input.next()) {
switch (input.c) {
case "\\":
if (input.next()) {
switch (input.c) {
case "\\":
b.push(input.c.charCodeAt(0));
break;
case "b":
b.push(0x08);
break;
case "f":
b.push(0x0c);
break;
case "n":
b.push(0x0a);
break;
case "r":
b.push(0x0d);
break;
case "t":
b.push(0x09);
break;
case "v":
b.push(0x0b);
break;
case "0":
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7": {
const s = input.c;
const t = input.take(2);
if (t === false) {
return false;
}
const n = parseInt(s + t, 8);
if (isNaN(n)) {
return false;
}
b.push(n);
break;
}
case "x": {
const s = input.c;
const t = input.take(2);
if (t === false) {
return false;
}
const n = parseInt(s + t, 16);
if (isNaN(n)) {
return false;
}
b.push(n);
break;
}
case "u": {
const s = input.c;
const t = input.take(4);
if (t === false) {
return false;
}
const n = parseInt(s + t, 16);
if (isNaN(n)) {
return false;
}
const chunk = new Uint8Array(4);
const view = new DataView(chunk.buffer);
view.setInt32(0, n, true);
b.push(chunk[0], chunk[1], chunk[2], chunk[3]);
break;
}
case "U": {
const s = input.c;
const t = input.take(8);
if (t === false) {
return false;
}
const tc = protoInt64.uEnc(s + t);
const chunk = new Uint8Array(8);
const view = new DataView(chunk.buffer);
view.setInt32(0, tc.lo, true);
view.setInt32(4, tc.hi, true);
b.push(chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7]);
break;
}
}
}
break;
default:
b.push(input.c.charCodeAt(0));
}
}
return new Uint8Array(b);
}
+2
View File
@@ -0,0 +1,2 @@
import type { Util } from "./util.js";
export declare function makeUtilCommon(): Omit<Util, "newFieldList" | "initFields">;
+244
View File
@@ -0,0 +1,244 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { setEnumType } from "./enum.js";
import { Message } from "../message.js";
import { scalarEquals } from "./scalars.js";
import { ScalarType } from "../scalar.js";
import { isMessage } from "../is-message.js";
/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-argument,no-case-declarations */
export function makeUtilCommon() {
return {
setEnumType,
initPartial(source, target) {
if (source === undefined) {
return;
}
const type = target.getType();
for (const member of type.fields.byMember()) {
const localName = member.localName, t = target, s = source;
if (s[localName] == null) {
// TODO if source is a Message instance, we should use isFieldSet() here to support future field presence
continue;
}
switch (member.kind) {
case "oneof":
const sk = s[localName].case;
if (sk === undefined) {
continue;
}
const sourceField = member.findField(sk);
let val = s[localName].value;
if (sourceField &&
sourceField.kind == "message" &&
!isMessage(val, sourceField.T)) {
val = new sourceField.T(val);
}
else if (sourceField &&
sourceField.kind === "scalar" &&
sourceField.T === ScalarType.BYTES) {
val = toU8Arr(val);
}
t[localName] = { case: sk, value: val };
break;
case "scalar":
case "enum":
let copy = s[localName];
if (member.T === ScalarType.BYTES) {
copy = member.repeated
? copy.map(toU8Arr)
: toU8Arr(copy);
}
t[localName] = copy;
break;
case "map":
switch (member.V.kind) {
case "scalar":
case "enum":
if (member.V.T === ScalarType.BYTES) {
for (const [k, v] of Object.entries(s[localName])) {
t[localName][k] = toU8Arr(v);
}
}
else {
Object.assign(t[localName], s[localName]);
}
break;
case "message":
const messageType = member.V.T;
for (const k of Object.keys(s[localName])) {
let val = s[localName][k];
if (!messageType.fieldWrapper) {
// We only take partial input for messages that are not a wrapper type.
// For those messages, we recursively normalize the partial input.
val = new messageType(val);
}
t[localName][k] = val;
}
break;
}
break;
case "message":
const mt = member.T;
if (member.repeated) {
t[localName] = s[localName].map((val) => isMessage(val, mt) ? val : new mt(val));
}
else {
const val = s[localName];
if (mt.fieldWrapper) {
if (
// We can't use BytesValue.typeName as that will create a circular import
mt.typeName === "google.protobuf.BytesValue") {
t[localName] = toU8Arr(val);
}
else {
t[localName] = val;
}
}
else {
t[localName] = isMessage(val, mt) ? val : new mt(val);
}
}
break;
}
}
},
// TODO use isFieldSet() here to support future field presence
equals(type, a, b) {
if (a === b) {
return true;
}
if (!a || !b) {
return false;
}
return type.fields.byMember().every((m) => {
const va = a[m.localName];
const vb = b[m.localName];
if (m.repeated) {
if (va.length !== vb.length) {
return false;
}
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- repeated fields are never "map"
switch (m.kind) {
case "message":
return va.every((a, i) => m.T.equals(a, vb[i]));
case "scalar":
return va.every((a, i) => scalarEquals(m.T, a, vb[i]));
case "enum":
return va.every((a, i) => scalarEquals(ScalarType.INT32, a, vb[i]));
}
throw new Error(`repeated cannot contain ${m.kind}`);
}
switch (m.kind) {
case "message":
let a = va;
let b = vb;
if (m.T.fieldWrapper) {
if (a !== undefined && !isMessage(a)) {
a = m.T.fieldWrapper.wrapField(a);
}
if (b !== undefined && !isMessage(b)) {
b = m.T.fieldWrapper.wrapField(b);
}
}
return m.T.equals(a, b);
case "enum":
return scalarEquals(ScalarType.INT32, va, vb);
case "scalar":
return scalarEquals(m.T, va, vb);
case "oneof":
if (va.case !== vb.case) {
return false;
}
const s = m.findField(va.case);
if (s === undefined) {
return true;
}
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- oneof fields are never "map"
switch (s.kind) {
case "message":
return s.T.equals(va.value, vb.value);
case "enum":
return scalarEquals(ScalarType.INT32, va.value, vb.value);
case "scalar":
return scalarEquals(s.T, va.value, vb.value);
}
throw new Error(`oneof cannot contain ${s.kind}`);
case "map":
const keys = Object.keys(va).concat(Object.keys(vb));
switch (m.V.kind) {
case "message":
const messageType = m.V.T;
return keys.every((k) => messageType.equals(va[k], vb[k]));
case "enum":
return keys.every((k) => scalarEquals(ScalarType.INT32, va[k], vb[k]));
case "scalar":
const scalarType = m.V.T;
return keys.every((k) => scalarEquals(scalarType, va[k], vb[k]));
}
break;
}
});
},
// TODO use isFieldSet() here to support future field presence
clone(message) {
const type = message.getType(), target = new type(), any = target;
for (const member of type.fields.byMember()) {
const source = message[member.localName];
let copy;
if (member.repeated) {
copy = source.map(cloneSingularField);
}
else if (member.kind == "map") {
copy = any[member.localName];
for (const [key, v] of Object.entries(source)) {
copy[key] = cloneSingularField(v);
}
}
else if (member.kind == "oneof") {
const f = member.findField(source.case);
copy = f
? { case: source.case, value: cloneSingularField(source.value) }
: { case: undefined };
}
else {
copy = cloneSingularField(source);
}
any[member.localName] = copy;
}
for (const uf of type.runtime.bin.listUnknownFields(message)) {
type.runtime.bin.onUnknownField(any, uf.no, uf.wireType, uf.data);
}
return target;
},
};
}
// clone a single field value - i.e. the element type of repeated fields, the value type of maps
function cloneSingularField(value) {
if (value === undefined) {
return value;
}
if (isMessage(value)) {
return value.clone();
}
if (value instanceof Uint8Array) {
const c = new Uint8Array(value.byteLength);
c.set(value);
return c;
}
return value;
}
// converts any ArrayLike<number> to Uint8Array if necessary.
function toU8Arr(input) {
return input instanceof Uint8Array ? input : new Uint8Array(input);
}
+38
View File
@@ -0,0 +1,38 @@
import type { FieldListSource } from "./field-list.js";
import type { FieldList } from "../field-list.js";
import type { EnumObject } from "./enum.js";
import type { Message, PartialMessage, PlainMessage } from "../message.js";
import type { MessageType } from "../message-type.js";
import type { EnumValueInfo } from "../enum.js";
/**
* Provides utilities used by generated code.
* All methods are internal and are not safe to use, they may break with a
* future release.
*/
export interface Util {
/**
* Create a field list
*/
newFieldList(fields: FieldListSource): FieldList;
/**
* Sets reflection information on a generated enum.
*/
setEnumType(enumObject: EnumObject, typeName: string, values: Omit<EnumValueInfo, "localName">[], opt?: {}): void;
/**
* Set default field values on the target message.
*/
initFields(target: Message): void;
/**
* Set specified field values on the target message, recursively.
*/
initPartial<T extends Message<T>>(source: PartialMessage<T> | undefined, target: T): void;
/**
* Compares two messages of the same type recursively.
* Will also return true if both messages are `undefined` or `null`.
*/
equals<T extends Message<T>>(type: MessageType<T>, a: T | PlainMessage<T> | undefined | null, b: T | PlainMessage<T> | undefined | null): boolean;
/**
* Create a deep copy.
*/
clone<T extends Message<T>>(message: T): T;
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
export {};