@bufbuild/protobuf
Advanced tools
| import type { DescField, DescMessage } from "../descriptors.js"; | ||
| import type { JsonObject, JsonValue } from "../json-value.js"; | ||
| import type { Struct } from "../wkt/gen/google/protobuf/struct_pb.js"; | ||
| /** | ||
| * Mapper between the local representation of a message field value | ||
| * and the message it represents. For most fields, the local value is the | ||
| * message itself. Types from google/protobuf/wrappers.proto are unwrapped | ||
| * to the wrapped scalar value when used in a singular field that is not | ||
| * part of a oneof group, and google.protobuf.Struct is represented with | ||
| * JsonObject when used in a field, except when used in | ||
| * google.protobuf.Value. | ||
| * | ||
| * @private | ||
| */ | ||
| export interface LocalMessageMapper { | ||
| /** | ||
| * Wrap a local value in the message it represents. For undefined - an | ||
| * unset field - a new message is created. Like the reflect API, wrapping | ||
| * an existing Struct field value creates a normalized copy, so that | ||
| * merging does not mutate the previous value in place. | ||
| */ | ||
| toMessage(local: unknown): Record<string, unknown>; | ||
| /** | ||
| * Convert a message to the local representation of the field value. | ||
| */ | ||
| toLocal(message: Record<string, unknown>): unknown; | ||
| } | ||
| /** | ||
| * Return the conversions between the local representation of the field | ||
| * value and the message it represents. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare function localMessageMapper(field: DescField & { | ||
| message: DescMessage; | ||
| }): LocalMessageMapper; | ||
| /** | ||
| * Convert the JsonValue representation of a google.protobuf.Struct to the | ||
| * message representation. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare function wktStructToReflect(json: JsonValue): Struct; | ||
| /** | ||
| * Convert a google.protobuf.Struct message to its JsonValue representation. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare function wktStructToLocal(val: Struct): JsonObject; |
| "use strict"; | ||
| // Copyright 2021-2026 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. | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.localMessageMapper = localMessageMapper; | ||
| exports.wktStructToReflect = wktStructToReflect; | ||
| exports.wktStructToLocal = wktStructToLocal; | ||
| const create_js_1 = require("../create.js"); | ||
| const guard_js_1 = require("./guard.js"); | ||
| const wrappers_js_1 = require("../wkt/wrappers.js"); | ||
| // google.protobuf.NullValue.NULL_VALUE; | ||
| const NULL_VALUE = 0; | ||
| /** | ||
| * Return the conversions between the local representation of the field | ||
| * value and the message it represents. | ||
| * | ||
| * @private | ||
| */ | ||
| function localMessageMapper(field) { | ||
| // google.protobuf.Struct fields are stored as JsonObject. | ||
| if (usesJsonRepresentation(field)) { | ||
| return { | ||
| toMessage: (local) => wktStructToReflect(local), | ||
| toLocal: (message) => wktStructToLocal(message), | ||
| }; | ||
| } | ||
| // Singular wrapper fields outside a oneof are unwrapped to the scalar value. | ||
| if (field.fieldKind == "message" && | ||
| !field.oneof && | ||
| (0, wrappers_js_1.isWrapperDesc)(field.message)) { | ||
| const wrapperDesc = field.message; | ||
| const valueLocalName = wrapperDesc.fields[0].localName; | ||
| return { | ||
| toMessage: (local) => { | ||
| const message = (0, create_js_1.create)(wrapperDesc); | ||
| if (local !== undefined) { | ||
| message[valueLocalName] = local; | ||
| } | ||
| return message; | ||
| }, | ||
| toLocal: (message) => message[valueLocalName], | ||
| }; | ||
| } | ||
| // For all other fields, the local value is the message itself. | ||
| const childDesc = field.message; | ||
| return { | ||
| toMessage: (local) => (local === undefined ? (0, create_js_1.create)(childDesc) : local), | ||
| toLocal: (message) => message, | ||
| }; | ||
| } | ||
| /** | ||
| * Returns true if values of this field are stored as JsonValue instead of | ||
| * a message: google.protobuf.Struct is represented with JsonObject when | ||
| * used in a field, except when used in google.protobuf.Value. | ||
| */ | ||
| function usesJsonRepresentation(field) { | ||
| return (field.message.typeName == "google.protobuf.Struct" && | ||
| field.parent.typeName != "google.protobuf.Value"); | ||
| } | ||
| /** | ||
| * Convert the JsonValue representation of a google.protobuf.Struct to the | ||
| * message representation. | ||
| * | ||
| * @private | ||
| */ | ||
| function wktStructToReflect(json) { | ||
| const struct = { | ||
| $typeName: "google.protobuf.Struct", | ||
| fields: {}, | ||
| }; | ||
| if ((0, guard_js_1.isObject)(json)) { | ||
| for (const k of Object.keys(json)) { | ||
| struct.fields[k] = wktValueToReflect(json[k]); | ||
| } | ||
| } | ||
| return struct; | ||
| } | ||
| /** | ||
| * Convert a google.protobuf.Struct message to its JsonValue representation. | ||
| * | ||
| * @private | ||
| */ | ||
| function wktStructToLocal(val) { | ||
| const json = {}; | ||
| for (const k of Object.keys(val.fields)) { | ||
| json[k] = wktValueToLocal(val.fields[k]); | ||
| } | ||
| return json; | ||
| } | ||
| function wktValueToLocal(val) { | ||
| switch (val.kind.case) { | ||
| case "structValue": | ||
| return wktStructToLocal(val.kind.value); | ||
| case "listValue": | ||
| return val.kind.value.values.map(wktValueToLocal); | ||
| case "nullValue": | ||
| case undefined: | ||
| return null; | ||
| default: | ||
| return val.kind.value; | ||
| } | ||
| } | ||
| function wktValueToReflect(json) { | ||
| const value = { | ||
| $typeName: "google.protobuf.Value", | ||
| kind: { case: undefined }, | ||
| }; | ||
| switch (typeof json) { | ||
| case "number": | ||
| value.kind = { case: "numberValue", value: json }; | ||
| break; | ||
| case "string": | ||
| value.kind = { case: "stringValue", value: json }; | ||
| break; | ||
| case "boolean": | ||
| value.kind = { case: "boolValue", value: json }; | ||
| break; | ||
| case "object": | ||
| if (json === null) { | ||
| value.kind = { case: "nullValue", value: NULL_VALUE }; | ||
| } | ||
| else if (Array.isArray(json)) { | ||
| const listValue = { | ||
| $typeName: "google.protobuf.ListValue", | ||
| values: [], | ||
| }; | ||
| if (Array.isArray(json)) { | ||
| for (const e of json) { | ||
| listValue.values.push(wktValueToReflect(e)); | ||
| } | ||
| } | ||
| value.kind = { | ||
| case: "listValue", | ||
| value: listValue, | ||
| }; | ||
| } | ||
| else { | ||
| value.kind = { | ||
| case: "structValue", | ||
| value: wktStructToReflect(json), | ||
| }; | ||
| } | ||
| break; | ||
| } | ||
| return value; | ||
| } |
| /** | ||
| * Minimum google.protobuf.Timestamp in milliseconds (inclusive). | ||
| * Only enforced in ProtoJSON. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare const timestampMsMin: number; | ||
| /** | ||
| * Maximum google.protobuf.Timestamp in milliseconds (inclusive). | ||
| * Only enforced in ProtoJSON. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare const timestampMsMax: number; | ||
| /** | ||
| * Minimum google.protobuf.Duration in seconds. | ||
| * Only enforced in ProtoJSON. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare const durationSecondsMin = -315576000000; | ||
| /** | ||
| * Maximum google.protobuf.Duration in seconds. | ||
| * Only enforced in ProtoJSON. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare const durationSecondsMax = 315576000000; |
| "use strict"; | ||
| // Copyright 2021-2026 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. | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.durationSecondsMax = exports.durationSecondsMin = exports.timestampMsMax = exports.timestampMsMin = void 0; | ||
| /** | ||
| * Minimum google.protobuf.Timestamp in milliseconds (inclusive). | ||
| * Only enforced in ProtoJSON. | ||
| * | ||
| * @private | ||
| */ | ||
| exports.timestampMsMin = Date.parse("0001-01-01T00:00:00Z"); | ||
| /** | ||
| * Maximum google.protobuf.Timestamp in milliseconds (inclusive). | ||
| * Only enforced in ProtoJSON. | ||
| * | ||
| * @private | ||
| */ | ||
| exports.timestampMsMax = Date.parse("9999-12-31T23:59:59Z"); | ||
| /** | ||
| * Minimum google.protobuf.Duration in seconds. | ||
| * Only enforced in ProtoJSON. | ||
| * | ||
| * @private | ||
| */ | ||
| exports.durationSecondsMin = -315576000000; | ||
| /** | ||
| * Maximum google.protobuf.Duration in seconds. | ||
| * Only enforced in ProtoJSON. | ||
| * | ||
| * @private | ||
| */ | ||
| exports.durationSecondsMax = 315576000000; |
| import type { DescField, DescMessage } from "../descriptors.js"; | ||
| import type { JsonObject, JsonValue } from "../json-value.js"; | ||
| import type { Struct } from "../wkt/gen/google/protobuf/struct_pb.js"; | ||
| /** | ||
| * Mapper between the local representation of a message field value | ||
| * and the message it represents. For most fields, the local value is the | ||
| * message itself. Types from google/protobuf/wrappers.proto are unwrapped | ||
| * to the wrapped scalar value when used in a singular field that is not | ||
| * part of a oneof group, and google.protobuf.Struct is represented with | ||
| * JsonObject when used in a field, except when used in | ||
| * google.protobuf.Value. | ||
| * | ||
| * @private | ||
| */ | ||
| export interface LocalMessageMapper { | ||
| /** | ||
| * Wrap a local value in the message it represents. For undefined - an | ||
| * unset field - a new message is created. Like the reflect API, wrapping | ||
| * an existing Struct field value creates a normalized copy, so that | ||
| * merging does not mutate the previous value in place. | ||
| */ | ||
| toMessage(local: unknown): Record<string, unknown>; | ||
| /** | ||
| * Convert a message to the local representation of the field value. | ||
| */ | ||
| toLocal(message: Record<string, unknown>): unknown; | ||
| } | ||
| /** | ||
| * Return the conversions between the local representation of the field | ||
| * value and the message it represents. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare function localMessageMapper(field: DescField & { | ||
| message: DescMessage; | ||
| }): LocalMessageMapper; | ||
| /** | ||
| * Convert the JsonValue representation of a google.protobuf.Struct to the | ||
| * message representation. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare function wktStructToReflect(json: JsonValue): Struct; | ||
| /** | ||
| * Convert a google.protobuf.Struct message to its JsonValue representation. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare function wktStructToLocal(val: Struct): JsonObject; |
| // Copyright 2021-2026 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 { create } from "../create.js"; | ||
| import { isObject } from "./guard.js"; | ||
| import { isWrapperDesc } from "../wkt/wrappers.js"; | ||
| // google.protobuf.NullValue.NULL_VALUE; | ||
| const NULL_VALUE = 0; | ||
| /** | ||
| * Return the conversions between the local representation of the field | ||
| * value and the message it represents. | ||
| * | ||
| * @private | ||
| */ | ||
| export function localMessageMapper(field) { | ||
| // google.protobuf.Struct fields are stored as JsonObject. | ||
| if (usesJsonRepresentation(field)) { | ||
| return { | ||
| toMessage: (local) => wktStructToReflect(local), | ||
| toLocal: (message) => wktStructToLocal(message), | ||
| }; | ||
| } | ||
| // Singular wrapper fields outside a oneof are unwrapped to the scalar value. | ||
| if (field.fieldKind == "message" && | ||
| !field.oneof && | ||
| isWrapperDesc(field.message)) { | ||
| const wrapperDesc = field.message; | ||
| const valueLocalName = wrapperDesc.fields[0].localName; | ||
| return { | ||
| toMessage: (local) => { | ||
| const message = create(wrapperDesc); | ||
| if (local !== undefined) { | ||
| message[valueLocalName] = local; | ||
| } | ||
| return message; | ||
| }, | ||
| toLocal: (message) => message[valueLocalName], | ||
| }; | ||
| } | ||
| // For all other fields, the local value is the message itself. | ||
| const childDesc = field.message; | ||
| return { | ||
| toMessage: (local) => (local === undefined ? create(childDesc) : local), | ||
| toLocal: (message) => message, | ||
| }; | ||
| } | ||
| /** | ||
| * Returns true if values of this field are stored as JsonValue instead of | ||
| * a message: google.protobuf.Struct is represented with JsonObject when | ||
| * used in a field, except when used in google.protobuf.Value. | ||
| */ | ||
| function usesJsonRepresentation(field) { | ||
| return (field.message.typeName == "google.protobuf.Struct" && | ||
| field.parent.typeName != "google.protobuf.Value"); | ||
| } | ||
| /** | ||
| * Convert the JsonValue representation of a google.protobuf.Struct to the | ||
| * message representation. | ||
| * | ||
| * @private | ||
| */ | ||
| export function wktStructToReflect(json) { | ||
| const struct = { | ||
| $typeName: "google.protobuf.Struct", | ||
| fields: {}, | ||
| }; | ||
| if (isObject(json)) { | ||
| for (const k of Object.keys(json)) { | ||
| struct.fields[k] = wktValueToReflect(json[k]); | ||
| } | ||
| } | ||
| return struct; | ||
| } | ||
| /** | ||
| * Convert a google.protobuf.Struct message to its JsonValue representation. | ||
| * | ||
| * @private | ||
| */ | ||
| export function wktStructToLocal(val) { | ||
| const json = {}; | ||
| for (const k of Object.keys(val.fields)) { | ||
| json[k] = wktValueToLocal(val.fields[k]); | ||
| } | ||
| return json; | ||
| } | ||
| function wktValueToLocal(val) { | ||
| switch (val.kind.case) { | ||
| case "structValue": | ||
| return wktStructToLocal(val.kind.value); | ||
| case "listValue": | ||
| return val.kind.value.values.map(wktValueToLocal); | ||
| case "nullValue": | ||
| case undefined: | ||
| return null; | ||
| default: | ||
| return val.kind.value; | ||
| } | ||
| } | ||
| function wktValueToReflect(json) { | ||
| const value = { | ||
| $typeName: "google.protobuf.Value", | ||
| kind: { case: undefined }, | ||
| }; | ||
| switch (typeof json) { | ||
| case "number": | ||
| value.kind = { case: "numberValue", value: json }; | ||
| break; | ||
| case "string": | ||
| value.kind = { case: "stringValue", value: json }; | ||
| break; | ||
| case "boolean": | ||
| value.kind = { case: "boolValue", value: json }; | ||
| break; | ||
| case "object": | ||
| if (json === null) { | ||
| value.kind = { case: "nullValue", value: NULL_VALUE }; | ||
| } | ||
| else if (Array.isArray(json)) { | ||
| const listValue = { | ||
| $typeName: "google.protobuf.ListValue", | ||
| values: [], | ||
| }; | ||
| if (Array.isArray(json)) { | ||
| for (const e of json) { | ||
| listValue.values.push(wktValueToReflect(e)); | ||
| } | ||
| } | ||
| value.kind = { | ||
| case: "listValue", | ||
| value: listValue, | ||
| }; | ||
| } | ||
| else { | ||
| value.kind = { | ||
| case: "structValue", | ||
| value: wktStructToReflect(json), | ||
| }; | ||
| } | ||
| break; | ||
| } | ||
| return value; | ||
| } |
| /** | ||
| * Minimum google.protobuf.Timestamp in milliseconds (inclusive). | ||
| * Only enforced in ProtoJSON. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare const timestampMsMin: number; | ||
| /** | ||
| * Maximum google.protobuf.Timestamp in milliseconds (inclusive). | ||
| * Only enforced in ProtoJSON. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare const timestampMsMax: number; | ||
| /** | ||
| * Minimum google.protobuf.Duration in seconds. | ||
| * Only enforced in ProtoJSON. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare const durationSecondsMin = -315576000000; | ||
| /** | ||
| * Maximum google.protobuf.Duration in seconds. | ||
| * Only enforced in ProtoJSON. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare const durationSecondsMax = 315576000000; |
| // Copyright 2021-2026 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. | ||
| /** | ||
| * Minimum google.protobuf.Timestamp in milliseconds (inclusive). | ||
| * Only enforced in ProtoJSON. | ||
| * | ||
| * @private | ||
| */ | ||
| export const timestampMsMin = /*@__PURE__*/ Date.parse("0001-01-01T00:00:00Z"); | ||
| /** | ||
| * Maximum google.protobuf.Timestamp in milliseconds (inclusive). | ||
| * Only enforced in ProtoJSON. | ||
| * | ||
| * @private | ||
| */ | ||
| export const timestampMsMax = /*@__PURE__*/ Date.parse("9999-12-31T23:59:59Z"); | ||
| /** | ||
| * Minimum google.protobuf.Duration in seconds. | ||
| * Only enforced in ProtoJSON. | ||
| * | ||
| * @private | ||
| */ | ||
| export const durationSecondsMin = -315576000000; | ||
| /** | ||
| * Maximum google.protobuf.Duration in seconds. | ||
| * Only enforced in ProtoJSON. | ||
| * | ||
| * @private | ||
| */ | ||
| export const durationSecondsMax = 315576000000; |
+199
-162
@@ -21,3 +21,2 @@ "use strict"; | ||
| const guard_js_1 = require("./reflect/guard.js"); | ||
| const unsafe_js_1 = require("./reflect/unsafe.js"); | ||
| const wrappers_js_1 = require("./wkt/wrappers.js"); | ||
@@ -40,77 +39,197 @@ // bootstrap-inject google.protobuf.Edition.EDITION_PROTO3: const $name = $number; | ||
| } | ||
| const message = createZeroMessage(schema); | ||
| if (init !== undefined) { | ||
| initMessage(schema, message, init); | ||
| return compiledCreate(schema)(init); | ||
| } | ||
| const compiledCreates = new WeakMap(); | ||
| /** | ||
| * Return the compiled create function for a message, compiling it on first use. */ | ||
| function compiledCreate(desc) { | ||
| let compiled = compiledCreates.get(desc); | ||
| if (compiled === undefined) { | ||
| compiled = compileCreate(desc); | ||
| compiledCreates.set(desc, compiled); | ||
| } | ||
| return message; | ||
| return compiled; | ||
| } | ||
| /** Singular field: scalar, enum, or message. */ | ||
| const INIT_SINGULAR = 0; | ||
| /** List field: a zero message has a fresh empty array. */ | ||
| const INIT_LIST = 1; | ||
| /** Map field: a zero message has a fresh empty object. */ | ||
| const INIT_MAP = 2; | ||
| /** Oneof group: the ADT is always stored, cases convert by case name. */ | ||
| const INIT_ONEOF = 3; | ||
| /* Compile the create function for this message type. */ | ||
| function compileCreate(desc) { | ||
| const typeName = desc.typeName; | ||
| const { properties, prototype } = compileInitMessage(desc); | ||
| return (init) => { | ||
| let message; | ||
| if (prototype !== undefined) { | ||
| message = Object.create(prototype); | ||
| message.$typeName = typeName; | ||
| } | ||
| else { | ||
| message = { $typeName: typeName }; | ||
| } | ||
| for (let i = 0; i < properties.length; i++) { | ||
| const property = properties[i]; | ||
| const name = property.name; | ||
| const initValue = init === null || init === void 0 ? void 0 : init[name]; | ||
| switch (property.kind) { | ||
| case INIT_SINGULAR: | ||
| if (initValue != null) { | ||
| message[name] = | ||
| property.convert !== undefined | ||
| ? property.convert(initValue) | ||
| : initValue; | ||
| } | ||
| else if (property.constant !== undefined) { | ||
| message[name] = property.constant; | ||
| } | ||
| break; | ||
| case INIT_LIST: | ||
| message[name] = | ||
| property.convert !== undefined && Array.isArray(initValue) | ||
| ? initValue.map(property.convert) | ||
| : (initValue !== null && initValue !== void 0 ? initValue : []); | ||
| break; | ||
| case INIT_MAP: | ||
| // Object.create(null) would be desirable for the fresh map, but is | ||
| // unsupported by React: | ||
| // https://react.dev/reference/react/use-server#serializable-parameters-and-return-values | ||
| if (property.convert === undefined || !(0, guard_js_1.isObject)(initValue)) { | ||
| message[name] = initValue !== null && initValue !== void 0 ? initValue : {}; | ||
| } | ||
| else { | ||
| const converted = {}; | ||
| const keys = Object.keys(initValue); | ||
| for (let k = 0; k < keys.length; k++) { | ||
| converted[keys[k]] = property.convert(initValue[keys[k]]); | ||
| } | ||
| message[name] = converted; | ||
| } | ||
| break; | ||
| case INIT_ONEOF: { | ||
| const oneofValue = initValue; | ||
| if ((oneofValue === null || oneofValue === void 0 ? void 0 : oneofValue.case) != null) { | ||
| const convert = property.convert.get(oneofValue.case); | ||
| if (convert !== undefined) { | ||
| message[name] = { | ||
| case: oneofValue.case, | ||
| value: convert(oneofValue.value), | ||
| }; | ||
| break; | ||
| } | ||
| } | ||
| message[name] = { case: undefined }; | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| return message; | ||
| }; | ||
| } | ||
| /** | ||
| * Sets field values from a MessageInitShape on a zero message. | ||
| * Classify every member once, so that creating a message is a walk over a | ||
| * compact list instead of a walk over the descriptor. | ||
| */ | ||
| function initMessage(messageDesc, message, init) { | ||
| for (const member of messageDesc.members) { | ||
| let value = init[member.localName]; | ||
| if (value == null) { | ||
| // intentionally ignore undefined and null | ||
| function compileInitMessage(desc) { | ||
| var _a, _b; | ||
| const properties = []; | ||
| const prototype = {}; | ||
| const usePrototype = needsPrototypeChain(desc); | ||
| for (const member of desc.members) { | ||
| const name = member.localName; | ||
| if (member.kind == "oneof") { | ||
| properties.push({ | ||
| name, | ||
| kind: INIT_ONEOF, | ||
| constant: undefined, | ||
| convert: compileConvertOneof(member), | ||
| }); | ||
| continue; | ||
| } | ||
| let field; | ||
| if (member.kind == "oneof") { | ||
| const oneofField = (0, unsafe_js_1.unsafeOneofCase)(init, member); | ||
| if (!oneofField) { | ||
| continue; | ||
| switch (member.fieldKind) { | ||
| case "message": { | ||
| // Singular message fields are absent from a zero message. | ||
| properties.push({ | ||
| name, | ||
| kind: INIT_SINGULAR, | ||
| constant: undefined, | ||
| convert: compileConvertMessage(member), | ||
| }); | ||
| break; | ||
| } | ||
| field = oneofField; | ||
| value = (0, unsafe_js_1.unsafeGet)(init, oneofField); | ||
| } | ||
| else { | ||
| field = member; | ||
| } | ||
| switch (field.fieldKind) { | ||
| case "message": | ||
| value = toMessage(field, value); | ||
| case "list": { | ||
| properties.push({ | ||
| name, | ||
| kind: INIT_LIST, | ||
| constant: undefined, | ||
| convert: member.listKind == "message" | ||
| ? ((_a = compileConvertMessage(member)) !== null && _a !== void 0 ? _a : ((value) => value)) | ||
| : member.scalar == descriptors_js_1.ScalarType.BYTES | ||
| ? toU8Arr | ||
| : undefined, | ||
| }); | ||
| break; | ||
| case "scalar": | ||
| value = initScalar(field, value); | ||
| } | ||
| case "map": { | ||
| properties.push({ | ||
| name, | ||
| kind: INIT_MAP, | ||
| constant: undefined, | ||
| convert: member.mapKind == "message" | ||
| ? ((_b = compileConvertMessage(member)) !== null && _b !== void 0 ? _b : ((value) => value)) | ||
| : member.scalar == descriptors_js_1.ScalarType.BYTES | ||
| ? toU8Arr | ||
| : undefined, | ||
| }); | ||
| break; | ||
| case "list": | ||
| value = initList(field, value); | ||
| } | ||
| default: { | ||
| const zeroValue = createZeroValue(member); | ||
| properties.push({ | ||
| name, | ||
| kind: INIT_SINGULAR, | ||
| constant: member.presence == IMPLICIT ? zeroValue : undefined, | ||
| convert: member.fieldKind == "scalar" && member.scalar == descriptors_js_1.ScalarType.BYTES | ||
| ? toU8Arr | ||
| : undefined, | ||
| }); | ||
| if (usePrototype) { | ||
| prototype[name] = zeroValue; | ||
| } | ||
| break; | ||
| case "map": | ||
| value = initMap(field, value); | ||
| break; | ||
| } | ||
| } | ||
| (0, unsafe_js_1.unsafeSet)(message, field, value); | ||
| } | ||
| return message; | ||
| return { | ||
| properties, | ||
| prototype: usePrototype ? prototype : undefined, | ||
| }; | ||
| } | ||
| function initScalar(field, value) { | ||
| if (field.scalar == descriptors_js_1.ScalarType.BYTES) { | ||
| return toU8Arr(value); | ||
| } | ||
| return value; | ||
| } | ||
| function initMap(field, value) { | ||
| if ((0, guard_js_1.isObject)(value)) { | ||
| if (field.scalar == descriptors_js_1.ScalarType.BYTES) { | ||
| return convertObjectValues(value, toU8Arr); | ||
| /** | ||
| * Compile the conversion of each case of a oneof group, keyed by case name. | ||
| */ | ||
| function compileConvertOneof(oneof) { | ||
| const converters = new Map(); | ||
| for (const field of oneof.fields) { | ||
| let convert; | ||
| if (field.fieldKind == "message") { | ||
| convert = compileConvertMessage(field); | ||
| } | ||
| if (field.mapKind == "message") { | ||
| return convertObjectValues(value, (val) => toMessage(field, val)); | ||
| else if (field.fieldKind == "scalar" && | ||
| field.scalar == descriptors_js_1.ScalarType.BYTES) { | ||
| convert = toU8Arr; | ||
| } | ||
| converters.set(field.localName, convert !== null && convert !== void 0 ? convert : ((value) => value)); | ||
| } | ||
| return value; | ||
| return converters; | ||
| } | ||
| function initList(field, value) { | ||
| if (Array.isArray(value)) { | ||
| if (field.scalar == descriptors_js_1.ScalarType.BYTES) { | ||
| return value.map(toU8Arr); | ||
| } | ||
| if (field.listKind == "message") { | ||
| return value.map((item) => toMessage(field, item)); | ||
| } | ||
| } | ||
| return value; | ||
| } | ||
| function toMessage(field, value) { | ||
| /** | ||
| * Compile the conversion of an init value for a message field, a message | ||
| * list item, or a message map value. Returns undefined if values are used | ||
| * as-is. | ||
| */ | ||
| function compileConvertMessage(field) { | ||
| if (field.fieldKind == "message" && | ||
@@ -121,16 +240,23 @@ !field.oneof && | ||
| // a singular field that is not part of a oneof group. | ||
| return initScalar(field.message.fields[0], value); | ||
| return field.message.fields[0].scalar == descriptors_js_1.ScalarType.BYTES | ||
| ? toU8Arr | ||
| : undefined; | ||
| } | ||
| if ((0, guard_js_1.isObject)(value)) { | ||
| if (field.message.typeName == "google.protobuf.Struct" && | ||
| field.parent.typeName !== "google.protobuf.Value") { | ||
| // google.protobuf.Struct is represented with JsonObject when used in a | ||
| // field, except when used in google.protobuf.Value. | ||
| if (field.message.typeName == "google.protobuf.Struct" && | ||
| field.parent.typeName !== "google.protobuf.Value") { | ||
| // google.protobuf.Struct is represented with JsonObject when used in a | ||
| // field, except when used in google.protobuf.Value. | ||
| return undefined; | ||
| } | ||
| const messageDesc = field.message; | ||
| // Resolved on first use, not here: the message type can be this very field's | ||
| // parent, whose create function is still being compiled. | ||
| let compiled; | ||
| return (value) => { | ||
| if (!(0, guard_js_1.isObject)(value) || (0, is_message_js_1.isMessage)(value, messageDesc)) { | ||
| return value; | ||
| } | ||
| if (!(0, is_message_js_1.isMessage)(value, field.message)) { | ||
| return create(field.message, value); | ||
| } | ||
| } | ||
| return value; | ||
| compiled !== null && compiled !== void 0 ? compiled : (compiled = compiledCreate(messageDesc)); | ||
| return compiled(value); | ||
| }; | ||
| } | ||
@@ -141,80 +267,3 @@ // converts any ArrayLike<number> to Uint8Array if necessary. | ||
| } | ||
| function convertObjectValues(obj, fn) { | ||
| const ret = {}; | ||
| for (const entry of Object.entries(obj)) { | ||
| ret[entry[0]] = fn(entry[1]); | ||
| } | ||
| return ret; | ||
| } | ||
| const tokenZeroMessageField = Symbol(); | ||
| const messagePrototypes = new WeakMap(); | ||
| /** | ||
| * Create a zero message. | ||
| */ | ||
| function createZeroMessage(desc) { | ||
| let msg; | ||
| if (!needsPrototypeChain(desc)) { | ||
| msg = { | ||
| $typeName: desc.typeName, | ||
| }; | ||
| for (const member of desc.members) { | ||
| if (member.kind == "oneof" || member.presence == IMPLICIT) { | ||
| msg[member.localName] = createZeroField(member); | ||
| } | ||
| } | ||
| } | ||
| else { | ||
| // Support default values and track presence via the prototype chain | ||
| const cached = messagePrototypes.get(desc); | ||
| let prototype; | ||
| let members; | ||
| if (cached) { | ||
| ({ prototype, members } = cached); | ||
| } | ||
| else { | ||
| prototype = {}; | ||
| members = new Set(); | ||
| for (const member of desc.members) { | ||
| if (member.kind == "oneof") { | ||
| // we can only put immutable values on the prototype, | ||
| // oneof ADTs are mutable | ||
| continue; | ||
| } | ||
| if (member.fieldKind != "scalar" && member.fieldKind != "enum") { | ||
| // only scalar and enum values are immutable, map, list, and message | ||
| // are not | ||
| continue; | ||
| } | ||
| if (member.presence == IMPLICIT) { | ||
| // implicit presence tracks field presence by zero values - e.g. 0, false, "", are unset, 1, true, "x" are set. | ||
| // message, map, list fields are mutable, and also have IMPLICIT presence. | ||
| continue; | ||
| } | ||
| members.add(member); | ||
| prototype[member.localName] = createZeroField(member); | ||
| } | ||
| messagePrototypes.set(desc, { prototype, members }); | ||
| } | ||
| msg = Object.create(prototype); | ||
| msg.$typeName = desc.typeName; | ||
| for (const member of desc.members) { | ||
| if (members.has(member)) { | ||
| continue; | ||
| } | ||
| if (member.kind == "field") { | ||
| if (member.fieldKind == "message") { | ||
| continue; | ||
| } | ||
| if (member.fieldKind == "scalar" || member.fieldKind == "enum") { | ||
| if (member.presence != IMPLICIT) { | ||
| continue; | ||
| } | ||
| } | ||
| } | ||
| msg[member.localName] = createZeroField(member); | ||
| } | ||
| } | ||
| return msg; | ||
| } | ||
| /** | ||
| * Do we need the prototype chain to track field presence? | ||
@@ -238,18 +287,6 @@ */ | ||
| /** | ||
| * Returns a zero value for oneof groups, and for every field kind except | ||
| * messages. Scalar and enum fields can have default values. | ||
| * Returns the zero value for a scalar or enum field. Scalar and enum fields | ||
| * can have default values. | ||
| */ | ||
| function createZeroField(field) { | ||
| if (field.kind == "oneof") { | ||
| return { case: undefined }; | ||
| } | ||
| if (field.fieldKind == "list") { | ||
| return []; | ||
| } | ||
| if (field.fieldKind == "map") { | ||
| return {}; // Object.create(null) would be desirable here, but is unsupported by react https://react.dev/reference/react/use-server#serializable-parameters-and-return-values | ||
| } | ||
| if (field.fieldKind == "message") { | ||
| return tokenZeroMessageField; | ||
| } | ||
| function createZeroValue(field) { | ||
| const defaultValue = field.getDefaultValue(); | ||
@@ -256,0 +293,0 @@ if (defaultValue !== undefined) { |
+321
-153
@@ -22,3 +22,6 @@ "use strict"; | ||
| const scalar_js_1 = require("./reflect/scalar.js"); | ||
| const reflect_js_1 = require("./reflect/reflect.js"); | ||
| const error_js_1 = require("./reflect/error.js"); | ||
| const unsafe_js_1 = require("./reflect/unsafe.js"); | ||
| const message_js_1 = require("./reflect/message.js"); | ||
| const create_js_1 = require("./create.js"); | ||
| const binary_encoding_js_1 = require("./wire/binary-encoding.js"); | ||
@@ -36,5 +39,5 @@ const varint_js_1 = require("./wire/varint.js"); | ||
| function fromBinary(schema, bytes, options) { | ||
| const msg = (0, reflect_js_1.reflect)(schema, undefined, false); | ||
| readMessage(msg, new binary_encoding_js_1.BinaryReader(bytes), makeReadContext(options), false, bytes.byteLength); | ||
| return msg.message; | ||
| const message = (0, create_js_1.create)(schema); | ||
| compiledReader(schema).read(message, new binary_encoding_js_1.BinaryReader(bytes), makeReadContext(options), bytes.byteLength); | ||
| return message; | ||
| } | ||
@@ -51,48 +54,103 @@ /** | ||
| function mergeFromBinary(schema, target, bytes, options) { | ||
| readMessage((0, reflect_js_1.reflect)(schema, target, false), new binary_encoding_js_1.BinaryReader(bytes), makeReadContext(options), false, bytes.byteLength); | ||
| if (target.$typeName !== schema.typeName && | ||
| schema.fields.length > 0) { | ||
| throw new error_js_1.FieldError(schema.fields[0], `cannot use ${schema.fields[0]} with message ${target.$typeName}`, "ForeignFieldError"); | ||
| } | ||
| compiledReader(schema).read(target, new binary_encoding_js_1.BinaryReader(bytes), makeReadContext(options), bytes.byteLength); | ||
| return target; | ||
| } | ||
| const compiledReaders = new WeakMap(); | ||
| /** | ||
| * If `delimited` is false, read the length given in `lengthOrDelimitedFieldNo`. | ||
| * | ||
| * If `delimited` is true, read until an EndGroup tag. `lengthOrDelimitedFieldNo` | ||
| * is the expected field number. | ||
| * | ||
| * @private | ||
| * Return the compiled decoder for a message, compiling it on first use. | ||
| */ | ||
| function readMessage(message, reader, ctx, delimited, lengthOrDelimitedFieldNo) { | ||
| var _a; | ||
| if (++ctx.depth > ctx.recursionLimit) { | ||
| throw new Error(`cannot decode ${message.desc} from binary: maximum recursion depth of ${ctx.recursionLimit} reached`); | ||
| function compiledReader(desc) { | ||
| let compiled = compiledReaders.get(desc); | ||
| if (compiled === undefined) { | ||
| compiled = compileMessage(desc); | ||
| } | ||
| const end = delimited ? reader.len : reader.pos + lengthOrDelimitedFieldNo; | ||
| let fieldNo; | ||
| let wireType; | ||
| const unknownFields = (_a = message.getUnknown()) !== null && _a !== void 0 ? _a : []; | ||
| while (reader.pos < end) { | ||
| [fieldNo, wireType] = reader.tag(); | ||
| if (delimited && wireType == binary_encoding_js_1.WireType.EndGroup) { | ||
| break; | ||
| return compiled; | ||
| } | ||
| function compileMessage(desc) { | ||
| const descString = String(desc); | ||
| const fieldReaders = new Map(); | ||
| const compiled = { | ||
| read: compileMessageReader(descString, fieldReaders), | ||
| readGroup: compileGroupReader(descString, fieldReaders), | ||
| }; | ||
| // Register before compiling fields, so that recursive message types | ||
| // resolve to this instance instead of compiling endlessly. | ||
| compiledReaders.set(desc, compiled); | ||
| for (const field of desc.fields) { | ||
| fieldReaders.set(field.number, compileFieldReader(field)); | ||
| } | ||
| return compiled; | ||
| } | ||
| /** | ||
| * Create a decoder for a length-prefixed message body, dispatching wire | ||
| * records to the compiled field decoders by field number. | ||
| */ | ||
| function compileMessageReader(descString, fieldReaders) { | ||
| return (message, reader, ctx, length) => { | ||
| var _a; | ||
| if (++ctx.depth > ctx.recursionLimit) { | ||
| throw new Error(`cannot decode ${descString} from binary: maximum recursion depth of ${ctx.recursionLimit} reached`); | ||
| } | ||
| const field = message.findNumber(fieldNo); | ||
| if (!field) { | ||
| // Use remaining recursion budget for skipping nested groups | ||
| const recursionLimit = ctx.recursionLimit - ctx.depth; | ||
| const data = reader.skip(wireType, fieldNo, recursionLimit); | ||
| if (ctx.readUnknownFields) { | ||
| unknownFields.push({ no: fieldNo, wireType, data }); | ||
| const end = reader.pos + length; | ||
| const unknownFields = (_a = message.$unknown) !== null && _a !== void 0 ? _a : []; | ||
| while (reader.pos < end) { | ||
| const [fieldNo, wireType] = reader.tag(); | ||
| const fieldReader = fieldReaders.get(fieldNo); | ||
| if (fieldReader === undefined) { | ||
| // Use remaining recursion budget for skipping nested groups | ||
| const data = reader.skip(wireType, fieldNo, ctx.recursionLimit - ctx.depth); | ||
| if (ctx.readUnknownFields) { | ||
| unknownFields.push({ no: fieldNo, wireType, data }); | ||
| } | ||
| continue; | ||
| } | ||
| continue; | ||
| fieldReader(message, reader, ctx, wireType); | ||
| } | ||
| readField(message, reader, field, wireType, ctx); | ||
| } | ||
| if (delimited) { | ||
| if (wireType != binary_encoding_js_1.WireType.EndGroup || fieldNo !== lengthOrDelimitedFieldNo) { | ||
| if (unknownFields.length > 0) { | ||
| message.$unknown = unknownFields; | ||
| } | ||
| ctx.depth--; | ||
| }; | ||
| } | ||
| /** | ||
| * Create a decoder for a message with the delimited encoding (group), | ||
| * reading until the EndGroup tag, like compileMessageReader. | ||
| */ | ||
| function compileGroupReader(descString, fieldReaders) { | ||
| return (message, reader, ctx, fieldNo) => { | ||
| var _a; | ||
| if (++ctx.depth > ctx.recursionLimit) { | ||
| throw new Error(`cannot decode ${descString} from binary: maximum recursion depth of ${ctx.recursionLimit} reached`); | ||
| } | ||
| let recordFieldNo; | ||
| let wireType; | ||
| const unknownFields = (_a = message.$unknown) !== null && _a !== void 0 ? _a : []; | ||
| while (reader.pos < reader.len) { | ||
| [recordFieldNo, wireType] = reader.tag(); | ||
| if (wireType == binary_encoding_js_1.WireType.EndGroup) { | ||
| break; | ||
| } | ||
| const fieldReader = fieldReaders.get(recordFieldNo); | ||
| if (fieldReader === undefined) { | ||
| // Use remaining recursion budget for skipping nested groups | ||
| const data = reader.skip(wireType, recordFieldNo, ctx.recursionLimit - ctx.depth); | ||
| if (ctx.readUnknownFields) { | ||
| unknownFields.push({ no: recordFieldNo, wireType, data }); | ||
| } | ||
| continue; | ||
| } | ||
| fieldReader(message, reader, ctx, wireType); | ||
| } | ||
| if (wireType != binary_encoding_js_1.WireType.EndGroup || recordFieldNo !== fieldNo) { | ||
| throw new Error("invalid end group tag"); | ||
| } | ||
| } | ||
| if (unknownFields.length > 0) { | ||
| message.setUnknown(unknownFields); | ||
| } | ||
| ctx.depth--; | ||
| if (unknownFields.length > 0) { | ||
| message.$unknown = unknownFields; | ||
| } | ||
| ctx.depth--; | ||
| }; | ||
| } | ||
@@ -103,149 +161,259 @@ /** | ||
| function readField(message, reader, field, wireType, ctx) { | ||
| var _a; | ||
| compileFieldReader(field)(message[unsafe_js_1.unsafeLocal], reader, ctx, wireType); | ||
| } | ||
| function compileFieldReader(field) { | ||
| switch (field.fieldKind) { | ||
| case "scalar": | ||
| message.set(field, readScalar(reader, field.scalar, field.utf8Validation)); | ||
| break; | ||
| return compileScalarFieldReader(field); | ||
| case "enum": | ||
| const val = readScalar(reader, descriptors_js_1.ScalarType.INT32); | ||
| if (field.enum.open) { | ||
| message.set(field, val); | ||
| } | ||
| else { | ||
| const ok = field.enum.values.some((v) => v.number === val); | ||
| if (ok) { | ||
| message.set(field, val); | ||
| } | ||
| else if (ctx.readUnknownFields) { | ||
| const bytes = []; | ||
| (0, varint_js_1.varint32write)(val, bytes); | ||
| const unknownFields = (_a = message.getUnknown()) !== null && _a !== void 0 ? _a : []; | ||
| unknownFields.push({ | ||
| no: field.number, | ||
| wireType, | ||
| data: new Uint8Array(bytes), | ||
| }); | ||
| message.setUnknown(unknownFields); | ||
| } | ||
| } | ||
| break; | ||
| return compileEnumFieldReader(field); | ||
| case "message": | ||
| message.set(field, readMessageField(reader, ctx, field, message.get(field))); | ||
| break; | ||
| return compileMessageFieldReader(field); | ||
| case "list": | ||
| readListField(reader, wireType, message.get(field), ctx); | ||
| break; | ||
| return compileListFieldReader(field); | ||
| case "map": | ||
| readMapEntry(reader, message.get(field), ctx); | ||
| break; | ||
| return compileMapFieldReader(field); | ||
| } | ||
| } | ||
| // Read a map field, expecting key field = 1, value field = 2 | ||
| function readMapEntry(reader, map, ctx) { | ||
| const field = map.field(); | ||
| let key; | ||
| let val; | ||
| // Read the length of the map entry, which is a varint. | ||
| const len = reader.uint32(); | ||
| // WARNING: Calculate end AFTER advancing reader.pos (above), so that | ||
| // reader.pos is at the start of the map entry. | ||
| const end = reader.pos + len; | ||
| while (reader.pos < end) { | ||
| const [fieldNo] = reader.tag(); | ||
| switch (fieldNo) { | ||
| case 1: | ||
| key = readScalar(reader, field.mapKey, field.utf8Validation); | ||
| break; | ||
| case 2: | ||
| switch (field.mapKind) { | ||
| case "scalar": | ||
| val = readScalar(reader, field.scalar, field.utf8Validation); | ||
| break; | ||
| case "enum": | ||
| val = reader.int32(); | ||
| break; | ||
| case "message": | ||
| val = readMessageField(reader, ctx, field); | ||
| break; | ||
| } | ||
| break; | ||
| function compileScalarFieldReader(field) { | ||
| const readScalar = compileScalarReader(field.scalar, field.utf8Validation, field.longAsString); | ||
| const localName = field.localName; | ||
| if (field.oneof) { | ||
| const oneofLocalName = field.oneof.localName; | ||
| return (message, reader) => { | ||
| message[oneofLocalName] = { | ||
| case: localName, | ||
| value: readScalar(reader), | ||
| }; | ||
| }; | ||
| } | ||
| return (message, reader) => { | ||
| message[localName] = readScalar(reader); | ||
| }; | ||
| } | ||
| function compileEnumFieldReader(field) { | ||
| var _a; | ||
| const localName = field.localName; | ||
| const oneofLocalName = (_a = field.oneof) === null || _a === void 0 ? void 0 : _a.localName; | ||
| if (field.enum.open) { | ||
| if (oneofLocalName !== undefined) { | ||
| return (message, reader) => { | ||
| message[oneofLocalName] = { case: localName, value: reader.int32() }; | ||
| }; | ||
| } | ||
| return (message, reader) => { | ||
| message[localName] = reader.int32(); | ||
| }; | ||
| } | ||
| if (key === undefined) { | ||
| key = (0, scalar_js_1.scalarZeroValue)(field.mapKey, false); | ||
| } | ||
| if (val === undefined) { | ||
| switch (field.mapKind) { | ||
| case "scalar": | ||
| val = (0, scalar_js_1.scalarZeroValue)(field.scalar, false); | ||
| break; | ||
| case "enum": | ||
| val = field.enum.values[0].number; | ||
| break; | ||
| case "message": | ||
| val = (0, reflect_js_1.reflect)(field.message, undefined, false); | ||
| break; | ||
| // Closed enums: unknown values are stored as unknown fields. | ||
| const values = field.enum.values; | ||
| const fieldNo = field.number; | ||
| return (message, reader, ctx, wireType) => { | ||
| var _a; | ||
| const val = reader.int32(); | ||
| if (values.some((v) => v.number === val)) { | ||
| if (oneofLocalName !== undefined) { | ||
| message[oneofLocalName] = { case: localName, value: val }; | ||
| } | ||
| else { | ||
| message[localName] = val; | ||
| } | ||
| } | ||
| else if (ctx.readUnknownFields) { | ||
| const bytes = []; | ||
| (0, varint_js_1.varint32write)(val, bytes); | ||
| const unknownFields = (_a = message.$unknown) !== null && _a !== void 0 ? _a : []; | ||
| unknownFields.push({ | ||
| no: fieldNo, | ||
| wireType, | ||
| data: new Uint8Array(bytes), | ||
| }); | ||
| message.$unknown = unknownFields; | ||
| } | ||
| }; | ||
| } | ||
| function compileMessageFieldReader(field) { | ||
| const localName = field.localName; | ||
| const { toMessage, toLocal } = (0, message_js_1.localMessageMapper)(field); | ||
| const readChild = compileChildReader(field); | ||
| if (field.oneof) { | ||
| const oneofLocalName = field.oneof.localName; | ||
| return (message, reader, ctx) => { | ||
| const oneof = message[oneofLocalName]; | ||
| const child = toMessage(oneof.case === localName ? oneof.value : undefined); | ||
| readChild(child, reader, ctx); | ||
| message[oneofLocalName] = { case: localName, value: toLocal(child) }; | ||
| }; | ||
| } | ||
| map.set(key, val); | ||
| return (message, reader, ctx) => { | ||
| const child = toMessage(message[localName]); | ||
| readChild(child, reader, ctx); | ||
| message[localName] = toLocal(child); | ||
| }; | ||
| } | ||
| function readListField(reader, wireType, list, ctx) { | ||
| var _a; | ||
| const field = list.field(); | ||
| if (field.listKind === "message") { | ||
| list.add(readMessageField(reader, ctx, field)); | ||
| return; | ||
| /** | ||
| * Compile a decoder for the wire format of a message field, honoring the | ||
| * delimited encoding of the field. | ||
| */ | ||
| function compileChildReader(field) { | ||
| const compiledChild = compiledReader(field.message); | ||
| if (field.delimitedEncoding) { | ||
| const fieldNo = field.number; | ||
| return (child, reader, ctx) => compiledChild.readGroup(child, reader, ctx, fieldNo); | ||
| } | ||
| const scalarType = (_a = field.scalar) !== null && _a !== void 0 ? _a : descriptors_js_1.ScalarType.INT32; | ||
| const packed = wireType == binary_encoding_js_1.WireType.LengthDelimited && | ||
| scalarType != descriptors_js_1.ScalarType.STRING && | ||
| scalarType != descriptors_js_1.ScalarType.BYTES; | ||
| if (!packed) { | ||
| list.add(readScalar(reader, scalarType, field.utf8Validation)); | ||
| return; | ||
| return (child, reader, ctx) => compiledChild.read(child, reader, ctx, reader.uint32()); | ||
| } | ||
| function compileListFieldReader(field) { | ||
| const localName = field.localName; | ||
| if (field.listKind == "message") { | ||
| const { toMessage, toLocal } = (0, message_js_1.localMessageMapper)(field); | ||
| const readChild = compileChildReader(field); | ||
| return (message, reader, ctx) => { | ||
| const child = toMessage(undefined); | ||
| readChild(child, reader, ctx); | ||
| message[localName].push(toLocal(child)); | ||
| }; | ||
| } | ||
| const e = reader.uint32() + reader.pos; | ||
| while (reader.pos < e) { | ||
| list.add(readScalar(reader, scalarType, field.utf8Validation)); | ||
| const scalarType = field.listKind == "enum" ? descriptors_js_1.ScalarType.INT32 : field.scalar; | ||
| const longAsString = field.listKind == "scalar" ? field.longAsString : false; | ||
| const readScalar = compileScalarReader(scalarType, field.utf8Validation, longAsString); | ||
| const packedPossible = scalarType != descriptors_js_1.ScalarType.STRING && scalarType != descriptors_js_1.ScalarType.BYTES; | ||
| return (message, reader, ctx, wireType) => { | ||
| const items = message[localName]; | ||
| if (wireType == binary_encoding_js_1.WireType.LengthDelimited && packedPossible) { | ||
| const end = reader.uint32() + reader.pos; | ||
| while (reader.pos < end) { | ||
| items.push(readScalar(reader)); | ||
| } | ||
| } | ||
| else { | ||
| items.push(readScalar(reader)); | ||
| } | ||
| }; | ||
| } | ||
| function compileMapFieldReader(field) { | ||
| const localName = field.localName; | ||
| const readKey = compileScalarReader(field.mapKey, field.utf8Validation, false); | ||
| const keyZero = (0, scalar_js_1.scalarZeroValue)(field.mapKey, false); | ||
| let readValue; | ||
| let valueDefault; | ||
| switch (field.mapKind) { | ||
| case "scalar": { | ||
| const scalar = field.scalar; | ||
| const readScalar = compileScalarReader(scalar, field.utf8Validation, false); | ||
| readValue = (reader) => readScalar(reader); | ||
| // Bytes zero values are created per entry, so that entries do not share | ||
| // one instance. | ||
| if (scalar == descriptors_js_1.ScalarType.BYTES) { | ||
| valueDefault = () => new Uint8Array(0); | ||
| } | ||
| else { | ||
| const zero = (0, scalar_js_1.scalarZeroValue)(scalar, false); | ||
| valueDefault = () => zero; | ||
| } | ||
| break; | ||
| } | ||
| case "enum": { | ||
| const zero = field.enum.values[0].number; | ||
| readValue = (reader) => reader.int32(); | ||
| valueDefault = () => zero; | ||
| break; | ||
| } | ||
| case "message": { | ||
| const { toMessage, toLocal } = (0, message_js_1.localMessageMapper)(field); | ||
| const readChild = compiledReader(field.message).read; | ||
| readValue = (reader, ctx) => { | ||
| const child = toMessage(undefined); | ||
| readChild(child, reader, ctx, reader.uint32()); | ||
| return toLocal(child); | ||
| }; | ||
| valueDefault = () => toLocal(toMessage(undefined)); | ||
| break; | ||
| } | ||
| } | ||
| return (message, reader, ctx) => { | ||
| const record = message[localName]; | ||
| let key; | ||
| let val; | ||
| // Read the length of the map entry, which is a varint. | ||
| const len = reader.uint32(); | ||
| // Calculate end AFTER advancing reader.pos (above), so that reader.pos is | ||
| // at the start of the map entry. | ||
| const end = reader.pos + len; | ||
| while (reader.pos < end) { | ||
| // Map entries have the key in field 1, and the value in field 2. | ||
| const [fieldNo] = reader.tag(); | ||
| switch (fieldNo) { | ||
| case 1: | ||
| key = readKey(reader); | ||
| break; | ||
| case 2: | ||
| val = readValue(reader, ctx); | ||
| break; | ||
| } | ||
| } | ||
| if (key === undefined) { | ||
| key = keyZero; | ||
| } | ||
| if (val === undefined) { | ||
| val = valueDefault(); | ||
| } | ||
| // Object property keys are always strings or symbols. Assigning with a | ||
| // boolean, number, or bigint key implicitly converts it to a string. | ||
| record[key] = val; | ||
| }; | ||
| } | ||
| function readMessageField(reader, ctx, field, mergeMessage) { | ||
| const delimited = field.delimitedEncoding; | ||
| const message = mergeMessage !== null && mergeMessage !== void 0 ? mergeMessage : (0, reflect_js_1.reflect)(field.message, undefined, false); | ||
| readMessage(message, reader, ctx, delimited, delimited ? field.number : reader.uint32()); | ||
| return message; | ||
| } | ||
| function readScalar(reader, type, validateUtf8 = false) { | ||
| /** | ||
| * Returns a reader for a scalar value. For 64-bit integers, BinaryReader | ||
| * already returns the local representation (bigint or string), so, unlike in | ||
| * the reflection layer, no validation is needed here. | ||
| */ | ||
| function compileScalarReader(type, utf8Validation, longAsString) { | ||
| switch (type) { | ||
| case descriptors_js_1.ScalarType.STRING: | ||
| return reader.string(validateUtf8); | ||
| return (reader) => reader.string(utf8Validation); | ||
| case descriptors_js_1.ScalarType.BOOL: | ||
| return reader.bool(); | ||
| return (reader) => reader.bool(); | ||
| case descriptors_js_1.ScalarType.DOUBLE: | ||
| return reader.double(); | ||
| return (reader) => reader.double(); | ||
| case descriptors_js_1.ScalarType.FLOAT: | ||
| return reader.float(); | ||
| return (reader) => reader.float(); | ||
| case descriptors_js_1.ScalarType.INT32: | ||
| return reader.int32(); | ||
| return (reader) => reader.int32(); | ||
| case descriptors_js_1.ScalarType.INT64: | ||
| return reader.int64(); | ||
| if (longAsString) { | ||
| return (reader) => String(reader.int64()); | ||
| } | ||
| return (reader) => reader.int64(); | ||
| case descriptors_js_1.ScalarType.UINT64: | ||
| return reader.uint64(); | ||
| if (longAsString) { | ||
| return (reader) => String(reader.uint64()); | ||
| } | ||
| return (reader) => reader.uint64(); | ||
| case descriptors_js_1.ScalarType.FIXED64: | ||
| return reader.fixed64(); | ||
| if (longAsString) { | ||
| return (reader) => String(reader.fixed64()); | ||
| } | ||
| return (reader) => reader.fixed64(); | ||
| case descriptors_js_1.ScalarType.BYTES: | ||
| return reader.bytes(); | ||
| return (reader) => reader.bytes(); | ||
| case descriptors_js_1.ScalarType.FIXED32: | ||
| return reader.fixed32(); | ||
| return (reader) => reader.fixed32(); | ||
| case descriptors_js_1.ScalarType.SFIXED32: | ||
| return reader.sfixed32(); | ||
| return (reader) => reader.sfixed32(); | ||
| case descriptors_js_1.ScalarType.SFIXED64: | ||
| return reader.sfixed64(); | ||
| if (longAsString) { | ||
| return (reader) => String(reader.sfixed64()); | ||
| } | ||
| return (reader) => reader.sfixed64(); | ||
| case descriptors_js_1.ScalarType.SINT64: | ||
| return reader.sint64(); | ||
| if (longAsString) { | ||
| return (reader) => String(reader.sint64()); | ||
| } | ||
| return (reader) => reader.sint64(); | ||
| case descriptors_js_1.ScalarType.UINT32: | ||
| return reader.uint32(); | ||
| return (reader) => reader.uint32(); | ||
| case descriptors_js_1.ScalarType.SINT32: | ||
| return reader.sint32(); | ||
| return (reader) => reader.sint32(); | ||
| } | ||
| } |
+601
-303
@@ -25,9 +25,14 @@ "use strict"; | ||
| const create_js_1 = require("./create.js"); | ||
| const reflect_js_1 = require("./reflect/reflect.js"); | ||
| const error_js_1 = require("./reflect/error.js"); | ||
| const reflect_check_js_1 = require("./reflect/reflect-check.js"); | ||
| const names_js_1 = require("./reflect/names.js"); | ||
| const scalar_js_1 = require("./reflect/scalar.js"); | ||
| const unsafe_js_1 = require("./reflect/unsafe.js"); | ||
| const message_js_1 = require("./reflect/message.js"); | ||
| const base64_encoding_js_1 = require("./wire/base64-encoding.js"); | ||
| const index_js_1 = require("./wkt/index.js"); | ||
| const extensions_js_1 = require("./extensions.js"); | ||
| const json_js_1 = require("./wkt/json.js"); | ||
| // bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name = $number; | ||
| const IMPLICIT = 2; | ||
| function makeReadContext(options) { | ||
@@ -66,16 +71,5 @@ return Object.assign(Object.assign({ ignoreUnknownFields: false, recursionLimit: 100 }, options), { depth: 0 }); | ||
| function fromJson(schema, json, options) { | ||
| const msg = (0, reflect_js_1.reflect)(schema); | ||
| try { | ||
| readMessage(msg, json, makeReadContext(options)); | ||
| } | ||
| catch (e) { | ||
| if ((0, error_js_1.isFieldError)(e)) { | ||
| // @ts-expect-error we use the ES2022 error CTOR option "cause" for better stack traces | ||
| throw new Error(`cannot decode ${e.field()} from JSON: ${e.message}`, { | ||
| cause: e, | ||
| }); | ||
| } | ||
| throw e; | ||
| } | ||
| return msg.message; | ||
| const message = (0, create_js_1.create)(schema); | ||
| readMessage(schema, message, json, options); | ||
| return message; | ||
| } | ||
@@ -95,4 +89,16 @@ /** | ||
| function mergeFromJson(schema, target, json, options) { | ||
| if (target.$typeName !== schema.typeName && | ||
| schema.fields.length > 0) { | ||
| throw new error_js_1.FieldError(schema.fields[0], `cannot use ${schema.fields[0]} with message ${target.$typeName}`, "ForeignFieldError"); | ||
| } | ||
| readMessage(schema, target, json, options); | ||
| return target; | ||
| } | ||
| /** | ||
| * Run the compiled decoder for the message, wrapping FieldErrors with the | ||
| * standard error message. | ||
| */ | ||
| function readMessage(schema, message, json, options) { | ||
| try { | ||
| readMessage((0, reflect_js_1.reflect)(schema, target), json, makeReadContext(options)); | ||
| compiledReader(schema)(message, json, makeReadContext(options)); | ||
| } | ||
@@ -108,3 +114,2 @@ catch (e) { | ||
| } | ||
| return target; | ||
| } | ||
@@ -115,3 +120,5 @@ /** | ||
| function enumFromJson(descEnum, json) { | ||
| return readEnum(descEnum, json, false); | ||
| // With ignoreUnknownFields false, the converter never returns the token | ||
| // for ignored unknown enum values. | ||
| return compileEnumConverter(descEnum)(json, false); | ||
| } | ||
@@ -124,214 +131,506 @@ /** | ||
| } | ||
| const messageJsonFields = new WeakMap(); | ||
| function getJsonField(desc, jsonKey) { | ||
| var _a; | ||
| if (!messageJsonFields.has(desc)) { | ||
| const jsonNames = new Map(); | ||
| for (const field of desc.fields) { | ||
| jsonNames.set(field.name, field).set(field.jsonName, field); | ||
| } | ||
| messageJsonFields.set(desc, jsonNames); | ||
| const compiledReaders = new WeakMap(); | ||
| /** | ||
| * Return the compiled decoder for a message, compiling it on first use. | ||
| */ | ||
| function compiledReader(desc) { | ||
| let compiled = compiledReaders.get(desc); | ||
| if (compiled === undefined) { | ||
| compiled = compileMessage(desc); | ||
| } | ||
| return (_a = messageJsonFields.get(desc)) === null || _a === void 0 ? void 0 : _a.get(jsonKey); | ||
| return compiled; | ||
| } | ||
| function readMessage(msg, json, ctx) { | ||
| var _a; | ||
| if (++ctx.depth > ctx.recursionLimit) { | ||
| throw new Error(`cannot decode ${msg.desc} from JSON: maximum recursion depth of ${ctx.recursionLimit} reached`); | ||
| function compileMessage(desc) { | ||
| const descString = String(desc); | ||
| const readWkt = compileWkt(desc); | ||
| if (readWkt !== undefined) { | ||
| // All message decoders count against the recursion limit, including | ||
| // well-known types with a custom JSON representation. | ||
| const compiled = (message, json, ctx) => { | ||
| if (++ctx.depth > ctx.recursionLimit) { | ||
| throw new Error(`cannot decode ${descString} from JSON: maximum recursion depth of ${ctx.recursionLimit} reached`); | ||
| } | ||
| readWkt(message, json, ctx); | ||
| ctx.depth--; | ||
| }; | ||
| compiledReaders.set(desc, compiled); | ||
| return compiled; | ||
| } | ||
| if (tryWktFromJson(msg, json, ctx)) { | ||
| ctx.depth--; | ||
| return; | ||
| } | ||
| if (json == null || Array.isArray(json) || typeof json != "object") { | ||
| throw new Error(`cannot decode ${msg.desc} from JSON: ${(0, reflect_check_js_1.formatVal)(json)}`); | ||
| } | ||
| const oneofSeen = new Map(); | ||
| const fieldSeen = new Set(); | ||
| for (const [jsonKey, jsonValue] of Object.entries(json)) { | ||
| const field = getJsonField(msg.desc, jsonKey); | ||
| if (field) { | ||
| if (fieldSeen.has(field)) { | ||
| // The same field may be set by its proto name and its JSON name, or by | ||
| // a duplicate or unicode-escaped key that JSON.parse already collapsed. | ||
| // Checked before the null-skip below so that a null entry still counts. | ||
| throw new error_js_1.FieldError(field, "set multiple times"); | ||
| const typeName = desc.typeName; | ||
| // Fields are looked up by their proto name and their JSON name. | ||
| const fieldsByJsonKey = new Map(); | ||
| const compiled = (message, json, ctx) => { | ||
| var _a; | ||
| if (++ctx.depth > ctx.recursionLimit) { | ||
| throw new Error(`cannot decode ${descString} from JSON: maximum recursion depth of ${ctx.recursionLimit} reached`); | ||
| } | ||
| if (json == null || Array.isArray(json) || typeof json != "object") { | ||
| throw new Error(`cannot decode ${descString} from JSON: ${(0, reflect_check_js_1.formatVal)(json)}`); | ||
| } | ||
| const oneofSeen = new Map(); | ||
| const fieldSeen = new Set(); | ||
| const jsonKeys = Object.keys(json); | ||
| for (let i = 0; i < jsonKeys.length; i++) { | ||
| const jsonKey = jsonKeys[i]; | ||
| const jsonValue = json[jsonKey]; | ||
| const entry = fieldsByJsonKey.get(jsonKey); | ||
| if (entry !== undefined) { | ||
| const field = entry.field; | ||
| if (fieldSeen.has(field)) { | ||
| // The same field may be set by its proto name and its JSON name, or by | ||
| // a duplicate or unicode-escaped key that JSON.parse already collapsed. | ||
| // Checked before the null-skip below so that a null entry still counts. | ||
| throw new error_js_1.FieldError(field, "set multiple times"); | ||
| } | ||
| fieldSeen.add(field); | ||
| if (entry.oneofScalarNullSkip && jsonValue === null) { | ||
| continue; | ||
| } | ||
| if (entry.oneof) { | ||
| const seen = oneofSeen.get(entry.oneof); | ||
| if (seen !== undefined) { | ||
| throw new error_js_1.FieldError(entry.oneof, `oneof set multiple times by ${seen.name} and ${field.name}`); | ||
| } | ||
| oneofSeen.set(entry.oneof, field); | ||
| } | ||
| entry.read(message, jsonValue, ctx); | ||
| } | ||
| fieldSeen.add(field); | ||
| if (field.oneof && jsonValue === null && field.fieldKind == "scalar") { | ||
| // see conformance test Required.Proto3.JsonInput.OneofFieldNull{First,Second} | ||
| continue; | ||
| } | ||
| if (field.oneof) { | ||
| const seen = oneofSeen.get(field.oneof); | ||
| if (seen !== undefined) { | ||
| throw new error_js_1.FieldError(field.oneof, `oneof set multiple times by ${seen.name} and ${field.name}`); | ||
| else { | ||
| const extension = jsonKey.startsWith("[") && jsonKey.endsWith("]") | ||
| ? (_a = ctx.registry) === null || _a === void 0 ? void 0 : _a.getExtension(jsonKey.substring(1, jsonKey.length - 1)) | ||
| : undefined; | ||
| if ((extension === null || extension === void 0 ? void 0 : extension.extendee.typeName) == typeName) { | ||
| const [container, field, get] = (0, extensions_js_1.createExtensionContainer)(extension); | ||
| compileFieldReader(field)(container[unsafe_js_1.unsafeLocal], jsonValue, ctx); | ||
| (0, extensions_js_1.setExtension)(message, extension, get()); | ||
| } | ||
| oneofSeen.set(field.oneof, field); | ||
| if (extension === undefined && !ctx.ignoreUnknownFields) { | ||
| throw new Error(`cannot decode ${descString} from JSON: key "${jsonKey}" is unknown`); | ||
| } | ||
| } | ||
| readField(msg, field, jsonValue, ctx); | ||
| } | ||
| else { | ||
| let extension = undefined; | ||
| if (jsonKey.startsWith("[") && | ||
| jsonKey.endsWith("]") && | ||
| // biome-ignore lint/suspicious/noAssignInExpressions: no | ||
| (extension = (_a = ctx.registry) === null || _a === void 0 ? void 0 : _a.getExtension(jsonKey.substring(1, jsonKey.length - 1))) && | ||
| extension.extendee.typeName === msg.desc.typeName) { | ||
| const [container, field, get] = (0, extensions_js_1.createExtensionContainer)(extension); | ||
| readField(container, field, jsonValue, ctx); | ||
| (0, extensions_js_1.setExtension)(msg.message, extension, get()); | ||
| ctx.depth--; | ||
| }; | ||
| // Register before compiling fields, so that recursive message types | ||
| // resolve to this instance instead of compiling endlessly. | ||
| compiledReaders.set(desc, compiled); | ||
| for (const field of desc.fields) { | ||
| const entry = { | ||
| read: compileFieldReader(field), | ||
| field, | ||
| oneof: field.oneof, | ||
| oneofScalarNullSkip: field.oneof !== undefined && field.fieldKind == "scalar", | ||
| }; | ||
| fieldsByJsonKey.set(field.name, entry).set(field.jsonName, entry); | ||
| } | ||
| return compiled; | ||
| } | ||
| /** | ||
| * Compile a decoder for a well-known type with a custom JSON representation, | ||
| * or return undefined for other messages. The recursion limit is enforced by | ||
| * the caller. | ||
| */ | ||
| function compileWkt(desc) { | ||
| if (!desc.typeName.startsWith("google.protobuf.")) { | ||
| return undefined; | ||
| } | ||
| switch (desc.typeName) { | ||
| case "google.protobuf.Any": | ||
| return (message, json, ctx) => anyFromJson(message, json, ctx); | ||
| case "google.protobuf.Timestamp": | ||
| return (message, json) => timestampFromJson(message, json); | ||
| case "google.protobuf.Duration": | ||
| return (message, json) => durationFromJson(message, json); | ||
| case "google.protobuf.FieldMask": | ||
| return (message, json) => fieldMaskFromJson(message, json); | ||
| case "google.protobuf.Struct": | ||
| return (message, json, ctx) => structFromJson(message, json, ctx); | ||
| case "google.protobuf.Value": | ||
| return (message, json, ctx) => valueFromJson(message, json, ctx); | ||
| case "google.protobuf.ListValue": | ||
| return (message, json, ctx) => listValueFromJson(message, json, ctx); | ||
| default: | ||
| if ((0, index_js_1.isWrapperDesc)(desc)) { | ||
| const valueField = desc.fields[0]; | ||
| const localName = valueField.localName; | ||
| const scalar = valueField.scalar; | ||
| const longAsString = valueField.longAsString; | ||
| const readScalar = compileScalarConverter(valueField); | ||
| return (message, json) => { | ||
| if (json === null) { | ||
| message[localName] = (0, scalar_js_1.scalarZeroValue)(scalar, longAsString); | ||
| } | ||
| else { | ||
| message[localName] = readScalar(json); | ||
| } | ||
| }; | ||
| } | ||
| if (!extension && !ctx.ignoreUnknownFields) { | ||
| throw new Error(`cannot decode ${msg.desc} from JSON: key "${jsonKey}" is unknown`); | ||
| } | ||
| } | ||
| return undefined; | ||
| } | ||
| ctx.depth--; | ||
| } | ||
| function readField(msg, field, json, ctx) { | ||
| function compileFieldReader(field) { | ||
| switch (field.fieldKind) { | ||
| case "scalar": | ||
| readScalarField(msg, field, json); | ||
| break; | ||
| return compileScalarFieldReader(field); | ||
| case "enum": | ||
| readEnumField(msg, field, json, ctx); | ||
| break; | ||
| return compileEnumFieldReader(field); | ||
| case "message": | ||
| readMessageField(msg, field, json, ctx); | ||
| break; | ||
| return compileMessageFieldReader(field); | ||
| case "list": | ||
| readListField(msg.get(field), json, ctx); | ||
| break; | ||
| return compileListFieldReader(field); | ||
| case "map": | ||
| readMapField(msg.get(field), json, ctx); | ||
| break; | ||
| return compileMapFieldReader(field); | ||
| } | ||
| } | ||
| function readListOrMapItem(field, json, ctx) { | ||
| if (field.scalar && json !== null) { | ||
| return scalarFromJson(field, json); | ||
| function compileScalarFieldReader(field) { | ||
| const readScalar = compileScalarConverter(field); | ||
| const localName = field.localName; | ||
| if (field.oneof) { | ||
| // JSON null for a oneof scalar member is skipped by the message decoder. | ||
| const oneofLocalName = field.oneof.localName; | ||
| return (message, json) => { | ||
| message[oneofLocalName] = { | ||
| case: localName, | ||
| value: readScalar(json), | ||
| }; | ||
| }; | ||
| } | ||
| if (field.message && !isResetSentinelNullValue(field, json)) { | ||
| const msgValue = (0, reflect_js_1.reflect)(field.message); | ||
| readMessage(msgValue, json, ctx); | ||
| return msgValue; | ||
| const clear = compileClear(field); | ||
| return (message, json) => { | ||
| if (json === null) { | ||
| clear(message); | ||
| } | ||
| else { | ||
| message[localName] = readScalar(json); | ||
| } | ||
| }; | ||
| } | ||
| /** | ||
| * Compile a function that resets the field to unset, mirroring the clear | ||
| * operation of the reflect API for fields that are not part of a oneof. | ||
| */ | ||
| function compileClear(field) { | ||
| const localName = field.localName; | ||
| if (field.presence != IMPLICIT) { | ||
| // Fields with explicit presence have properties on the prototype chain | ||
| // for default / zero values (except for proto3). By deleting their own | ||
| // property, the field is reset. | ||
| return (message) => { | ||
| delete message[localName]; | ||
| }; | ||
| } | ||
| if (field.enum && !isResetSentinelNullValue(field, json)) { | ||
| return readEnum(field.enum, json, ctx.ignoreUnknownFields); | ||
| if (field.fieldKind == "enum") { | ||
| const zero = field.enum.values[0].number; | ||
| return (message) => { | ||
| message[localName] = zero; | ||
| }; | ||
| } | ||
| throw new error_js_1.FieldError(field, `${field.fieldKind === "list" ? "list item" : "map value"} must not be null`); | ||
| const scalar = field.scalar; | ||
| const longAsString = field.longAsString; | ||
| return (message) => { | ||
| message[localName] = (0, scalar_js_1.scalarZeroValue)(scalar, longAsString); | ||
| }; | ||
| } | ||
| function readMapField(map, json, ctx) { | ||
| if (json === null) { | ||
| return; | ||
| function compileEnumFieldReader(field) { | ||
| const readEnumValue = compileEnumConverter(field.enum); | ||
| const checkEnum = compileEnumCheck(field.enum); | ||
| const localName = field.localName; | ||
| // Fields with enum google.protobuf.NullValue permit a Protobuf-serializable | ||
| // null; for all other enums, JSON null resets the field. | ||
| const nullResets = field.enum.typeName != "google.protobuf.NullValue"; | ||
| if (field.oneof) { | ||
| const oneofLocalName = field.oneof.localName; | ||
| return (message, json, ctx) => { | ||
| if (json === null && nullResets) { | ||
| const oneof = message[oneofLocalName]; | ||
| if (oneof.case === localName) { | ||
| message[oneofLocalName] = { case: undefined }; | ||
| } | ||
| return; | ||
| } | ||
| const value = readEnumValue(json, ctx.ignoreUnknownFields); | ||
| if (value === tokenIgnoredUnknownEnum) { | ||
| return; | ||
| } | ||
| const check = checkEnum(value); | ||
| if (check !== true) { | ||
| throw new error_js_1.FieldError(field, (0, reflect_check_js_1.reasonSingular)(field, value, check)); | ||
| } | ||
| message[oneofLocalName] = { case: localName, value }; | ||
| }; | ||
| } | ||
| const field = map.field(); | ||
| if (typeof json != "object" || Array.isArray(json)) { | ||
| throw new error_js_1.FieldError(field, "expected object, got " + (0, reflect_check_js_1.formatVal)(json)); | ||
| } | ||
| const seen = new Set(); | ||
| for (const [jsonMapKey, jsonMapValue] of Object.entries(json)) { | ||
| const key = mapKeyFromJson(field.mapKey, jsonMapKey); | ||
| if (seen.has(key)) { | ||
| throw new error_js_1.FieldError(field, `duplicate map key "${jsonMapKey}"`); | ||
| const clear = compileClear(field); | ||
| return (message, json, ctx) => { | ||
| if (json === null && nullResets) { | ||
| clear(message); | ||
| return; | ||
| } | ||
| seen.add(key); | ||
| const value = readListOrMapItem(field, jsonMapValue, ctx); | ||
| if (value !== tokenIgnoredUnknownEnum) { | ||
| map.set(key, value); | ||
| const value = readEnumValue(json, ctx.ignoreUnknownFields); | ||
| if (value === tokenIgnoredUnknownEnum) { | ||
| return; | ||
| } | ||
| } | ||
| const check = checkEnum(value); | ||
| if (check !== true) { | ||
| throw new error_js_1.FieldError(field, (0, reflect_check_js_1.reasonSingular)(field, value, check)); | ||
| } | ||
| message[localName] = value; | ||
| }; | ||
| } | ||
| function readListField(list, json, ctx) { | ||
| if (json === null) { | ||
| return; | ||
| function compileMessageFieldReader(field) { | ||
| const localName = field.localName; | ||
| const { toMessage, toLocal } = (0, message_js_1.localMessageMapper)(field); | ||
| const readChild = compiledReader(field.message); | ||
| // Fields with message google.protobuf.Value permit a Protobuf-serializable | ||
| // null; for all other messages, JSON null resets the field. | ||
| const nullResets = field.message.typeName != "google.protobuf.Value"; | ||
| if (field.oneof) { | ||
| const oneofLocalName = field.oneof.localName; | ||
| return (message, json, ctx) => { | ||
| const oneof = message[oneofLocalName]; | ||
| if (json === null && nullResets) { | ||
| if (oneof.case === localName) { | ||
| message[oneofLocalName] = { case: undefined }; | ||
| } | ||
| return; | ||
| } | ||
| const child = toMessage(oneof.case === localName ? oneof.value : undefined); | ||
| readChild(child, json, ctx); | ||
| message[oneofLocalName] = { case: localName, value: toLocal(child) }; | ||
| }; | ||
| } | ||
| const field = list.field(); | ||
| if (!Array.isArray(json)) { | ||
| throw new error_js_1.FieldError(field, "expected Array, got " + (0, reflect_check_js_1.formatVal)(json)); | ||
| } | ||
| for (const jsonItem of json) { | ||
| const value = readListOrMapItem(field, jsonItem, ctx); | ||
| if (value !== tokenIgnoredUnknownEnum) { | ||
| list.add(value); | ||
| return (message, json, ctx) => { | ||
| if (json === null && nullResets) { | ||
| delete message[localName]; | ||
| return; | ||
| } | ||
| } | ||
| const child = toMessage(message[localName]); | ||
| readChild(child, json, ctx); | ||
| message[localName] = toLocal(child); | ||
| }; | ||
| } | ||
| function readMessageField(msg, field, json, ctx) { | ||
| if (isResetSentinelNullValue(field, json)) { | ||
| msg.clear(field); | ||
| return; | ||
| } | ||
| const msgValue = msg.isSet(field) ? msg.get(field) : (0, reflect_js_1.reflect)(field.message); | ||
| readMessage(msgValue, json, ctx); | ||
| msg.set(field, msgValue); | ||
| function compileListFieldReader(field) { | ||
| const localName = field.localName; | ||
| const readItem = compileListItemReader(field); | ||
| return (message, json, ctx) => { | ||
| if (json === null) { | ||
| return; | ||
| } | ||
| if (!Array.isArray(json)) { | ||
| throw new error_js_1.FieldError(field, "expected Array, got " + (0, reflect_check_js_1.formatVal)(json)); | ||
| } | ||
| const items = message[localName]; | ||
| for (let i = 0; i < json.length; i++) { | ||
| const value = readItem(json[i], ctx, items.length); | ||
| if (value !== tokenIgnoredUnknownEnum) { | ||
| items.push(value); | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| function readEnumField(msg, field, json, ctx) { | ||
| if (isResetSentinelNullValue(field, json)) { | ||
| msg.clear(field); | ||
| return; | ||
| /** | ||
| * Compile a decoder for a list item. The index is only used in errors, and | ||
| * accounts for previously merged items. | ||
| */ | ||
| function compileListItemReader(field) { | ||
| switch (field.listKind) { | ||
| case "scalar": { | ||
| const parseScalar = compileScalarParse(field); | ||
| const checkValue = (0, reflect_check_js_1.checkScalarValue)(field.scalar); | ||
| const toLocal = compileScalarToLocal(field); | ||
| return (json, ctx, index) => { | ||
| if (json === null) { | ||
| throw new error_js_1.FieldError(field, "list item must not be null"); | ||
| } | ||
| const value = parseScalar(json); | ||
| const check = checkValue(value); | ||
| if (check !== true) { | ||
| throw new error_js_1.FieldError(field, `list item #${index + 1}: ${(0, reflect_check_js_1.reasonSingular)(field, value, check)}`); | ||
| } | ||
| return toLocal(value); | ||
| }; | ||
| } | ||
| case "enum": { | ||
| const readEnumValue = compileEnumConverter(field.enum); | ||
| const checkEnum = compileEnumCheck(field.enum); | ||
| const nullResets = field.enum.typeName != "google.protobuf.NullValue"; | ||
| return (json, ctx, index) => { | ||
| if (json === null && nullResets) { | ||
| throw new error_js_1.FieldError(field, "list item must not be null"); | ||
| } | ||
| const value = readEnumValue(json, ctx.ignoreUnknownFields); | ||
| if (value === tokenIgnoredUnknownEnum) { | ||
| return value; | ||
| } | ||
| const check = checkEnum(value); | ||
| if (check !== true) { | ||
| throw new error_js_1.FieldError(field, `list item #${index + 1}: ${(0, reflect_check_js_1.reasonSingular)(field, value, check)}`); | ||
| } | ||
| return value; | ||
| }; | ||
| } | ||
| case "message": { | ||
| const { toMessage, toLocal } = (0, message_js_1.localMessageMapper)(field); | ||
| const readChild = compiledReader(field.message); | ||
| const nullResets = field.message.typeName != "google.protobuf.Value"; | ||
| return (json, ctx) => { | ||
| if (json === null && nullResets) { | ||
| throw new error_js_1.FieldError(field, "list item must not be null"); | ||
| } | ||
| const child = toMessage(undefined); | ||
| readChild(child, json, ctx); | ||
| return toLocal(child); | ||
| }; | ||
| } | ||
| } | ||
| const enumValue = readEnum(field.enum, json, ctx.ignoreUnknownFields); | ||
| if (enumValue !== tokenIgnoredUnknownEnum) { | ||
| msg.set(field, enumValue); | ||
| } | ||
| } | ||
| function readScalarField(msg, field, json) { | ||
| if (json === null) { | ||
| msg.clear(field); | ||
| function compileMapFieldReader(field) { | ||
| const localName = field.localName; | ||
| const mapKey = field.mapKey; | ||
| const parseMapKey = compileMapKeyParse(mapKey); | ||
| const checkMapKey = (0, reflect_check_js_1.checkScalarValue)(mapKey); | ||
| let parseValue; | ||
| // Additional validation for scalar and enum values, matching the checks | ||
| // of the reflect API. Message values need no validation. | ||
| let checkValue; | ||
| let toLocalValue = (value) => value; | ||
| // Fields with google.protobuf.Value or google.protobuf.NullValue values | ||
| // permit a Protobuf-serializable null. | ||
| let nullResets = true; | ||
| switch (field.mapKind) { | ||
| case "scalar": { | ||
| parseValue = compileScalarParse(field); | ||
| checkValue = (0, reflect_check_js_1.checkScalarValue)(field.scalar); | ||
| toLocalValue = compileScalarToLocal(field); | ||
| break; | ||
| } | ||
| case "enum": { | ||
| const readEnumValue = compileEnumConverter(field.enum); | ||
| parseValue = (json, ctx) => readEnumValue(json, ctx.ignoreUnknownFields); | ||
| checkValue = compileEnumCheck(field.enum); | ||
| nullResets = field.enum.typeName != "google.protobuf.NullValue"; | ||
| break; | ||
| } | ||
| case "message": { | ||
| const { toMessage, toLocal } = (0, message_js_1.localMessageMapper)(field); | ||
| const readChild = compiledReader(field.message); | ||
| nullResets = field.message.typeName != "google.protobuf.Value"; | ||
| parseValue = (json, ctx) => { | ||
| const child = toMessage(undefined); | ||
| readChild(child, json, ctx); | ||
| return toLocal(child); | ||
| }; | ||
| break; | ||
| } | ||
| } | ||
| else { | ||
| msg.set(field, scalarFromJson(field, json)); | ||
| } | ||
| return (message, json, ctx) => { | ||
| if (json === null) { | ||
| return; | ||
| } | ||
| if (typeof json != "object" || Array.isArray(json)) { | ||
| throw new error_js_1.FieldError(field, "expected object, got " + (0, reflect_check_js_1.formatVal)(json)); | ||
| } | ||
| const record = message[localName]; | ||
| const seen = new Set(); | ||
| const jsonMapKeys = Object.keys(json); | ||
| for (let i = 0; i < jsonMapKeys.length; i++) { | ||
| const jsonMapKey = jsonMapKeys[i]; | ||
| const jsonMapValue = json[jsonMapKey]; | ||
| const key = parseMapKey(jsonMapKey); | ||
| if (seen.has(key)) { | ||
| throw new error_js_1.FieldError(field, `duplicate map key "${jsonMapKey}"`); | ||
| } | ||
| seen.add(key); | ||
| if (jsonMapValue === null && nullResets) { | ||
| throw new error_js_1.FieldError(field, "map value must not be null"); | ||
| } | ||
| const value = parseValue(jsonMapValue, ctx); | ||
| if (value === tokenIgnoredUnknownEnum) { | ||
| continue; | ||
| } | ||
| const checkKey = checkMapKey(key); | ||
| if (checkKey !== true) { | ||
| throw new error_js_1.FieldError(field, `invalid map key: ${(0, reflect_check_js_1.reasonSingular)({ scalar: mapKey }, key, checkKey)}`); | ||
| } | ||
| if (checkValue !== undefined) { | ||
| const check = checkValue(value); | ||
| if (check !== true) { | ||
| throw new error_js_1.FieldError(field, `map entry ${(0, reflect_check_js_1.formatVal)(key)}: ${(0, reflect_check_js_1.reasonSingular)(field, value, check)}`); | ||
| } | ||
| } | ||
| // Object property keys are always strings or symbols. Assigning with a | ||
| // boolean, number, or bigint key implicitly converts it to a string. | ||
| record[key] = toLocalValue(value); | ||
| } | ||
| }; | ||
| } | ||
| const tokenIgnoredUnknownEnum = Symbol(); | ||
| /** | ||
| * Indicates whether a value is a sentinel for reseting a field. | ||
| * | ||
| * For this to be true, the value must be a JSON null and the field must not | ||
| * permit a present, Protobuf-serializable null. | ||
| * | ||
| * Only message google.protobuf.Value and enum google.protobuf.NullValue fields | ||
| * permit Protobuf-serializable nulls. | ||
| * | ||
| * Note that field-resetting sentinel nulls are not permitted in lists and maps. | ||
| * Compile a converter from a JSON value to an enum value. JSON null returns | ||
| * the enum's first value. With ignoreUnknownFields false, unknown string | ||
| * values raise an error; with true, they return tokenIgnoredUnknownEnum. | ||
| * The value is not checked against the enum's values, see compileEnumCheck. | ||
| */ | ||
| function isResetSentinelNullValue(field, json) { | ||
| var _a, _b; | ||
| return (json === null && | ||
| ((_a = field.message) === null || _a === void 0 ? void 0 : _a.typeName) != "google.protobuf.Value" && | ||
| ((_b = field.enum) === null || _b === void 0 ? void 0 : _b.typeName) != "google.protobuf.NullValue"); | ||
| function compileEnumConverter(desc) { | ||
| const zero = desc.values[0].number; | ||
| const values = desc.values; | ||
| return (json, ignoreUnknownFields) => { | ||
| if (json === null) { | ||
| return zero; | ||
| } | ||
| switch (typeof json) { | ||
| case "number": | ||
| if (Number.isInteger(json)) { | ||
| return json; | ||
| } | ||
| break; | ||
| case "string": { | ||
| const value = values.find((ev) => ev.name === json); | ||
| if (value !== undefined) { | ||
| return value.number; | ||
| } | ||
| if (ignoreUnknownFields) { | ||
| return tokenIgnoredUnknownEnum; | ||
| } | ||
| break; | ||
| } | ||
| } | ||
| throw new Error(`cannot decode ${desc} from JSON: ${(0, reflect_check_js_1.formatVal)(json)}`); | ||
| }; | ||
| } | ||
| const tokenIgnoredUnknownEnum = Symbol(); | ||
| function readEnum(desc, json, ignoreUnknownFields) { | ||
| if (json === null) { | ||
| return desc.values[0].number; | ||
| /** | ||
| * Compile the check that the reflect API performs for enum values: open | ||
| * enums accept any int32 value, closed enums accept only declared values. | ||
| */ | ||
| function compileEnumCheck(desc) { | ||
| if (desc.open) { | ||
| return (0, reflect_check_js_1.checkScalarValue)(descriptors_js_1.ScalarType.INT32); | ||
| } | ||
| switch (typeof json) { | ||
| case "number": | ||
| if (Number.isInteger(json)) { | ||
| return json; | ||
| } | ||
| break; | ||
| case "string": | ||
| const value = desc.values.find((ev) => ev.name === json); | ||
| if (value !== undefined) { | ||
| return value.number; | ||
| } | ||
| if (ignoreUnknownFields) { | ||
| return tokenIgnoredUnknownEnum; | ||
| } | ||
| break; | ||
| } | ||
| throw new Error(`cannot decode ${desc} from JSON: ${(0, reflect_check_js_1.formatVal)(json)}`); | ||
| const values = desc.values; | ||
| return (value) => values.some((v) => v.number === value); | ||
| } | ||
| /** | ||
| * Try to parse a JSON value to a scalar value for the reflect API. | ||
| * | ||
| * Returns the input if the JSON value cannot be converted. Raises a FieldError | ||
| * if conversion would be ambiguous. | ||
| * Compile a converter from a JSON value to the local representation of a | ||
| * scalar, fusing JSON parsing, the validation of the reflect API, and the | ||
| * conversion to the local 64-bit integer representation. | ||
| */ | ||
| function scalarFromJson(field, json) { | ||
| // int64, sfixed64, sint64, fixed64, uint64: Reflect supports string and number. | ||
| // string, bool: Supported by reflect. | ||
| function compileScalarConverter(field) { | ||
| const parseScalar = compileScalarParse(field); | ||
| const checkValue = (0, reflect_check_js_1.checkScalarValue)(field.scalar); | ||
| const toLocal = compileScalarToLocal(field); | ||
| return (json) => { | ||
| const value = parseScalar(json); | ||
| const check = checkValue(value); | ||
| if (check !== true) { | ||
| throw new error_js_1.FieldError(field, (0, reflect_check_js_1.reasonSingular)(field, value, check)); | ||
| } | ||
| return toLocal(value); | ||
| }; | ||
| } | ||
| /** | ||
| * Compile the JSON-specific parsing step for a scalar value: the special | ||
| * string values of float and double, string-encoded numbers, and base64 | ||
| * bytes. Returns the input unchanged if the JSON value cannot be converted; | ||
| * the validation step raises an error for it. | ||
| */ | ||
| function compileScalarParse(field) { | ||
| switch (field.scalar) { | ||
@@ -342,36 +641,38 @@ // float, double: JSON value will be a number or one of the special string values "NaN", "Infinity", and "-Infinity". | ||
| case descriptors_js_1.ScalarType.FLOAT: | ||
| if (json === "NaN") | ||
| return NaN; | ||
| if (json === "Infinity") | ||
| return Number.POSITIVE_INFINITY; | ||
| if (json === "-Infinity") | ||
| return Number.NEGATIVE_INFINITY; | ||
| if (typeof json == "number") { | ||
| if (Number.isNaN(json)) { | ||
| // NaN must be encoded with string constants | ||
| throw new error_js_1.FieldError(field, "unexpected NaN number"); | ||
| return (json) => { | ||
| if (json === "NaN") | ||
| return NaN; | ||
| if (json === "Infinity") | ||
| return Number.POSITIVE_INFINITY; | ||
| if (json === "-Infinity") | ||
| return Number.NEGATIVE_INFINITY; | ||
| if (typeof json == "number") { | ||
| if (Number.isNaN(json)) { | ||
| // NaN must be encoded with string constants | ||
| throw new error_js_1.FieldError(field, "unexpected NaN number"); | ||
| } | ||
| if (!Number.isFinite(json)) { | ||
| // Infinity must be encoded with string constants | ||
| throw new error_js_1.FieldError(field, "unexpected infinite number"); | ||
| } | ||
| return json; | ||
| } | ||
| if (!Number.isFinite(json)) { | ||
| // Infinity must be encoded with string constants | ||
| throw new error_js_1.FieldError(field, "unexpected infinite number"); | ||
| if (typeof json == "string") { | ||
| if (json === "") { | ||
| // empty string is not a number | ||
| return json; | ||
| } | ||
| if (json.trim().length !== json.length) { | ||
| // extra whitespace | ||
| return json; | ||
| } | ||
| const float = Number(json); | ||
| if (!Number.isFinite(float)) { | ||
| // Infinity and NaN must be encoded with string constants | ||
| return json; | ||
| } | ||
| return float; | ||
| } | ||
| break; | ||
| } | ||
| if (typeof json == "string") { | ||
| if (json === "") { | ||
| // empty string is not a number | ||
| break; | ||
| } | ||
| if (json.trim().length !== json.length) { | ||
| // extra whitespace | ||
| break; | ||
| } | ||
| const float = Number(json); | ||
| if (!Number.isFinite(float)) { | ||
| // Infinity and NaN must be encoded with string constants | ||
| break; | ||
| } | ||
| return float; | ||
| } | ||
| break; | ||
| return json; | ||
| }; | ||
| // int32, fixed32, uint32: JSON value will be a decimal number. Either numbers or strings are accepted. | ||
@@ -383,38 +684,74 @@ case descriptors_js_1.ScalarType.INT32: | ||
| case descriptors_js_1.ScalarType.UINT32: | ||
| return int32FromJson(json); | ||
| return int32FromJson; | ||
| // 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 descriptors_js_1.ScalarType.BYTES: | ||
| if (typeof json == "string") { | ||
| if (json === "") { | ||
| return new Uint8Array(0); | ||
| return (json) => { | ||
| if (typeof json == "string") { | ||
| if (json === "") { | ||
| return new Uint8Array(0); | ||
| } | ||
| try { | ||
| return (0, base64_encoding_js_1.base64Decode)(json); | ||
| } | ||
| catch (e) { | ||
| const message = e instanceof Error ? e.message : String(e); | ||
| throw new error_js_1.FieldError(field, message); | ||
| } | ||
| } | ||
| try { | ||
| return (0, base64_encoding_js_1.base64Decode)(json); | ||
| } | ||
| catch (e) { | ||
| const message = e instanceof Error ? e.message : String(e); | ||
| throw new error_js_1.FieldError(field, message); | ||
| } | ||
| return json; | ||
| }; | ||
| // int64, sfixed64, sint64, fixed64, uint64: The validation step accepts | ||
| // string and number. string, bool: no conversion. | ||
| default: | ||
| return (json) => json; | ||
| } | ||
| } | ||
| /** | ||
| * Compile the conversion of a validated scalar value to its local | ||
| * representation: 64-bit integers become bigint, or string with the | ||
| * longAsString option. | ||
| */ | ||
| function compileScalarToLocal(field) { | ||
| const longAsString = field.fieldKind !== "map" && field.longAsString; | ||
| switch (field.scalar) { | ||
| case descriptors_js_1.ScalarType.INT64: | ||
| case descriptors_js_1.ScalarType.SFIXED64: | ||
| case descriptors_js_1.ScalarType.SINT64: | ||
| if (longAsString) { | ||
| return (value) => String(value); | ||
| } | ||
| break; | ||
| return (value) => typeof value == "string" || typeof value == "number" | ||
| ? proto_int64_js_1.protoInt64.parse(value) | ||
| : value; | ||
| case descriptors_js_1.ScalarType.FIXED64: | ||
| case descriptors_js_1.ScalarType.UINT64: | ||
| if (longAsString) { | ||
| return (value) => String(value); | ||
| } | ||
| return (value) => typeof value == "string" || typeof value == "number" | ||
| ? proto_int64_js_1.protoInt64.uParse(value) | ||
| : value; | ||
| default: | ||
| return (value) => value; | ||
| } | ||
| return json; | ||
| } | ||
| /** | ||
| * Try to parse a JSON value to a map key for the reflect API. | ||
| * Canonicalizes 64-bit integers given as string, so that "01 and "1" are one | ||
| * key, and duplicates can raise an error. | ||
| * Returns the input if the JSON value cannot be converted. | ||
| * Return a parser from a JSON value to a map key for the given key type. | ||
| * Canonicalizes 64-bit integers given as string, so that "01" and "1" are | ||
| * one key, and duplicates can raise an error. | ||
| * The parser returns the input if the JSON value cannot be converted. | ||
| */ | ||
| function mapKeyFromJson(type, jsonString) { | ||
| function compileMapKeyParse(type) { | ||
| switch (type) { | ||
| case descriptors_js_1.ScalarType.BOOL: | ||
| switch (jsonString) { | ||
| case "true": | ||
| return true; | ||
| case "false": | ||
| return false; | ||
| } | ||
| return jsonString; | ||
| return (jsonString) => { | ||
| switch (jsonString) { | ||
| case "true": | ||
| return true; | ||
| case "false": | ||
| return false; | ||
| } | ||
| return jsonString; | ||
| }; | ||
| case descriptors_js_1.ScalarType.INT32: | ||
@@ -425,3 +762,3 @@ case descriptors_js_1.ScalarType.FIXED32: | ||
| case descriptors_js_1.ScalarType.SINT32: | ||
| return int32FromJson(jsonString); | ||
| return int32FromJson; | ||
| case descriptors_js_1.ScalarType.INT64: | ||
@@ -432,7 +769,8 @@ case descriptors_js_1.ScalarType.SINT64: | ||
| case descriptors_js_1.ScalarType.FIXED64: | ||
| return /^-?0+$/.test(jsonString) | ||
| return (jsonString) => /^-?0+$/.test(jsonString) | ||
| ? "0" | ||
| : jsonString.replace(/^(-?)0+(?=\d)/, "$1"); | ||
| default: | ||
| return jsonString; | ||
| // ScalarType.STRING | ||
| return (jsonString) => jsonString; | ||
| } | ||
@@ -557,42 +895,2 @@ } | ||
| } | ||
| function tryWktFromJson(msg, jsonValue, ctx) { | ||
| if (!msg.desc.typeName.startsWith("google.protobuf.")) { | ||
| return false; | ||
| } | ||
| switch (msg.desc.typeName) { | ||
| case "google.protobuf.Any": | ||
| anyFromJson(msg.message, jsonValue, ctx); | ||
| return true; | ||
| case "google.protobuf.Timestamp": | ||
| timestampFromJson(msg.message, jsonValue); | ||
| return true; | ||
| case "google.protobuf.Duration": | ||
| durationFromJson(msg.message, jsonValue); | ||
| return true; | ||
| case "google.protobuf.FieldMask": | ||
| fieldMaskFromJson(msg.message, jsonValue); | ||
| return true; | ||
| case "google.protobuf.Struct": | ||
| structFromJson(msg.message, jsonValue, ctx); | ||
| return true; | ||
| case "google.protobuf.Value": | ||
| valueFromJson(msg.message, jsonValue, ctx); | ||
| return true; | ||
| case "google.protobuf.ListValue": | ||
| listValueFromJson(msg.message, jsonValue, ctx); | ||
| return true; | ||
| default: | ||
| if ((0, index_js_1.isWrapperDesc)(msg.desc)) { | ||
| const valueField = msg.desc.fields[0]; | ||
| if (jsonValue === null) { | ||
| msg.clear(valueField); | ||
| } | ||
| else { | ||
| msg.set(valueField, scalarFromJson(valueField, jsonValue)); | ||
| } | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
| } | ||
| function anyFromJson(any, json, ctx) { | ||
@@ -620,7 +918,6 @@ var _a; | ||
| } | ||
| const msg = (0, reflect_js_1.reflect)(desc); | ||
| const message = (0, create_js_1.create)(desc); | ||
| if ((0, index_js_1.hasCustomJsonRepresentation)(desc) && | ||
| Object.prototype.hasOwnProperty.call(json, "value")) { | ||
| const value = json.value; | ||
| readMessage(msg, value, ctx); | ||
| compiledReader(desc)(message, json.value, ctx); | ||
| } | ||
@@ -631,5 +928,5 @@ else { | ||
| delete copy["@type"]; | ||
| readMessage(msg, copy, ctx); | ||
| compiledReader(desc)(message, copy, ctx); | ||
| } | ||
| (0, index_js_1.anyPack)(msg.desc, msg.message, any); | ||
| (0, index_js_1.anyPack)(desc, message, any); | ||
| } | ||
@@ -650,4 +947,3 @@ function timestampFromJson(timestamp, json) { | ||
| } | ||
| if (ms < Date.parse("0001-01-01T00:00:00Z") || | ||
| ms > Date.parse("9999-12-31T23:59:59Z")) { | ||
| if (ms < json_js_1.timestampMsMin || ms > json_js_1.timestampMsMax) { | ||
| throw new Error(`cannot decode message ${timestamp.$typeName} from JSON: must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive`); | ||
@@ -672,3 +968,3 @@ } | ||
| const longSeconds = Number(match[1]); | ||
| if (longSeconds > 315576000000 || longSeconds < -315576000000) { | ||
| if (longSeconds > json_js_1.durationSecondsMax || longSeconds < json_js_1.durationSecondsMin) { | ||
| throw new Error(`cannot decode message ${duration.$typeName} from JSON: ${(0, reflect_check_js_1.formatVal)(json)}`); | ||
@@ -704,6 +1000,8 @@ } | ||
| } | ||
| for (const [k, v] of Object.entries(json)) { | ||
| const parsedV = (0, create_js_1.create)(index_js_1.ValueSchema); | ||
| valueFromJson(parsedV, v, ctx); | ||
| struct.fields[k] = parsedV; | ||
| const keys = Object.keys(json); | ||
| for (let i = 0; i < keys.length; i++) { | ||
| const key = keys[i]; | ||
| const parsedValue = (0, create_js_1.create)(index_js_1.ValueSchema); | ||
| valueFromJson(parsedValue, json[key], ctx); | ||
| struct.fields[key] = parsedValue; | ||
| } | ||
@@ -750,7 +1048,7 @@ } | ||
| } | ||
| for (const e of json) { | ||
| for (let i = 0; i < json.length; i++) { | ||
| const value = (0, create_js_1.create)(index_js_1.ValueSchema); | ||
| valueFromJson(value, e, ctx); | ||
| valueFromJson(value, json[i], ctx); | ||
| listValue.values.push(value); | ||
| } | ||
| } |
@@ -1,2 +0,2 @@ | ||
| import { type DescField } from "../descriptors.js"; | ||
| import { type DescEnum, type DescField, type DescMessage, ScalarType } from "../descriptors.js"; | ||
| import { FieldError } from "./error.js"; | ||
@@ -19,2 +19,28 @@ /** | ||
| }, key: unknown, value: unknown): FieldError | undefined; | ||
| type InvalidScalarValueErr = false | "invalid UTF8" | `${string} out of range`; | ||
| /** | ||
| * Return the check for values of the given scalar type. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare function checkScalarValue(scalar: ScalarType): (value: unknown) => true | InvalidScalarValueErr; | ||
| /** | ||
| * Format the reason why a value is invalid for a singular field. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare function reasonSingular(field: { | ||
| scalar: ScalarType; | ||
| message?: undefined; | ||
| enum?: undefined; | ||
| } | { | ||
| scalar?: undefined; | ||
| message: DescMessage; | ||
| enum?: undefined; | ||
| } | { | ||
| scalar?: undefined; | ||
| message?: undefined; | ||
| enum: DescEnum; | ||
| }, val: unknown, details?: string | false): string; | ||
| export declare function formatVal(val: unknown): string; | ||
| export {}; |
@@ -19,2 +19,4 @@ "use strict"; | ||
| exports.checkMapEntry = checkMapEntry; | ||
| exports.checkScalarValue = checkScalarValue; | ||
| exports.reasonSingular = reasonSingular; | ||
| exports.formatVal = formatVal; | ||
@@ -68,3 +70,3 @@ const descriptors_js_1 = require("../descriptors.js"); | ||
| function checkMapEntry(field, key, value) { | ||
| const checkKey = checkScalarValue(key, field.mapKey); | ||
| const checkKey = checkScalarValue(field.mapKey)(key); | ||
| if (checkKey !== true) { | ||
@@ -81,3 +83,3 @@ return new error_js_1.FieldError(field, `invalid map key: ${reasonSingular({ scalar: field.mapKey }, key, checkKey)}`); | ||
| if (field.scalar !== undefined) { | ||
| return checkScalarValue(value, field.scalar); | ||
| return checkScalarValue(field.scalar)(value); | ||
| } | ||
@@ -88,3 +90,3 @@ if (field.enum !== undefined) { | ||
| // int32 (see https://protobuf.dev/programming-guides/proto3/#enum). | ||
| return checkScalarValue(value, descriptors_js_1.ScalarType.INT32); | ||
| return checkScalarValue(descriptors_js_1.ScalarType.INT32)(value); | ||
| } | ||
@@ -95,17 +97,24 @@ return field.enum.values.some((v) => v.number === value); | ||
| } | ||
| function checkScalarValue(value, scalar) { | ||
| /** | ||
| * Return the check for values of the given scalar type. | ||
| * | ||
| * @private | ||
| */ | ||
| function checkScalarValue(scalar) { | ||
| switch (scalar) { | ||
| case descriptors_js_1.ScalarType.DOUBLE: | ||
| return typeof value == "number"; | ||
| return (value) => typeof value == "number"; | ||
| case descriptors_js_1.ScalarType.FLOAT: | ||
| if (typeof value != "number") { | ||
| return false; | ||
| } | ||
| if (Number.isNaN(value) || !Number.isFinite(value)) { | ||
| return (value) => { | ||
| if (typeof value != "number") { | ||
| return false; | ||
| } | ||
| if (Number.isNaN(value) || !Number.isFinite(value)) { | ||
| return true; | ||
| } | ||
| if (value > binary_encoding_js_1.FLOAT32_MAX || value < binary_encoding_js_1.FLOAT32_MIN) { | ||
| return `${value.toFixed()} out of range`; | ||
| } | ||
| return true; | ||
| } | ||
| if (value > binary_encoding_js_1.FLOAT32_MAX || value < binary_encoding_js_1.FLOAT32_MIN) { | ||
| return `${value.toFixed()} out of range`; | ||
| } | ||
| return true; | ||
| }; | ||
| case descriptors_js_1.ScalarType.INT32: | ||
@@ -115,28 +124,34 @@ case descriptors_js_1.ScalarType.SFIXED32: | ||
| // signed | ||
| if (typeof value !== "number" || !Number.isInteger(value)) { | ||
| return false; | ||
| } | ||
| if (value > binary_encoding_js_1.INT32_MAX || value < binary_encoding_js_1.INT32_MIN) { | ||
| return `${value.toFixed()} out of range`; | ||
| } | ||
| return true; | ||
| return (value) => { | ||
| if (typeof value !== "number" || !Number.isInteger(value)) { | ||
| return false; | ||
| } | ||
| if (value > binary_encoding_js_1.INT32_MAX || value < binary_encoding_js_1.INT32_MIN) { | ||
| return `${value.toFixed()} out of range`; | ||
| } | ||
| return true; | ||
| }; | ||
| case descriptors_js_1.ScalarType.FIXED32: | ||
| case descriptors_js_1.ScalarType.UINT32: | ||
| // unsigned | ||
| if (typeof value !== "number" || !Number.isInteger(value)) { | ||
| return false; | ||
| } | ||
| if (value > binary_encoding_js_1.UINT32_MAX || value < 0) { | ||
| return `${value.toFixed()} out of range`; | ||
| } | ||
| return true; | ||
| return (value) => { | ||
| if (typeof value !== "number" || !Number.isInteger(value)) { | ||
| return false; | ||
| } | ||
| if (value > binary_encoding_js_1.UINT32_MAX || value < 0) { | ||
| return `${value.toFixed()} out of range`; | ||
| } | ||
| return true; | ||
| }; | ||
| case descriptors_js_1.ScalarType.BOOL: | ||
| return typeof value == "boolean"; | ||
| return (value) => typeof value == "boolean"; | ||
| case descriptors_js_1.ScalarType.STRING: | ||
| if (typeof value != "string") { | ||
| return false; | ||
| } | ||
| return (0, text_encoding_js_1.getTextEncoding)().checkUtf8(value) || "invalid UTF8"; | ||
| return (value) => { | ||
| if (typeof value != "string") { | ||
| return false; | ||
| } | ||
| return (0, text_encoding_js_1.getTextEncoding)().checkUtf8(value) || "invalid UTF8"; | ||
| }; | ||
| case descriptors_js_1.ScalarType.BYTES: | ||
| return value instanceof Uint8Array; | ||
| return (value) => value instanceof Uint8Array; | ||
| case descriptors_js_1.ScalarType.INT64: | ||
@@ -146,31 +161,40 @@ case descriptors_js_1.ScalarType.SFIXED64: | ||
| // signed | ||
| if (typeof value == "bigint" || | ||
| typeof value == "number" || | ||
| (typeof value == "string" && value.length > 0)) { | ||
| try { | ||
| proto_int64_js_1.protoInt64.parse(value); | ||
| return true; | ||
| return (value) => { | ||
| if (typeof value == "bigint" || | ||
| typeof value == "number" || | ||
| (typeof value == "string" && value.length > 0)) { | ||
| try { | ||
| proto_int64_js_1.protoInt64.parse(value); | ||
| return true; | ||
| } | ||
| catch (_) { | ||
| return `${value} out of range`; | ||
| } | ||
| } | ||
| catch (_) { | ||
| return `${value} out of range`; | ||
| } | ||
| } | ||
| return false; | ||
| return false; | ||
| }; | ||
| case descriptors_js_1.ScalarType.FIXED64: | ||
| case descriptors_js_1.ScalarType.UINT64: | ||
| // unsigned | ||
| if (typeof value == "bigint" || | ||
| typeof value == "number" || | ||
| (typeof value == "string" && value.length > 0)) { | ||
| try { | ||
| proto_int64_js_1.protoInt64.uParse(value); | ||
| return true; | ||
| return (value) => { | ||
| if (typeof value == "bigint" || | ||
| typeof value == "number" || | ||
| (typeof value == "string" && value.length > 0)) { | ||
| try { | ||
| proto_int64_js_1.protoInt64.uParse(value); | ||
| return true; | ||
| } | ||
| catch (_) { | ||
| return `${value} out of range`; | ||
| } | ||
| } | ||
| catch (_) { | ||
| return `${value} out of range`; | ||
| } | ||
| } | ||
| return false; | ||
| return false; | ||
| }; | ||
| } | ||
| } | ||
| /** | ||
| * Format the reason why a value is invalid for a singular field. | ||
| * | ||
| * @private | ||
| */ | ||
| function reasonSingular(field, val, details) { | ||
@@ -177,0 +201,0 @@ details = |
@@ -28,4 +28,3 @@ "use strict"; | ||
| const guard_js_1 = require("./guard.js"); | ||
| // google.protobuf.NullValue.NULL_VALUE; | ||
| const NULL_VALUE = 0; | ||
| const message_js_1 = require("./message.js"); | ||
| /** | ||
@@ -324,3 +323,3 @@ * Create a ReflectMessage. | ||
| // field, except when used in google.protobuf.Value. | ||
| return wktStructToLocal(value.message); | ||
| return (0, message_js_1.wktStructToLocal)(value.message); | ||
| } | ||
@@ -346,3 +345,3 @@ return value.message; | ||
| // field, except when used in google.protobuf.Value. | ||
| value = wktStructToReflect(value); | ||
| value = (0, message_js_1.wktStructToReflect)(value); | ||
| } | ||
@@ -474,77 +473,1 @@ } | ||
| } | ||
| function wktStructToReflect(json) { | ||
| const struct = { | ||
| $typeName: "google.protobuf.Struct", | ||
| fields: {}, | ||
| }; | ||
| if ((0, guard_js_1.isObject)(json)) { | ||
| for (const [k, v] of Object.entries(json)) { | ||
| struct.fields[k] = wktValueToReflect(v); | ||
| } | ||
| } | ||
| return struct; | ||
| } | ||
| function wktStructToLocal(val) { | ||
| const json = {}; | ||
| for (const [k, v] of Object.entries(val.fields)) { | ||
| json[k] = wktValueToLocal(v); | ||
| } | ||
| return json; | ||
| } | ||
| function wktValueToLocal(val) { | ||
| switch (val.kind.case) { | ||
| case "structValue": | ||
| return wktStructToLocal(val.kind.value); | ||
| case "listValue": | ||
| return val.kind.value.values.map(wktValueToLocal); | ||
| case "nullValue": | ||
| case undefined: | ||
| return null; | ||
| default: | ||
| return val.kind.value; | ||
| } | ||
| } | ||
| function wktValueToReflect(json) { | ||
| const value = { | ||
| $typeName: "google.protobuf.Value", | ||
| kind: { case: undefined }, | ||
| }; | ||
| switch (typeof json) { | ||
| case "number": | ||
| value.kind = { case: "numberValue", value: json }; | ||
| break; | ||
| case "string": | ||
| value.kind = { case: "stringValue", value: json }; | ||
| break; | ||
| case "boolean": | ||
| value.kind = { case: "boolValue", value: json }; | ||
| break; | ||
| case "object": | ||
| if (json === null) { | ||
| value.kind = { case: "nullValue", value: NULL_VALUE }; | ||
| } | ||
| else if (Array.isArray(json)) { | ||
| const listValue = { | ||
| $typeName: "google.protobuf.ListValue", | ||
| values: [], | ||
| }; | ||
| if (Array.isArray(json)) { | ||
| for (const e of json) { | ||
| listValue.values.push(wktValueToReflect(e)); | ||
| } | ||
| } | ||
| value.kind = { | ||
| case: "listValue", | ||
| value: listValue, | ||
| }; | ||
| } | ||
| else { | ||
| value.kind = { | ||
| case: "structValue", | ||
| value: wktStructToReflect(json), | ||
| }; | ||
| } | ||
| break; | ||
| } | ||
| return value; | ||
| } |
@@ -584,2 +584,3 @@ "use strict"; | ||
| }; | ||
| let toStr; | ||
| if (isExtension) { | ||
@@ -596,3 +597,3 @@ // extension field | ||
| field.jsonName = `[${typeName}]`; // option json_name is not allowed on extension fields | ||
| field.toString = () => `extension ${typeName}`; | ||
| toStr = () => `extension ${typeName}`; | ||
| const extendee = reg.getMessage(trimLeadingDot(proto.extendee)); | ||
@@ -612,4 +613,12 @@ assert(extendee, `invalid FieldDescriptorProto: extendee ${proto.extendee} not found`); | ||
| field.jsonName = proto.jsonName; | ||
| field.toString = () => `field ${parent.typeName}.${proto.name}`; | ||
| toStr = () => `field ${parent.typeName}.${proto.name}`; | ||
| } | ||
| // A plain assignment throws where built-in prototypes are frozen. The | ||
| // attributes match what an assignment produces. | ||
| Object.defineProperty(field, "toString", { | ||
| value: toStr, | ||
| writable: true, | ||
| enumerable: true, | ||
| configurable: true, | ||
| }); | ||
| const label = proto.label; | ||
@@ -616,0 +625,0 @@ const type = proto.type; |
@@ -22,4 +22,8 @@ import type { MessageShape } from "./types.js"; | ||
| /** | ||
| * Write a single field to binary format, if it is set. Used to serialize | ||
| * extensions: extensions always have explicit presence, so an extension | ||
| * value that was just set on the container is always written. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare function writeField(writer: BinaryWriter, opts: BinaryWriteOptions, msg: ReflectMessage, field: DescField): void; |
+363
-125
@@ -18,5 +18,10 @@ "use strict"; | ||
| exports.writeField = writeField; | ||
| const reflect_js_1 = require("./reflect/reflect.js"); | ||
| const binary_encoding_js_1 = require("./wire/binary-encoding.js"); | ||
| const descriptors_js_1 = require("./descriptors.js"); | ||
| const error_js_1 = require("./reflect/error.js"); | ||
| const unsafe_js_1 = require("./reflect/unsafe.js"); | ||
| const message_js_1 = require("./reflect/message.js"); | ||
| const proto_int64_js_1 = require("./proto-int64.js"); | ||
| // bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name = $number; | ||
| const IMPLICIT = 2; | ||
| // bootstrap-inject google.protobuf.FeatureSet.FieldPresence.LEGACY_REQUIRED: const $name = $number; | ||
@@ -32,154 +37,387 @@ const LEGACY_REQUIRED = 3; | ||
| function toBinary(schema, message, options) { | ||
| return writeFields(new binary_encoding_js_1.BinaryWriter(), makeWriteOptions(options), (0, reflect_js_1.reflect)(schema, message)).finish(); | ||
| const writer = new binary_encoding_js_1.BinaryWriter(); | ||
| compiledWriter(schema)(writer, makeWriteOptions(options), message); | ||
| return writer.finish(); | ||
| } | ||
| function writeFields(writer, opts, msg) { | ||
| var _a; | ||
| for (const f of msg.sortedFields) { | ||
| if (!msg.isSet(f)) { | ||
| if (f.presence == LEGACY_REQUIRED) { | ||
| throw new Error(`cannot encode ${f} to binary: required field not set`); | ||
| const compiledWriters = new WeakMap(); | ||
| /** | ||
| * Return the compiled encoder for a message, compiling it on first use. | ||
| */ | ||
| function compiledWriter(desc) { | ||
| let compiled = compiledWriters.get(desc); | ||
| if (compiled === undefined) { | ||
| compiled = compileMessage(desc); | ||
| } | ||
| return compiled; | ||
| } | ||
| function compileMessage(desc) { | ||
| const typeName = desc.typeName; | ||
| const sortedFields = desc.fields.concat().sort((a, b) => a.number - b.number); | ||
| // The field reported in ForeignFieldError. | ||
| const foreignField = sortedFields[0]; | ||
| const fieldWriters = []; | ||
| const compiled = (writer, opts, message) => { | ||
| if (message.$typeName !== typeName && foreignField !== undefined) { | ||
| throw new error_js_1.FieldError(foreignField, `cannot use ${foreignField} with message ${message.$typeName}`, "ForeignFieldError"); | ||
| } | ||
| for (let i = 0; i < fieldWriters.length; i++) { | ||
| fieldWriters[i](writer, opts, message); | ||
| } | ||
| const unknown = message.$unknown; | ||
| if (unknown !== undefined && opts.writeUnknownFields) { | ||
| for (let i = 0; i < unknown.length; i++) { | ||
| const { no, wireType, data } = unknown[i]; | ||
| writer.tag(no, wireType).raw(data); | ||
| } | ||
| continue; | ||
| } | ||
| writeField(writer, opts, msg, f); | ||
| }; | ||
| // Register before compiling fields, so that recursive message types | ||
| // resolve to this instance instead of compiling endlessly. | ||
| compiledWriters.set(desc, compiled); | ||
| for (const field of sortedFields) { | ||
| fieldWriters.push(compileField(field)); | ||
| } | ||
| if (opts.writeUnknownFields) { | ||
| for (const { no, wireType, data } of (_a = msg.getUnknown()) !== null && _a !== void 0 ? _a : []) { | ||
| writer.tag(no, wireType).raw(data); | ||
| } | ||
| } | ||
| return writer; | ||
| return compiled; | ||
| } | ||
| /** | ||
| * @private | ||
| */ | ||
| function writeField(writer, opts, msg, field) { | ||
| var _a; | ||
| function compileField(field) { | ||
| switch (field.fieldKind) { | ||
| case "message": | ||
| case "scalar": | ||
| case "enum": | ||
| writeScalar(writer, msg.desc.typeName, field.name, (_a = field.scalar) !== null && _a !== void 0 ? _a : descriptors_js_1.ScalarType.INT32, field.number, msg.get(field)); | ||
| break; | ||
| return compileSingularField(field); | ||
| case "list": | ||
| writeListField(writer, opts, field, msg.get(field)); | ||
| break; | ||
| case "message": | ||
| writeMessageField(writer, opts, field, msg.get(field)); | ||
| break; | ||
| return compileListField(field); | ||
| case "map": | ||
| for (const [key, val] of msg.get(field)) { | ||
| writeMapEntry(writer, opts, field, key, val); | ||
| } | ||
| break; | ||
| return compileMapField(field); | ||
| } | ||
| } | ||
| function writeScalar(writer, msgName, fieldName, scalarType, fieldNo, value) { | ||
| writeScalarValue(writer.tag(fieldNo, writeTypeOfScalar(scalarType)), msgName, fieldName, scalarType, value); | ||
| } | ||
| function writeMessageField(writer, opts, field, message) { | ||
| if (field.delimitedEncoding) { | ||
| writeFields(writer.tag(field.number, binary_encoding_js_1.WireType.StartGroup), opts, message).tag(field.number, binary_encoding_js_1.WireType.EndGroup); | ||
| /** | ||
| * Compile an encoder for a singular field: the presence check, and the | ||
| * value encoder. | ||
| */ | ||
| function compileSingularField(field) { | ||
| const writeValue = compileSingularValue(field); | ||
| const localName = field.localName; | ||
| if (field.oneof) { | ||
| const oneofLocalName = field.oneof.localName; | ||
| return (writer, opts, message) => { | ||
| const oneof = message[oneofLocalName]; | ||
| if (oneof.case === localName) { | ||
| writeValue(writer, opts, oneof.value); | ||
| } | ||
| }; | ||
| } | ||
| else { | ||
| writeFields(writer.tag(field.number, binary_encoding_js_1.WireType.LengthDelimited).fork(), opts, message).join(); | ||
| if (field.presence != IMPLICIT) { | ||
| const requiredError = field.presence == LEGACY_REQUIRED | ||
| ? `cannot encode ${field} to binary: required field not set` | ||
| : undefined; | ||
| return (writer, opts, message) => { | ||
| const value = message[localName]; | ||
| // Fields with explicit presence have properties on the prototype | ||
| // chain for default / zero values (except for proto3). | ||
| if (value !== undefined && | ||
| Object.prototype.hasOwnProperty.call(message, localName)) { | ||
| writeValue(writer, opts, value); | ||
| } | ||
| else if (requiredError !== undefined) { | ||
| throw new Error(requiredError); | ||
| } | ||
| }; | ||
| } | ||
| // Implicit presence: the field is set when the value is not the zero | ||
| // value. The check is inlined per type, see isScalarZeroValue. | ||
| if (field.fieldKind == "enum") { | ||
| const zero = field.enum.values[0].number; | ||
| return (writer, opts, message) => { | ||
| const value = message[localName]; | ||
| if (value !== zero) { | ||
| writeValue(writer, opts, value); | ||
| } | ||
| }; | ||
| } | ||
| switch (field.scalar) { | ||
| case descriptors_js_1.ScalarType.BOOL: | ||
| return (writer, opts, message) => { | ||
| const value = message[localName]; | ||
| if (value !== false) { | ||
| writeValue(writer, opts, value); | ||
| } | ||
| }; | ||
| case descriptors_js_1.ScalarType.STRING: | ||
| return (writer, opts, message) => { | ||
| const value = message[localName]; | ||
| if (value !== "") { | ||
| writeValue(writer, opts, value); | ||
| } | ||
| }; | ||
| case descriptors_js_1.ScalarType.BYTES: | ||
| return (writer, opts, message) => { | ||
| const value = message[localName]; | ||
| if (!(value instanceof Uint8Array) || value.byteLength > 0) { | ||
| writeValue(writer, opts, value); | ||
| } | ||
| }; | ||
| case descriptors_js_1.ScalarType.DOUBLE: | ||
| case descriptors_js_1.ScalarType.FLOAT: | ||
| return (writer, opts, message) => { | ||
| const value = message[localName]; | ||
| // Object.is distinguishes -0 from 0. | ||
| if (!Object.is(value, 0)) { | ||
| writeValue(writer, opts, value); | ||
| } | ||
| }; | ||
| default: | ||
| return (writer, opts, message) => { | ||
| const value = message[localName]; | ||
| // Loose comparison matches 0n, 0 and "0". | ||
| if (value != 0) { | ||
| writeValue(writer, opts, value); | ||
| } | ||
| }; | ||
| } | ||
| } | ||
| function writeListField(writer, opts, field, list) { | ||
| var _a; | ||
| if (field.listKind == "message") { | ||
| for (const item of list) { | ||
| writeMessageField(writer, opts, field, item); | ||
| /** | ||
| * Compile an encoder for the value of a singular field, including the tag. | ||
| */ | ||
| function compileSingularValue(field) { | ||
| switch (field.fieldKind) { | ||
| case "message": { | ||
| const { toMessage } = (0, message_js_1.localMessageMapper)(field); | ||
| const writeChild = compileChildWriter(field); | ||
| return (writer, opts, value) => { | ||
| writeChild(writer, opts, toMessage(value)); | ||
| }; | ||
| } | ||
| return; | ||
| case "scalar": | ||
| case "enum": { | ||
| const scalarType = field.fieldKind == "enum" ? descriptors_js_1.ScalarType.INT32 : field.scalar; | ||
| const fieldNo = field.number; | ||
| const wireType = writeTypeOfScalar(scalarType); | ||
| const writeScalar = compileScalarValue(scalarType, field.parent.typeName, field.name); | ||
| return (writer, opts, value) => { | ||
| writer.tag(fieldNo, wireType); | ||
| writeScalar(writer, value); | ||
| }; | ||
| } | ||
| } | ||
| const scalarType = (_a = field.scalar) !== null && _a !== void 0 ? _a : descriptors_js_1.ScalarType.INT32; | ||
| if (field.packed) { | ||
| if (!list.size) { | ||
| return; | ||
| } | ||
| function compileListField(field) { | ||
| const localName = field.localName; | ||
| const fieldNo = field.number; | ||
| switch (field.listKind) { | ||
| case "message": { | ||
| const { toMessage } = (0, message_js_1.localMessageMapper)(field); | ||
| const writeChild = compileChildWriter(field); | ||
| return (writer, opts, message) => { | ||
| const items = message[localName]; | ||
| for (let i = 0; i < items.length; i++) { | ||
| writeChild(writer, opts, toMessage(items[i])); | ||
| } | ||
| }; | ||
| } | ||
| writer.tag(field.number, binary_encoding_js_1.WireType.LengthDelimited).fork(); | ||
| for (const item of list) { | ||
| writeScalarValue(writer, field.parent.typeName, field.name, scalarType, item); | ||
| case "scalar": | ||
| case "enum": { | ||
| const scalarType = field.listKind == "enum" ? descriptors_js_1.ScalarType.INT32 : field.scalar; | ||
| const writeScalar = compileScalarValue(scalarType, field.parent.typeName, field.name); | ||
| if (field.packed) { | ||
| return (writer, opts, message) => { | ||
| const items = message[localName]; | ||
| if (items.length == 0) { | ||
| return; | ||
| } | ||
| writer.tag(fieldNo, binary_encoding_js_1.WireType.LengthDelimited).fork(); | ||
| for (let i = 0; i < items.length; i++) { | ||
| writeScalar(writer, items[i]); | ||
| } | ||
| writer.join(); | ||
| }; | ||
| } | ||
| const wireType = writeTypeOfScalar(scalarType); | ||
| return (writer, opts, message) => { | ||
| const items = message[localName]; | ||
| for (let i = 0; i < items.length; i++) { | ||
| writer.tag(fieldNo, wireType); | ||
| writeScalar(writer, items[i]); | ||
| } | ||
| }; | ||
| } | ||
| writer.join(); | ||
| return; | ||
| } | ||
| for (const item of list) { | ||
| writeScalar(writer, field.parent.typeName, field.name, scalarType, field.number, item); | ||
| } | ||
| function compileMapField(field) { | ||
| const localName = field.localName; | ||
| const fieldNo = field.number; | ||
| const writeKey = compileMapKey(field); | ||
| if (field.mapKind == "message") { | ||
| const { toMessage } = (0, message_js_1.localMessageMapper)(field); | ||
| const writeMessage = compiledWriter(field.message); | ||
| return (writer, opts, message) => { | ||
| const record = message[localName]; | ||
| const keys = Object.keys(record); | ||
| for (let i = 0; i < keys.length; i++) { | ||
| const key = keys[i]; | ||
| writer.tag(fieldNo, binary_encoding_js_1.WireType.LengthDelimited).fork(); | ||
| writeKey(writer, key); | ||
| // The value of a map entry is always field number 2. | ||
| writer.tag(2, binary_encoding_js_1.WireType.LengthDelimited).fork(); | ||
| writeMessage(writer, opts, toMessage(record[key])); | ||
| writer.join(); | ||
| writer.join(); | ||
| } | ||
| }; | ||
| } | ||
| const scalarType = field.mapKind == "enum" ? descriptors_js_1.ScalarType.INT32 : field.scalar; | ||
| const valueWireType = writeTypeOfScalar(scalarType); | ||
| const writeScalar = compileScalarValue(scalarType, field.parent.typeName, field.name); | ||
| return (writer, opts, message) => { | ||
| const record = message[localName]; | ||
| const keys = Object.keys(record); | ||
| for (let i = 0; i < keys.length; i++) { | ||
| const key = keys[i]; | ||
| writer.tag(fieldNo, binary_encoding_js_1.WireType.LengthDelimited).fork(); | ||
| writeKey(writer, key); | ||
| // The value of a map entry is always field number 2. | ||
| writer.tag(2, valueWireType); | ||
| writeScalar(writer, record[key]); | ||
| writer.join(); | ||
| } | ||
| }; | ||
| } | ||
| function writeMapEntry(writer, opts, field, key, value) { | ||
| var _a; | ||
| writer.tag(field.number, binary_encoding_js_1.WireType.LengthDelimited).fork(); | ||
| // write key, expecting key field number = 1 | ||
| writeScalar(writer, field.parent.typeName, field.name, field.mapKey, 1, key); | ||
| // write value, expecting value field number = 2 | ||
| switch (field.mapKind) { | ||
| case "scalar": | ||
| case "enum": | ||
| writeScalar(writer, field.parent.typeName, field.name, (_a = field.scalar) !== null && _a !== void 0 ? _a : descriptors_js_1.ScalarType.INT32, 2, value); | ||
| break; | ||
| case "message": | ||
| writeFields(writer.tag(2, binary_encoding_js_1.WireType.LengthDelimited).fork(), opts, value).join(); | ||
| break; | ||
| /** | ||
| * Compile an encoder for a map key. Map keys are stored as object keys and | ||
| * are always strings locally. Convert them to their scalar type before | ||
| * writing, like the reflect API does when iterating map entries. | ||
| */ | ||
| function compileMapKey(field) { | ||
| const wireType = writeTypeOfScalar(field.mapKey); | ||
| const writeScalar = compileScalarValue(field.mapKey, field.parent.typeName, field.name); | ||
| const convertKey = compileMapKeyConverter(field.mapKey); | ||
| return (writer, key) => { | ||
| // The key of a map entry is always field number 1. | ||
| writer.tag(1, wireType); | ||
| writeScalar(writer, convertKey(key)); | ||
| }; | ||
| } | ||
| /** | ||
| * Returns a converter from an object key (always a string) to the closest | ||
| * possible type for the map key type. Invalid keys are passed through to | ||
| * the scalar writer, which raises an error for them. | ||
| */ | ||
| function compileMapKeyConverter(type) { | ||
| switch (type) { | ||
| case descriptors_js_1.ScalarType.STRING: | ||
| return (key) => key; | ||
| case descriptors_js_1.ScalarType.BOOL: | ||
| return (key) => (key === "true" ? true : key === "false" ? false : key); | ||
| case descriptors_js_1.ScalarType.UINT64: | ||
| case descriptors_js_1.ScalarType.FIXED64: | ||
| return (key) => { | ||
| try { | ||
| return proto_int64_js_1.protoInt64.uParse(key); | ||
| } | ||
| catch (_a) { | ||
| return key; | ||
| } | ||
| }; | ||
| case descriptors_js_1.ScalarType.INT64: | ||
| case descriptors_js_1.ScalarType.SFIXED64: | ||
| case descriptors_js_1.ScalarType.SINT64: | ||
| return (key) => { | ||
| try { | ||
| return proto_int64_js_1.protoInt64.parse(key); | ||
| } | ||
| catch (_a) { | ||
| return key; | ||
| } | ||
| }; | ||
| default: | ||
| // Handles INT32, UINT32, SINT32, FIXED32, SFIXED32. | ||
| // We do not use individual cases to save a few bytes code size. | ||
| return (key) => { | ||
| const n = Number.parseInt(key); | ||
| return Number.isFinite(n) ? n : key; | ||
| }; | ||
| } | ||
| writer.join(); | ||
| } | ||
| function writeScalarValue(writer, msgName, fieldName, type, value) { | ||
| try { | ||
| switch (type) { | ||
| case descriptors_js_1.ScalarType.STRING: | ||
| writer.string(value); | ||
| break; | ||
| case descriptors_js_1.ScalarType.BOOL: | ||
| writer.bool(value); | ||
| break; | ||
| case descriptors_js_1.ScalarType.DOUBLE: | ||
| writer.double(value); | ||
| break; | ||
| case descriptors_js_1.ScalarType.FLOAT: | ||
| writer.float(value); | ||
| break; | ||
| case descriptors_js_1.ScalarType.INT32: | ||
| writer.int32(value); | ||
| break; | ||
| case descriptors_js_1.ScalarType.INT64: | ||
| writer.int64(value); | ||
| break; | ||
| case descriptors_js_1.ScalarType.UINT64: | ||
| writer.uint64(value); | ||
| break; | ||
| case descriptors_js_1.ScalarType.FIXED64: | ||
| writer.fixed64(value); | ||
| break; | ||
| case descriptors_js_1.ScalarType.BYTES: | ||
| writer.bytes(value); | ||
| break; | ||
| case descriptors_js_1.ScalarType.FIXED32: | ||
| writer.fixed32(value); | ||
| break; | ||
| case descriptors_js_1.ScalarType.SFIXED32: | ||
| writer.sfixed32(value); | ||
| break; | ||
| case descriptors_js_1.ScalarType.SFIXED64: | ||
| writer.sfixed64(value); | ||
| break; | ||
| case descriptors_js_1.ScalarType.SINT64: | ||
| writer.sint64(value); | ||
| break; | ||
| case descriptors_js_1.ScalarType.UINT32: | ||
| writer.uint32(value); | ||
| break; | ||
| case descriptors_js_1.ScalarType.SINT32: | ||
| writer.sint32(value); | ||
| break; | ||
| /** | ||
| * Compile an encoder for a bare scalar value (no tag), wrapping errors from | ||
| * the writer with the message and field name. | ||
| */ | ||
| function compileScalarValue(type, messageName, fieldName) { | ||
| const writeScalar = compileScalarWrite(type); | ||
| return (writer, value) => { | ||
| try { | ||
| writeScalar(writer, value); | ||
| } | ||
| } | ||
| catch (e) { | ||
| if (e instanceof Error) { | ||
| throw new Error(`cannot encode field ${msgName}.${fieldName} to binary: ${e.message}`); | ||
| catch (e) { | ||
| if (e instanceof Error) { | ||
| throw new Error(`cannot encode field ${messageName}.${fieldName} to binary: ${e.message}`); | ||
| } | ||
| throw e; | ||
| } | ||
| throw e; | ||
| }; | ||
| } | ||
| function compileScalarWrite(type) { | ||
| switch (type) { | ||
| case descriptors_js_1.ScalarType.STRING: | ||
| return (writer, value) => writer.string(value); | ||
| case descriptors_js_1.ScalarType.BOOL: | ||
| return (writer, value) => writer.bool(value); | ||
| case descriptors_js_1.ScalarType.DOUBLE: | ||
| return (writer, value) => writer.double(value); | ||
| case descriptors_js_1.ScalarType.FLOAT: | ||
| return (writer, value) => writer.float(value); | ||
| case descriptors_js_1.ScalarType.INT32: | ||
| return (writer, value) => writer.int32(value); | ||
| case descriptors_js_1.ScalarType.INT64: | ||
| return (writer, value) => writer.int64(value); | ||
| case descriptors_js_1.ScalarType.UINT64: | ||
| return (writer, value) => writer.uint64(value); | ||
| case descriptors_js_1.ScalarType.FIXED64: | ||
| return (writer, value) => writer.fixed64(value); | ||
| case descriptors_js_1.ScalarType.BYTES: | ||
| return (writer, value) => writer.bytes(value); | ||
| case descriptors_js_1.ScalarType.FIXED32: | ||
| return (writer, value) => writer.fixed32(value); | ||
| case descriptors_js_1.ScalarType.SFIXED32: | ||
| return (writer, value) => writer.sfixed32(value); | ||
| case descriptors_js_1.ScalarType.SFIXED64: | ||
| return (writer, value) => writer.sfixed64(value); | ||
| case descriptors_js_1.ScalarType.SINT64: | ||
| return (writer, value) => writer.sint64(value); | ||
| case descriptors_js_1.ScalarType.UINT32: | ||
| return (writer, value) => writer.uint32(value); | ||
| case descriptors_js_1.ScalarType.SINT32: | ||
| return (writer, value) => writer.sint32(value); | ||
| } | ||
| } | ||
| /** | ||
| * Write a single field to binary format, if it is set. Used to serialize | ||
| * extensions: extensions always have explicit presence, so an extension | ||
| * value that was just set on the container is always written. | ||
| * | ||
| * @private | ||
| */ | ||
| function writeField(writer, opts, msg, field) { | ||
| compileField(field)(writer, opts, msg[unsafe_js_1.unsafeLocal]); | ||
| } | ||
| /** | ||
| * Compile an encoder for the wire format of a message field, honoring the | ||
| * delimited encoding of the field. The tag is written by the encoder. | ||
| */ | ||
| function compileChildWriter(field) { | ||
| const fieldNo = field.number; | ||
| const writeMessage = compiledWriter(field.message); | ||
| if (field.delimitedEncoding) { | ||
| return (writer, opts, child) => { | ||
| writer.tag(fieldNo, binary_encoding_js_1.WireType.StartGroup); | ||
| writeMessage(writer, opts, child); | ||
| writer.tag(fieldNo, binary_encoding_js_1.WireType.EndGroup); | ||
| }; | ||
| } | ||
| return (writer, opts, child) => { | ||
| writer.tag(fieldNo, binary_encoding_js_1.WireType.LengthDelimited).fork(); | ||
| writeMessage(writer, opts, child); | ||
| writer.join(); | ||
| }; | ||
| } | ||
| function writeTypeOfScalar(type) { | ||
@@ -186,0 +424,0 @@ switch (type) { |
+393
-159
@@ -21,8 +21,12 @@ "use strict"; | ||
| const names_js_1 = require("./reflect/names.js"); | ||
| const reflect_js_1 = require("./reflect/reflect.js"); | ||
| const index_js_1 = require("./wkt/index.js"); | ||
| const wrappers_js_1 = require("./wkt/wrappers.js"); | ||
| const json_js_1 = require("./wkt/json.js"); | ||
| const index_js_2 = require("./wire/index.js"); | ||
| const extensions_js_1 = require("./extensions.js"); | ||
| const reflect_check_js_1 = require("./reflect/reflect-check.js"); | ||
| const error_js_1 = require("./reflect/error.js"); | ||
| const unsafe_js_1 = require("./reflect/unsafe.js"); | ||
| const scalar_js_1 = require("./reflect/scalar.js"); | ||
| const message_js_1 = require("./reflect/message.js"); | ||
| // bootstrap-inject google.protobuf.FeatureSet.FieldPresence.LEGACY_REQUIRED: const $name = $number; | ||
@@ -46,3 +50,3 @@ const LEGACY_REQUIRED = 3; | ||
| function toJson(schema, message, options) { | ||
| return reflectToJson((0, reflect_js_1.reflect)(schema, message), makeWriteOptions(options)); | ||
| return compiledWriter(schema)(makeWriteOptions(options), message); | ||
| } | ||
@@ -71,119 +75,333 @@ /** | ||
| } | ||
| function reflectToJson(msg, opts) { | ||
| var _a; | ||
| const wktJson = tryWktToJson(msg, opts); | ||
| if (wktJson !== undefined) | ||
| return wktJson; | ||
| const json = {}; | ||
| for (const f of msg.sortedFields) { | ||
| if (!msg.isSet(f)) { | ||
| if (f.presence == LEGACY_REQUIRED) { | ||
| throw new Error(`cannot encode ${f} to JSON: required field not set`); | ||
| const compiledWriters = new WeakMap(); | ||
| /** | ||
| * Return the compiled encoder for a message, compiling it on first use. | ||
| */ | ||
| function compiledWriter(desc) { | ||
| let compiled = compiledWriters.get(desc); | ||
| if (compiled === undefined) { | ||
| compiled = compileMessage(desc); | ||
| } | ||
| return compiled; | ||
| } | ||
| function compileMessage(desc) { | ||
| const typeName = desc.typeName; | ||
| const writeWkt = compileWkt(desc); | ||
| if (writeWkt !== undefined) { | ||
| // The field reported in ForeignFieldError. All well-known types with a | ||
| // custom JSON representation have at least one field. | ||
| const foreignField = desc.fields[0]; | ||
| const compiledWriter = (opts, message) => { | ||
| if (message.$typeName !== typeName && foreignField !== undefined) { | ||
| throw new error_js_1.FieldError(foreignField, `cannot use ${foreignField} with message ${message.$typeName}`, "ForeignFieldError"); | ||
| } | ||
| if (!opts.alwaysEmitImplicit || f.presence !== IMPLICIT) { | ||
| // Fields with implicit presence omit zero values (e.g. empty string) by default | ||
| continue; | ||
| } | ||
| return writeWkt(opts, message); | ||
| }; | ||
| compiledWriters.set(desc, compiledWriter); | ||
| return compiledWriter; | ||
| } | ||
| const sortedFields = desc.fields.concat().sort((a, b) => a.number - b.number); | ||
| // The field reported in ForeignFieldError. | ||
| const foreignField = sortedFields[0]; | ||
| const fieldWriters = []; | ||
| const compiledWriter = (opts, message) => { | ||
| if (message.$typeName !== typeName && foreignField !== undefined) { | ||
| throw new error_js_1.FieldError(foreignField, `cannot use ${foreignField} with message ${message.$typeName}`, "ForeignFieldError"); | ||
| } | ||
| const jsonValue = fieldToJson(f, msg.get(f), opts); | ||
| if (jsonValue !== undefined) { | ||
| json[jsonName(f, opts)] = jsonValue; | ||
| const json = {}; | ||
| for (let i = 0; i < fieldWriters.length; i++) { | ||
| fieldWriters[i](opts, message, json); | ||
| } | ||
| if (opts.registry) { | ||
| writeExtensions(json, opts, opts.registry, message, desc); | ||
| } | ||
| return json; | ||
| }; | ||
| // Register before compiling fields, so that recursive message types | ||
| // resolve to this instance instead of compiling endlessly. | ||
| compiledWriters.set(desc, compiledWriter); | ||
| for (const field of sortedFields) { | ||
| fieldWriters.push(compileField(field)); | ||
| } | ||
| if (opts.registry) { | ||
| const tagSeen = new Set(); | ||
| for (const { no } of (_a = msg.getUnknown()) !== null && _a !== void 0 ? _a : []) { | ||
| // Same tag can appear multiple times, so we | ||
| // keep track and skip identical ones. | ||
| if (!tagSeen.has(no)) { | ||
| tagSeen.add(no); | ||
| const extension = opts.registry.getExtensionFor(msg.desc, no); | ||
| if (!extension) { | ||
| continue; | ||
| return compiledWriter; | ||
| } | ||
| /** | ||
| * Compile an encoder for a well-known type with a custom JSON representation, | ||
| * or return undefined for other messages. | ||
| */ | ||
| function compileWkt(desc) { | ||
| if (!desc.typeName.startsWith("google.protobuf.")) { | ||
| return undefined; | ||
| } | ||
| switch (desc.typeName) { | ||
| case "google.protobuf.Any": | ||
| return (opts, message) => anyToJson(message, opts); | ||
| case "google.protobuf.Timestamp": | ||
| return (opts, message) => timestampToJson(message); | ||
| case "google.protobuf.Duration": | ||
| return (opts, message) => durationToJson(message); | ||
| case "google.protobuf.FieldMask": | ||
| return (opts, message) => fieldMaskToJson(message); | ||
| case "google.protobuf.Struct": | ||
| return (opts, message) => structToJson(message); | ||
| case "google.protobuf.Value": | ||
| return (opts, message) => valueToJson(message); | ||
| case "google.protobuf.ListValue": | ||
| return (opts, message) => listValueToJson(message); | ||
| default: | ||
| if ((0, wrappers_js_1.isWrapperDesc)(desc)) { | ||
| const valueField = desc.fields[0]; | ||
| const localName = valueField.localName; | ||
| const zero = (0, scalar_js_1.scalarZeroValue)(valueField.scalar, false); | ||
| const writeScalar = compileScalarValue(valueField); | ||
| return (opts, message) => { | ||
| const value = message[localName]; | ||
| return writeScalar(opts, value === undefined ? zero : value); | ||
| }; | ||
| } | ||
| return undefined; | ||
| } | ||
| } | ||
| function compileField(field) { | ||
| switch (field.fieldKind) { | ||
| case "scalar": | ||
| case "enum": | ||
| case "message": | ||
| return compileSingularField(field); | ||
| case "list": | ||
| case "map": { | ||
| const writeValue = field.fieldKind == "list" | ||
| ? compileListValue(field) | ||
| : compileMapValue(field); | ||
| const protoName = field.name; | ||
| const jsonKey = field.jsonName; | ||
| const localName = field.localName; | ||
| return (opts, message, json) => { | ||
| const value = writeValue(opts, message[localName]); | ||
| if (value !== undefined) { | ||
| json[opts.useProtoFieldName ? protoName : jsonKey] = value; | ||
| } | ||
| const value = (0, extensions_js_1.getExtension)(msg.message, extension); | ||
| const [container, field] = (0, extensions_js_1.createExtensionContainer)(extension, value); | ||
| const jsonValue = fieldToJson(field, container.get(field), opts); | ||
| if (jsonValue !== undefined) { | ||
| json[extension.jsonName] = jsonValue; | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| } | ||
| return json; | ||
| } | ||
| function fieldToJson(f, val, opts) { | ||
| switch (f.fieldKind) { | ||
| /** | ||
| * Compile an encoder for a singular field: the presence check, and the | ||
| * value encoder. | ||
| */ | ||
| function compileSingularField(field) { | ||
| const writeValue = compileSingularValue(field); | ||
| const protoName = field.name; | ||
| const jsonKey = field.jsonName; | ||
| const localName = field.localName; | ||
| if (field.oneof) { | ||
| const oneofLocalName = field.oneof.localName; | ||
| return (opts, message, json) => { | ||
| const oneof = message[oneofLocalName]; | ||
| if (oneof.case === localName) { | ||
| json[opts.useProtoFieldName ? protoName : jsonKey] = writeValue(opts, oneof.value); | ||
| } | ||
| }; | ||
| } | ||
| if (field.presence != IMPLICIT) { | ||
| const requiredError = field.presence == LEGACY_REQUIRED | ||
| ? `cannot encode ${field} to JSON: required field not set` | ||
| : undefined; | ||
| return (opts, message, json) => { | ||
| const value = message[localName]; | ||
| // Fields with explicit presence have properties on the prototype | ||
| // chain for default / zero values (except for proto3). | ||
| if (value !== undefined && | ||
| Object.prototype.hasOwnProperty.call(message, localName)) { | ||
| json[opts.useProtoFieldName ? protoName : jsonKey] = writeValue(opts, value); | ||
| } | ||
| else if (requiredError !== undefined) { | ||
| throw new Error(requiredError); | ||
| } | ||
| }; | ||
| } | ||
| // Implicit presence: the field is emitted when the value is not the zero | ||
| // value, or when alwaysEmitImplicit is enabled. The zero check is inlined | ||
| // per type, see isScalarZeroValue. | ||
| if (field.fieldKind == "enum") { | ||
| const zero = field.enum.values[0].number; | ||
| return (opts, message, json) => { | ||
| const value = message[localName]; | ||
| if (value !== zero || opts.alwaysEmitImplicit) { | ||
| json[opts.useProtoFieldName ? protoName : jsonKey] = writeValue(opts, value); | ||
| } | ||
| }; | ||
| } | ||
| switch (field.scalar) { | ||
| case descriptors_js_1.ScalarType.BOOL: | ||
| return (opts, message, json) => { | ||
| const value = message[localName]; | ||
| if (value !== false || opts.alwaysEmitImplicit) { | ||
| json[opts.useProtoFieldName ? protoName : jsonKey] = writeValue(opts, value); | ||
| } | ||
| }; | ||
| case descriptors_js_1.ScalarType.STRING: | ||
| return (opts, message, json) => { | ||
| const value = message[localName]; | ||
| if (value !== "" || opts.alwaysEmitImplicit) { | ||
| json[opts.useProtoFieldName ? protoName : jsonKey] = writeValue(opts, value); | ||
| } | ||
| }; | ||
| case descriptors_js_1.ScalarType.BYTES: | ||
| return (opts, message, json) => { | ||
| const value = message[localName]; | ||
| if (!(value instanceof Uint8Array) || | ||
| value.byteLength > 0 || | ||
| opts.alwaysEmitImplicit) { | ||
| json[opts.useProtoFieldName ? protoName : jsonKey] = writeValue(opts, value); | ||
| } | ||
| }; | ||
| case descriptors_js_1.ScalarType.DOUBLE: | ||
| case descriptors_js_1.ScalarType.FLOAT: | ||
| return (opts, message, json) => { | ||
| const value = message[localName]; | ||
| // Object.is distinguishes -0 from 0. | ||
| if (!Object.is(value, 0) || opts.alwaysEmitImplicit) { | ||
| json[opts.useProtoFieldName ? protoName : jsonKey] = writeValue(opts, value); | ||
| } | ||
| }; | ||
| default: | ||
| return (opts, message, json) => { | ||
| const value = message[localName]; | ||
| // Loose comparison matches 0n, 0 and "0". | ||
| if (value != 0 || opts.alwaysEmitImplicit) { | ||
| json[opts.useProtoFieldName ? protoName : jsonKey] = writeValue(opts, value); | ||
| } | ||
| }; | ||
| } | ||
| } | ||
| /** | ||
| * Compile an encoder for the value of a field of any kind. Used for | ||
| * extension values. | ||
| */ | ||
| function compileFieldValue(field) { | ||
| switch (field.fieldKind) { | ||
| case "scalar": | ||
| return scalarToJson(f, val); | ||
| case "enum": | ||
| case "message": | ||
| return reflectToJson(val, opts); | ||
| case "enum": | ||
| return enumToJsonInternal(f.enum, val, opts.enumAsInteger); | ||
| return compileSingularValue(field); | ||
| case "list": | ||
| return listToJson(val, opts); | ||
| return compileListValue(field); | ||
| case "map": | ||
| return mapToJson(val, opts); | ||
| return compileMapValue(field); | ||
| } | ||
| } | ||
| function mapToJson(map, opts) { | ||
| const f = map.field(); | ||
| const jsonObj = {}; | ||
| switch (f.mapKind) { | ||
| /** | ||
| * Compile an encoder for the value of a singular field. | ||
| */ | ||
| function compileSingularValue(field) { | ||
| switch (field.fieldKind) { | ||
| case "scalar": | ||
| for (const [entryKey, entryValue] of map) { | ||
| jsonObj[entryKey] = scalarToJson(f, entryValue); | ||
| } | ||
| break; | ||
| return compileScalarValue(field); | ||
| case "enum": | ||
| return compileEnumValue(field); | ||
| case "message": | ||
| for (const [entryKey, entryValue] of map) { | ||
| jsonObj[entryKey] = reflectToJson(entryValue, opts); | ||
| } | ||
| break; | ||
| return compileMessageValue(field); | ||
| } | ||
| } | ||
| /** | ||
| * Compile an encoder for the value of a message field. | ||
| */ | ||
| function compileMessageValue(field) { | ||
| const { toMessage } = (0, message_js_1.localMessageMapper)(field); | ||
| const writeMessage = compiledWriter(field.message); | ||
| return (opts, value) => writeMessage(opts, toMessage(value)); | ||
| } | ||
| /** | ||
| * Compile an encoder for a list field value. Returns undefined for an empty | ||
| * list, unless alwaysEmitImplicit is enabled. | ||
| */ | ||
| function compileListValue(field) { | ||
| const writeItem = compileListItemValue(field); | ||
| return (opts, value) => { | ||
| const items = value; | ||
| if (items.length == 0 && !opts.alwaysEmitImplicit) { | ||
| return undefined; | ||
| } | ||
| const jsonArray = []; | ||
| for (let i = 0; i < items.length; i++) { | ||
| jsonArray.push(writeItem(opts, items[i])); | ||
| } | ||
| return jsonArray; | ||
| }; | ||
| } | ||
| function compileListItemValue(field) { | ||
| switch (field.listKind) { | ||
| case "scalar": | ||
| return compileScalarValue(field); | ||
| case "enum": | ||
| for (const [entryKey, entryValue] of map) { | ||
| jsonObj[entryKey] = enumToJsonInternal(f.enum, entryValue, opts.enumAsInteger); | ||
| } | ||
| break; | ||
| return compileEnumValue(field); | ||
| case "message": | ||
| return compileMessageValue(field); | ||
| } | ||
| return opts.alwaysEmitImplicit || map.size > 0 ? jsonObj : undefined; | ||
| } | ||
| function listToJson(list, opts) { | ||
| const f = list.field(); | ||
| const jsonArr = []; | ||
| switch (f.listKind) { | ||
| /** | ||
| * Compile an encoder for a map field value. Returns undefined for an empty | ||
| * map, unless alwaysEmitImplicit is enabled. Map keys are stored as object | ||
| * keys and are used as JSON keys as-is. | ||
| */ | ||
| function compileMapValue(field) { | ||
| const writeMapValue = compileMapEntryValue(field); | ||
| return (opts, value) => { | ||
| const record = value; | ||
| const keys = Object.keys(record); | ||
| if (keys.length == 0 && !opts.alwaysEmitImplicit) { | ||
| return undefined; | ||
| } | ||
| const jsonObject = {}; | ||
| for (let i = 0; i < keys.length; i++) { | ||
| const key = keys[i]; | ||
| jsonObject[key] = writeMapValue(opts, record[key]); | ||
| } | ||
| return jsonObject; | ||
| }; | ||
| } | ||
| function compileMapEntryValue(field) { | ||
| switch (field.mapKind) { | ||
| case "scalar": | ||
| for (const item of list) { | ||
| jsonArr.push(scalarToJson(f, item)); | ||
| } | ||
| break; | ||
| return compileScalarValue(field); | ||
| case "enum": | ||
| for (const item of list) { | ||
| jsonArr.push(enumToJsonInternal(f.enum, item, opts.enumAsInteger)); | ||
| } | ||
| break; | ||
| return compileEnumValue(field); | ||
| case "message": | ||
| for (const item of list) { | ||
| jsonArr.push(reflectToJson(item, opts)); | ||
| } | ||
| break; | ||
| return compileMessageValue(field); | ||
| } | ||
| return opts.alwaysEmitImplicit || jsonArr.length > 0 ? jsonArr : undefined; | ||
| } | ||
| function enumToJsonInternal(desc, value, enumAsInteger) { | ||
| var _a; | ||
| if (typeof value != "number") { | ||
| throw new Error(`cannot encode ${desc} to JSON: expected number, got ${(0, reflect_check_js_1.formatVal)(value)}`); | ||
| } | ||
| /** | ||
| * Compile an encoder for an enum value. | ||
| */ | ||
| function compileEnumValue(field) { | ||
| const desc = field.enum; | ||
| if (desc.typeName == "google.protobuf.NullValue") { | ||
| return null; | ||
| return (opts, value) => { | ||
| if (typeof value != "number") { | ||
| throw errorEnumValue(desc, value); | ||
| } | ||
| return null; | ||
| }; | ||
| } | ||
| if (enumAsInteger) { | ||
| return value; | ||
| } | ||
| const val = desc.value[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 | ||
| return (opts, value) => { | ||
| var _a, _b; | ||
| if (typeof value != "number") { | ||
| throw errorEnumValue(desc, value); | ||
| } | ||
| if (opts.enumAsInteger) { | ||
| return value; | ||
| } | ||
| // If we don't know the enum value, just return the number. | ||
| return (_b = (_a = desc.value[value]) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : value; | ||
| }; | ||
| } | ||
| function scalarToJson(field, value) { | ||
| var _a, _b, _c, _d, _e, _f; | ||
| function errorEnumValue(desc, value) { | ||
| return new Error(`cannot encode ${desc} to JSON: expected number, got ${(0, reflect_check_js_1.formatVal)(value)}`); | ||
| } | ||
| /** | ||
| * Compile an encoder for a scalar value. Errors report the original field | ||
| * descriptor, which may be a list or map field for items of those fields. | ||
| */ | ||
| function compileScalarValue(field) { | ||
| switch (field.scalar) { | ||
@@ -196,32 +414,40 @@ // int32, fixed32, uint32: JSON value will be a decimal number. Either numbers or strings are accepted. | ||
| case descriptors_js_1.ScalarType.UINT32: | ||
| if (typeof value != "number") { | ||
| throw new Error(`cannot encode ${field} to JSON: ${(_a = (0, reflect_check_js_1.checkField)(field, value)) === null || _a === void 0 ? void 0 : _a.message}`); | ||
| } | ||
| return value; | ||
| return (opts, value) => { | ||
| if (typeof value != "number") { | ||
| throw errorScalarValue(field, value); | ||
| } | ||
| 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 descriptors_js_1.ScalarType.FLOAT: | ||
| case descriptors_js_1.ScalarType.DOUBLE: // eslint-disable-line no-fallthrough | ||
| if (typeof value != "number") { | ||
| throw new Error(`cannot encode ${field} to JSON: ${(_b = (0, reflect_check_js_1.checkField)(field, value)) === null || _b === void 0 ? void 0 : _b.message}`); | ||
| } | ||
| if (Number.isNaN(value)) | ||
| return "NaN"; | ||
| if (value === Number.POSITIVE_INFINITY) | ||
| return "Infinity"; | ||
| if (value === Number.NEGATIVE_INFINITY) | ||
| return "-Infinity"; | ||
| return value; | ||
| case descriptors_js_1.ScalarType.DOUBLE: | ||
| return (opts, value) => { | ||
| if (typeof value != "number") { | ||
| throw errorScalarValue(field, value); | ||
| } | ||
| if (Number.isNaN(value)) | ||
| return "NaN"; | ||
| if (value === Number.POSITIVE_INFINITY) | ||
| return "Infinity"; | ||
| if (value === Number.NEGATIVE_INFINITY) | ||
| return "-Infinity"; | ||
| return value; | ||
| }; | ||
| // string: | ||
| case descriptors_js_1.ScalarType.STRING: | ||
| if (typeof value != "string") { | ||
| throw new Error(`cannot encode ${field} to JSON: ${(_c = (0, reflect_check_js_1.checkField)(field, value)) === null || _c === void 0 ? void 0 : _c.message}`); | ||
| } | ||
| return value; | ||
| return (opts, value) => { | ||
| if (typeof value != "string") { | ||
| throw errorScalarValue(field, value); | ||
| } | ||
| return value; | ||
| }; | ||
| // bool: | ||
| case descriptors_js_1.ScalarType.BOOL: | ||
| if (typeof value != "boolean") { | ||
| throw new Error(`cannot encode ${field} to JSON: ${(_d = (0, reflect_check_js_1.checkField)(field, value)) === null || _d === void 0 ? void 0 : _d.message}`); | ||
| } | ||
| return value; | ||
| return (opts, value) => { | ||
| if (typeof value != "boolean") { | ||
| throw errorScalarValue(field, value); | ||
| } | ||
| return value; | ||
| }; | ||
| // JSON value will be a decimal string. Either numbers or strings are accepted. | ||
@@ -233,46 +459,52 @@ case descriptors_js_1.ScalarType.UINT64: | ||
| case descriptors_js_1.ScalarType.SINT64: | ||
| if (typeof value == "bigint" || | ||
| typeof value == "string" || | ||
| (typeof value == "number" && Number.isInteger(value))) { | ||
| return value.toString(); | ||
| } | ||
| throw new Error(`cannot encode ${field} to JSON: ${(_e = (0, reflect_check_js_1.checkField)(field, value)) === null || _e === void 0 ? void 0 : _e.message}`); | ||
| return (opts, value) => { | ||
| if (typeof value == "bigint" || | ||
| typeof value == "string" || | ||
| (typeof value == "number" && Number.isInteger(value))) { | ||
| return value.toString(); | ||
| } | ||
| throw errorScalarValue(field, value); | ||
| }; | ||
| // 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 descriptors_js_1.ScalarType.BYTES: | ||
| if (value instanceof Uint8Array) { | ||
| return (0, index_js_2.base64Encode)(value); | ||
| } | ||
| throw new Error(`cannot encode ${field} to JSON: ${(_f = (0, reflect_check_js_1.checkField)(field, value)) === null || _f === void 0 ? void 0 : _f.message}`); | ||
| return (opts, value) => { | ||
| if (value instanceof Uint8Array) { | ||
| return (0, index_js_2.base64Encode)(value); | ||
| } | ||
| throw errorScalarValue(field, value); | ||
| }; | ||
| } | ||
| } | ||
| function jsonName(f, opts) { | ||
| return opts.useProtoFieldName ? f.name : f.jsonName; | ||
| function errorScalarValue(field, value) { | ||
| var _a; | ||
| return new Error(`cannot encode ${field} to JSON: ${(_a = (0, reflect_check_js_1.checkField)(field, value)) === null || _a === void 0 ? void 0 : _a.message}`); | ||
| } | ||
| // returns a json value if wkt, otherwise returns undefined. | ||
| function tryWktToJson(msg, opts) { | ||
| if (!msg.desc.typeName.startsWith("google.protobuf.")) { | ||
| return undefined; | ||
| /** | ||
| * Write extensions for unknown fields that are found in the registry. | ||
| */ | ||
| function writeExtensions(json, opts, registry, message, desc) { | ||
| const unknown = message.$unknown; | ||
| if (unknown === undefined) { | ||
| return; | ||
| } | ||
| switch (msg.desc.typeName) { | ||
| case "google.protobuf.Any": | ||
| return anyToJson(msg.message, opts); | ||
| case "google.protobuf.Timestamp": | ||
| return timestampToJson(msg.message); | ||
| case "google.protobuf.Duration": | ||
| return durationToJson(msg.message); | ||
| case "google.protobuf.FieldMask": | ||
| return fieldMaskToJson(msg.message); | ||
| case "google.protobuf.Struct": | ||
| return structToJson(msg.message); | ||
| case "google.protobuf.Value": | ||
| return valueToJson(msg.message); | ||
| case "google.protobuf.ListValue": | ||
| return listValueToJson(msg.message); | ||
| default: | ||
| if ((0, wrappers_js_1.isWrapperDesc)(msg.desc)) { | ||
| const valueField = msg.desc.fields[0]; | ||
| return scalarToJson(valueField, msg.get(valueField)); | ||
| const tagSeen = new Set(); | ||
| for (let i = 0; i < unknown.length; i++) { | ||
| const { no } = unknown[i]; | ||
| // Same tag can appear multiple times, so we | ||
| // keep track and skip identical ones. | ||
| if (!tagSeen.has(no)) { | ||
| tagSeen.add(no); | ||
| const extension = registry.getExtensionFor(desc, no); | ||
| if (!extension) { | ||
| continue; | ||
| } | ||
| return undefined; | ||
| const value = (0, extensions_js_1.getExtension)(message, extension); | ||
| const [container, field] = (0, extensions_js_1.createExtensionContainer)(extension, value); | ||
| const local = container[unsafe_js_1.unsafeLocal]; | ||
| const jsonValue = compileFieldValue(field)(opts, local[field.localName]); | ||
| if (jsonValue !== undefined) { | ||
| json[extension.jsonName] = jsonValue; | ||
| } | ||
| } | ||
| } | ||
@@ -296,6 +528,7 @@ } | ||
| } | ||
| const reflected = (0, reflect_js_1.reflect)(desc, message); | ||
| const json = (0, wrappers_js_1.hasCustomJsonRepresentation)(desc) | ||
| ? { value: tryWktToJson(reflected, opts) } | ||
| : reflectToJson(reflected, opts); | ||
| ? { | ||
| value: compiledWriter(desc)(opts, message), | ||
| } | ||
| : compiledWriter(desc)(opts, message); | ||
| json["@type"] = val.typeUrl; | ||
@@ -307,3 +540,3 @@ return json; | ||
| const nanos = val.nanos; | ||
| if (seconds > 315576000000 || seconds < -315576000000) { | ||
| if (seconds > json_js_1.durationSecondsMax || seconds < json_js_1.durationSecondsMin) { | ||
| throw new Error(`cannot encode message ${val.$typeName} to JSON: value out of range`); | ||
@@ -343,4 +576,6 @@ } | ||
| const json = {}; | ||
| for (const [k, v] of Object.entries(val.fields)) { | ||
| json[k] = valueToJson(v); | ||
| const keys = Object.keys(val.fields); | ||
| for (let i = 0; i < keys.length; i++) { | ||
| const key = keys[i]; | ||
| json[key] = valueToJson(val.fields[key]); | ||
| } | ||
@@ -375,4 +610,3 @@ return json; | ||
| const ms = Number(val.seconds) * 1000; | ||
| if (ms < Date.parse("0001-01-01T00:00:00Z") || | ||
| ms > Date.parse("9999-12-31T23:59:59Z")) { | ||
| if (ms < json_js_1.timestampMsMin || ms > json_js_1.timestampMsMax) { | ||
| throw new Error(`cannot encode message ${val.$typeName} to JSON: must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive`); | ||
@@ -379,0 +613,0 @@ } |
@@ -13,2 +13,3 @@ /** | ||
| export declare function base64Decode(base64Str: string): Uint8Array<ArrayBuffer>; | ||
| type Base64Encoding = "std" | "std_raw" | "url"; | ||
| /** | ||
@@ -24,2 +25,3 @@ * Encode a byte array to a base64 string. | ||
| */ | ||
| export declare function base64Encode(bytes: Uint8Array, encoding?: "std" | "std_raw" | "url"): string; | ||
| export declare function base64Encode(bytes: Uint8Array, encoding?: Base64Encoding): string; | ||
| export {}; |
@@ -18,2 +18,4 @@ "use strict"; | ||
| exports.base64Encode = base64Encode; | ||
| // Native Uint8Array.prototype.setFromBase64, if the runtime provides it. | ||
| const nativeSetFromBase64 = Uint8Array.prototype.setFromBase64; | ||
| /** | ||
@@ -31,10 +33,31 @@ * Decodes a base64 string to a byte array. | ||
| function base64Decode(base64Str) { | ||
| const len = base64Str.length; | ||
| // Decoded size, assuming a well-formed string: three bytes per group of | ||
| // four characters, minus one byte for each padding character. | ||
| let size = len - ((len + 3) >> 2); | ||
| if ((len & 3) == 0 && base64Str[len - 1] == "=") { | ||
| size -= base64Str[len - 2] == "=" ? 2 : 1; | ||
| } | ||
| const bytes = new Uint8Array(size); | ||
| let written = -1; | ||
| if (nativeSetFromBase64) { | ||
| try { | ||
| const result = nativeSetFromBase64.call(bytes, base64Str); | ||
| if (result.read == len) { | ||
| written = result.written; | ||
| } | ||
| } | ||
| catch (_a) { | ||
| // The native decoder rejects base64url and inner padding, which we accept. | ||
| } | ||
| } | ||
| if (written < 0) { | ||
| written = setFromBase64(bytes, base64Str); | ||
| } | ||
| return written == size ? bytes : bytes.subarray(0, written); | ||
| } | ||
| /** Writes into `bytes` from index 0 and returns the number of bytes written. */ | ||
| function setFromBase64(bytes, base64Str) { | ||
| const table = getDecodeTable(); | ||
| // estimate byte size, not accounting for inner padding and whitespace | ||
| let es = (base64Str.length * 3) / 4; | ||
| if (base64Str[base64Str.length - 2] == "=") | ||
| es -= 2; | ||
| else if (base64Str[base64Str.length - 1] == "=") | ||
| es -= 1; | ||
| let bytes = new Uint8Array(es), bytePos = 0, // position in byte array | ||
| let bytePos = 0, // position in byte array | ||
| groupPos = 0, // position in base64 group | ||
@@ -82,4 +105,10 @@ b, // current byte | ||
| throw Error("invalid base64 string"); | ||
| return bytes.subarray(0, bytePos); | ||
| return bytePos; | ||
| } | ||
| const nativeToBase64 = Uint8Array.prototype.toBase64; | ||
| const toBase64OptionsMap = { | ||
| std: { alphabet: "base64", omitPadding: false }, | ||
| std_raw: { alphabet: "base64", omitPadding: true }, | ||
| url: { alphabet: "base64url", omitPadding: true }, | ||
| }; | ||
| /** | ||
@@ -96,2 +125,5 @@ * Encode a byte array to a base64 string. | ||
| function base64Encode(bytes, encoding = "std") { | ||
| if (nativeToBase64) { | ||
| return nativeToBase64.call(bytes, toBase64OptionsMap[encoding]); | ||
| } | ||
| const table = getEncodeTable(encoding); | ||
@@ -98,0 +130,0 @@ const pad = encoding == "std"; |
@@ -63,26 +63,34 @@ /** | ||
| export declare class BinaryWriter { | ||
| private readonly encodeUtf8; | ||
| /** | ||
| * We cannot allocate a buffer for the entire output | ||
| * because we don't know its size. | ||
| * | ||
| * So we collect smaller chunks of known size and | ||
| * concat them later. | ||
| * | ||
| * Use `raw()` to push data to this array. It will flush | ||
| * `buf` first. | ||
| * Growable byte buffer. We allocate a reasonably sized | ||
| * initial buffer and double its capacity when needed. | ||
| */ | ||
| private chunks; | ||
| private buffer; | ||
| /** | ||
| * A growing buffer for byte values. If you don't know | ||
| * the size of the data you are writing, push to this | ||
| * array. | ||
| * Cached DataView for fixed-width writes. Read it via `view()`, which | ||
| * rebuilds it if `buffer` has since grown. | ||
| */ | ||
| protected buf: number[]; | ||
| private viewCache; | ||
| /** | ||
| * Previous fork states. | ||
| * Current write position in the buffer. | ||
| */ | ||
| private stack; | ||
| private pos; | ||
| /** | ||
| * Previous fork positions (the write position at the time | ||
| * `fork()` was called). | ||
| */ | ||
| private stackPos; | ||
| /** | ||
| * UTF-8 codec used by `string()`. Uses the text encoding's `encodeUtf8Into`, | ||
| * or emulates it if a custom `encodeUtf8` was passed to the constructor. | ||
| */ | ||
| private readonly encodeUtf8Into; | ||
| constructor(encodeUtf8?: (text: string) => Uint8Array); | ||
| private ensureCapacity; | ||
| /** | ||
| * The DataView over `buffer`, rebuilt only if the buffer has grown since it | ||
| * was last used. | ||
| */ | ||
| private view; | ||
| /** | ||
| * Return all bytes written and reset this writer. | ||
@@ -175,2 +183,10 @@ */ | ||
| uint64(value: string | number | bigint): this; | ||
| /** | ||
| * Write a 64-bit varint directly into the buffer. Accepts the value as | ||
| * split low/high 32-bit words. | ||
| * | ||
| * Ported from varint64write() to avoid the intermediate number[] buffer. | ||
| * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/writer.js#L344 | ||
| */ | ||
| private writeVarint64; | ||
| } | ||
@@ -187,3 +203,3 @@ export declare class BinaryReader { | ||
| readonly len: number; | ||
| protected readonly buf: Uint8Array; | ||
| private readonly buf; | ||
| private readonly view; | ||
@@ -205,7 +221,9 @@ constructor(buf: Uint8Array, decodeUtf8?: (bytes: Uint8Array, strict?: boolean) => string); | ||
| skip(wireType: WireType, fieldNo?: number, recursionLimit?: number): Uint8Array; | ||
| protected varint64: () => [number, number]; | ||
| private varint64Lo; | ||
| private varint64Hi; | ||
| private varint64; | ||
| /** | ||
| * Throws error if position in byte array is out of range. | ||
| */ | ||
| protected assertBounds(): void; | ||
| private assertBounds; | ||
| /** | ||
@@ -212,0 +230,0 @@ * Read a `uint32` field, an unsigned 32 bit varint. |
@@ -83,30 +83,51 @@ "use strict"; | ||
| class BinaryWriter { | ||
| constructor(encodeUtf8 = (0, text_encoding_js_1.getTextEncoding)().encodeUtf8) { | ||
| this.encodeUtf8 = encodeUtf8; | ||
| constructor(encodeUtf8) { | ||
| /** | ||
| * Previous fork states. | ||
| * Previous fork positions (the write position at the time | ||
| * `fork()` was called). | ||
| */ | ||
| this.stack = []; | ||
| this.chunks = []; | ||
| this.buf = []; | ||
| this.stackPos = []; | ||
| this.encodeUtf8Into = encodeUtf8 | ||
| ? (0, text_encoding_js_1.emulateEncodeInto)(encodeUtf8) | ||
| : (0, text_encoding_js_1.getTextEncoding)().encodeUtf8Into; | ||
| this.buffer = EMPTY_BUFFER; | ||
| this.viewCache = EMPTY_VIEW; | ||
| this.pos = 0; | ||
| } | ||
| ensureCapacity(size) { | ||
| const required = this.pos + size; | ||
| if (required > this.buffer.length) { | ||
| let newLen = this.buffer.length || INITIAL_SIZE; | ||
| while (newLen < required) | ||
| newLen *= 2; | ||
| const newBuf = new Uint8Array(newLen); | ||
| if (this.pos > 0) | ||
| newBuf.set(this.buffer); | ||
| this.buffer = newBuf; | ||
| } | ||
| } | ||
| /** | ||
| * The DataView over `buffer`, rebuilt only if the buffer has grown since it | ||
| * was last used. | ||
| */ | ||
| view() { | ||
| const bytes = this.buffer; | ||
| const view = this.viewCache; | ||
| // Since ensureCapacity() only ever replaces the buffer with a strictly larger one, | ||
| // equal lengths mean the view is still current. This is faster than comparing | ||
| // buffers directly. | ||
| if (view.byteLength === bytes.byteLength) | ||
| return view; | ||
| const newView = new DataView(bytes.buffer); | ||
| this.viewCache = newView; | ||
| return newView; | ||
| } | ||
| /** | ||
| * Return all bytes written and reset this writer. | ||
| */ | ||
| finish() { | ||
| if (this.buf.length) { | ||
| this.chunks.push(new Uint8Array(this.buf)); // flush the buffer | ||
| this.buf = []; | ||
| } | ||
| let len = 0; | ||
| for (let i = 0; i < this.chunks.length; i++) | ||
| len += this.chunks[i].length; | ||
| let bytes = new Uint8Array(len); | ||
| let offset = 0; | ||
| for (let i = 0; i < this.chunks.length; i++) { | ||
| bytes.set(this.chunks[i], offset); | ||
| offset += this.chunks[i].length; | ||
| } | ||
| this.chunks = []; | ||
| return bytes; | ||
| const result = this.buffer.slice(0, this.pos); | ||
| this.pos = 0; | ||
| this.stackPos = []; | ||
| return result; | ||
| } | ||
@@ -120,5 +141,7 @@ /** | ||
| fork() { | ||
| this.stack.push({ chunks: this.chunks, buf: this.buf }); | ||
| this.chunks = []; | ||
| this.buf = []; | ||
| this.stackPos.push(this.pos); | ||
| // Reserve room for the length prefix. Payloads under 128 bytes, fairly | ||
| // common, will need no copy in join(). | ||
| this.ensureCapacity(DEFAULT_LEN_PREFIX_SIZE); | ||
| this.buffer[this.pos++] = 0; | ||
| return this; | ||
@@ -131,13 +154,20 @@ } | ||
| join() { | ||
| // get chunk of fork | ||
| let chunk = this.finish(); | ||
| // restore previous state | ||
| let prev = this.stack.pop(); | ||
| if (!prev) | ||
| const forkPos = this.stackPos.pop(); | ||
| if (forkPos === undefined) | ||
| throw new Error("invalid state, fork stack empty"); | ||
| this.chunks = prev.chunks; | ||
| this.buf = prev.buf; | ||
| // write length of chunk as varint | ||
| this.uint32(chunk.byteLength); | ||
| return this.raw(chunk); | ||
| // fork() presumed the payload would fit the prefix it reserved. If it | ||
| // doesn't, we need to shift the bytes we just wrote. | ||
| const len = this.pos - forkPos - DEFAULT_LEN_PREFIX_SIZE; | ||
| const lenPrefixSize = varint32Size(len); | ||
| if (lenPrefixSize > DEFAULT_LEN_PREFIX_SIZE) { | ||
| // Widening pushes the payload past the end of the buffer, so grow first: | ||
| // copyWithin clamps to the buffer instead of throwing, so a short buffer | ||
| // would silently drop the tail of the payload. | ||
| this.ensureCapacity(lenPrefixSize - DEFAULT_LEN_PREFIX_SIZE); | ||
| this.buffer.copyWithin(forkPos + lenPrefixSize, forkPos + DEFAULT_LEN_PREFIX_SIZE, this.pos); | ||
| } | ||
| this.pos = forkPos; | ||
| this.uint32(len); | ||
| this.pos += len; | ||
| return this; | ||
| } | ||
@@ -158,7 +188,5 @@ /** | ||
| raw(chunk) { | ||
| if (this.buf.length) { | ||
| this.chunks.push(new Uint8Array(this.buf)); | ||
| this.buf = []; | ||
| } | ||
| this.chunks.push(chunk); | ||
| this.ensureCapacity(chunk.length); | ||
| this.buffer.set(chunk, this.pos); | ||
| this.pos += chunk.length; | ||
| return this; | ||
@@ -171,8 +199,14 @@ } | ||
| assertUInt32(value); | ||
| // write value as varint 32, inlined for speed | ||
| // uint32 varints are at most 5 bytes; reserve once and avoid per-byte | ||
| // capacity checks. | ||
| this.ensureCapacity(5); | ||
| if (value < 0x80) { | ||
| this.buffer[this.pos++] = value; | ||
| return this; | ||
| } | ||
| while (value > 0x7f) { | ||
| this.buf.push((value & 0x7f) | 0x80); | ||
| value = value >>> 7; | ||
| this.buffer[this.pos++] = (value & 0x7f) | 0x80; | ||
| value >>>= 7; | ||
| } | ||
| this.buf.push(value); | ||
| this.buffer[this.pos++] = value; | ||
| return this; | ||
@@ -185,3 +219,12 @@ } | ||
| assertInt32(value); | ||
| (0, varint_js_1.varint32write)(value, this.buf); | ||
| if (value >= 0) { | ||
| return this.uint32(value); | ||
| } | ||
| // Negative: sign-extend to 64 bits, encodes to 10 bytes. | ||
| this.ensureCapacity(10); | ||
| for (let i = 0; i < 9; i++) { | ||
| this.buffer[this.pos++] = (value & 0x7f) | 0x80; | ||
| value >>= 7; | ||
| } | ||
| this.buffer[this.pos++] = 1; | ||
| return this; | ||
@@ -193,3 +236,4 @@ } | ||
| bool(value) { | ||
| this.buf.push(value ? 1 : 0); | ||
| this.ensureCapacity(1); | ||
| this.buffer[this.pos++] = value ? 1 : 0; | ||
| return this; | ||
@@ -201,3 +245,3 @@ } | ||
| bytes(value) { | ||
| this.uint32(value.byteLength); // write length of chunk as varint | ||
| this.uint32(value.byteLength); | ||
| return this.raw(value); | ||
@@ -209,5 +253,45 @@ } | ||
| string(value) { | ||
| let chunk = this.encodeUtf8(value); | ||
| this.uint32(chunk.byteLength); // write length of chunk as varint | ||
| return this.raw(chunk); | ||
| // TextEncoder.encode() coerces its argument to string, but encodeInto() | ||
| // rejects non-strings. | ||
| if (typeof value !== "string") { | ||
| value = String(value); | ||
| } | ||
| const len = value.length; | ||
| // Fast path for ASCII. | ||
| if (len <= ASCII_MAX_LENGTH) { | ||
| this.ensureCapacity(len + 1); | ||
| const ascii = this.buffer; | ||
| let pos = this.pos; | ||
| ascii[pos++] = len; | ||
| let i = 0; | ||
| for (; i < len; i++) { | ||
| const code = value.charCodeAt(i); | ||
| if (code > 0x7f) | ||
| break; | ||
| ascii[pos++] = code; | ||
| } | ||
| if (i == len) { | ||
| this.pos = pos; | ||
| return this; | ||
| } | ||
| } | ||
| // encodeUtf8Into needs the full-length buffer upfront. The length prefix | ||
| // can be upto 5 bytes, and a UTF-16 code unit takes at most 3 UTF-8 bytes. | ||
| this.ensureCapacity(len * 3 + 5); | ||
| // The length prefix goes first, but the byte length is only known after | ||
| // encoding. We guess the final varint size here (assuming most text is | ||
| // ASCII) and then encode. | ||
| const lenPrefixSizeGuess = varint32Size(len); | ||
| const buf = this.buffer; | ||
| const start = this.pos; | ||
| const { written } = this.encodeUtf8Into(value, buf.subarray(start + lenPrefixSizeGuess)); | ||
| // If our guess was incorrect, we need to shift the bytes we just wrote. | ||
| const lenPrefixSize = varint32Size(written); | ||
| if (lenPrefixSize != lenPrefixSizeGuess) { | ||
| buf.copyWithin(start + lenPrefixSize, start + lenPrefixSizeGuess, start + lenPrefixSizeGuess + written); | ||
| } | ||
| // Write the lenPrefix and advance the pos. | ||
| this.uint32(written); | ||
| this.pos += written; | ||
| return this; | ||
| } | ||
@@ -219,5 +303,6 @@ /** | ||
| assertFloat32(value); | ||
| let chunk = new Uint8Array(4); | ||
| new DataView(chunk.buffer).setFloat32(0, value, true); | ||
| return this.raw(chunk); | ||
| this.ensureCapacity(4); | ||
| this.view().setFloat32(this.pos, value, true); | ||
| this.pos += 4; | ||
| return this; | ||
| } | ||
@@ -228,5 +313,6 @@ /** | ||
| double(value) { | ||
| let chunk = new Uint8Array(8); | ||
| new DataView(chunk.buffer).setFloat64(0, value, true); | ||
| return this.raw(chunk); | ||
| this.ensureCapacity(8); | ||
| this.view().setFloat64(this.pos, value, true); | ||
| this.pos += 8; | ||
| return this; | ||
| } | ||
@@ -238,5 +324,6 @@ /** | ||
| assertUInt32(value); | ||
| let chunk = new Uint8Array(4); | ||
| new DataView(chunk.buffer).setUint32(0, value, true); | ||
| return this.raw(chunk); | ||
| this.ensureCapacity(4); | ||
| this.view().setUint32(this.pos, value, true); | ||
| this.pos += 4; | ||
| return this; | ||
| } | ||
@@ -248,5 +335,6 @@ /** | ||
| assertInt32(value); | ||
| let chunk = new Uint8Array(4); | ||
| new DataView(chunk.buffer).setInt32(0, value, true); | ||
| return this.raw(chunk); | ||
| this.ensureCapacity(4); | ||
| this.view().setInt32(this.pos, value, true); | ||
| this.pos += 4; | ||
| return this; | ||
| } | ||
@@ -258,6 +346,4 @@ /** | ||
| assertInt32(value); | ||
| // zigzag encode | ||
| value = ((value << 1) ^ (value >> 31)) >>> 0; | ||
| (0, varint_js_1.varint32write)(value, this.buf); | ||
| return this; | ||
| // zigzag encode then emit as uint32 varint | ||
| return this.uint32(((value << 1) ^ (value >> 31)) >>> 0); | ||
| } | ||
@@ -268,6 +354,9 @@ /** | ||
| sfixed64(value) { | ||
| let chunk = new Uint8Array(8), view = new DataView(chunk.buffer), tc = proto_int64_js_1.protoInt64.enc(value); | ||
| view.setInt32(0, tc.lo, true); | ||
| view.setInt32(4, tc.hi, true); | ||
| return this.raw(chunk); | ||
| const tc = proto_int64_js_1.protoInt64.enc(value); | ||
| this.ensureCapacity(8); | ||
| const view = this.view(); | ||
| view.setInt32(this.pos, tc.lo, true); | ||
| view.setInt32(this.pos + 4, tc.hi, true); | ||
| this.pos += 8; | ||
| return this; | ||
| } | ||
@@ -278,6 +367,9 @@ /** | ||
| fixed64(value) { | ||
| let chunk = new Uint8Array(8), view = new DataView(chunk.buffer), tc = proto_int64_js_1.protoInt64.uEnc(value); | ||
| view.setInt32(0, tc.lo, true); | ||
| view.setInt32(4, tc.hi, true); | ||
| return this.raw(chunk); | ||
| const tc = proto_int64_js_1.protoInt64.uEnc(value); | ||
| this.ensureCapacity(8); | ||
| const view = this.view(); | ||
| view.setInt32(this.pos, tc.lo, true); | ||
| view.setInt32(this.pos + 4, tc.hi, true); | ||
| this.pos += 8; | ||
| return this; | ||
| } | ||
@@ -288,5 +380,4 @@ /** | ||
| int64(value) { | ||
| let tc = proto_int64_js_1.protoInt64.enc(value); | ||
| (0, varint_js_1.varint64write)(tc.lo, tc.hi, this.buf); | ||
| return this; | ||
| const tc = proto_int64_js_1.protoInt64.enc(value); | ||
| return this.writeVarint64(tc.lo, tc.hi); | ||
| } | ||
@@ -300,4 +391,3 @@ /** | ||
| sign = tc.hi >> 31, lo = (tc.lo << 1) ^ sign, hi = ((tc.hi << 1) | (tc.lo >>> 31)) ^ sign; | ||
| (0, varint_js_1.varint64write)(lo, hi, this.buf); | ||
| return this; | ||
| return this.writeVarint64(lo, hi); | ||
| } | ||
@@ -309,3 +399,43 @@ /** | ||
| const tc = proto_int64_js_1.protoInt64.uEnc(value); | ||
| (0, varint_js_1.varint64write)(tc.lo, tc.hi, this.buf); | ||
| return this.writeVarint64(tc.lo, tc.hi); | ||
| } | ||
| /** | ||
| * Write a 64-bit varint directly into the buffer. Accepts the value as | ||
| * split low/high 32-bit words. | ||
| * | ||
| * Ported from varint64write() to avoid the intermediate number[] buffer. | ||
| * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/writer.js#L344 | ||
| */ | ||
| writeVarint64(lo, hi) { | ||
| // Worst case: 10 bytes. | ||
| this.ensureCapacity(10); | ||
| const buf = this.buffer; | ||
| let pos = this.pos; | ||
| for (let i = 0; i < 28; i = i + 7) { | ||
| const shift = lo >>> i; | ||
| const hasNext = !(shift >>> 7 == 0 && hi == 0); | ||
| buf[pos++] = (hasNext ? shift | 0x80 : shift) & 0xff; | ||
| if (!hasNext) { | ||
| this.pos = pos; | ||
| return this; | ||
| } | ||
| } | ||
| const splitBits = ((lo >>> 28) & 0x0f) | ((hi & 0x07) << 4); | ||
| const hasMoreBits = !(hi >> 3 == 0); | ||
| buf[pos++] = (hasMoreBits ? splitBits | 0x80 : splitBits) & 0xff; | ||
| if (!hasMoreBits) { | ||
| this.pos = pos; | ||
| return this; | ||
| } | ||
| for (let i = 3; i < 31; i = i + 7) { | ||
| const shift = hi >>> i; | ||
| const hasNext = !(shift >>> 7 == 0); | ||
| buf[pos++] = (hasNext ? shift | 0x80 : shift) & 0xff; | ||
| if (!hasNext) { | ||
| this.pos = pos; | ||
| return this; | ||
| } | ||
| } | ||
| buf[pos++] = (hi >>> 31) & 0x01; | ||
| this.pos = pos; | ||
| return this; | ||
@@ -315,5 +445,47 @@ } | ||
| exports.BinaryWriter = BinaryWriter; | ||
| /** | ||
| * Capacity of the buffer allocated by the first write.. | ||
| */ | ||
| const INITIAL_SIZE = 128; | ||
| /** | ||
| * Bytes `fork()` reserves for the length prefix, betting that the payload will | ||
| * be under 128 bytes. `join()` fills them in, and widens them if the bet was | ||
| * wrong. | ||
| */ | ||
| const DEFAULT_LEN_PREFIX_SIZE = 1; | ||
| /** | ||
| * Shared empty buffer used as the initial value before the first write. | ||
| * Avoids allocating and zeroing `INITIAL_SIZE` bytes per BinaryWriter when a | ||
| * writer is only used for a tiny message (or not used at all). | ||
| */ | ||
| const EMPTY_BUFFER = new Uint8Array(0); | ||
| /** | ||
| * Shared empty view, paired with `EMPTY_BUFFER`. Never written to: any | ||
| * fixed-width write first grows the buffer, which replaces this view. | ||
| */ | ||
| const EMPTY_VIEW = new DataView(EMPTY_BUFFER.buffer); | ||
| /** | ||
| * Longest string on the ASCII fast paths. Must stay below 0x80, so | ||
| * that the writer's length prefix always fits a single varint byte. | ||
| */ | ||
| const ASCII_MAX_LENGTH = 32; | ||
| /** | ||
| * Number of bytes needed to encode `value` as an unsigned 32-bit varint. | ||
| */ | ||
| function varint32Size(value) { | ||
| if (value < 0x80) | ||
| return 1; | ||
| if (value < 0x4000) | ||
| return 2; | ||
| if (value < 0x200000) | ||
| return 3; | ||
| if (value < 0x10000000) | ||
| return 4; | ||
| return 5; | ||
| } | ||
| class BinaryReader { | ||
| constructor(buf, decodeUtf8 = (0, text_encoding_js_1.getTextEncoding)().decodeUtf8) { | ||
| this.decodeUtf8 = decodeUtf8; | ||
| this.varint64Lo = 0; | ||
| this.varint64Hi = 0; | ||
| this.varint64 = varint_js_1.varint64read; // dirty cast for `this` | ||
@@ -419,3 +591,4 @@ /** | ||
| int64() { | ||
| return proto_int64_js_1.protoInt64.dec(...this.varint64()); | ||
| this.varint64(); | ||
| return proto_int64_js_1.protoInt64.dec(this.varint64Lo, this.varint64Hi); | ||
| } | ||
@@ -426,3 +599,4 @@ /** | ||
| uint64() { | ||
| return proto_int64_js_1.protoInt64.uDec(...this.varint64()); | ||
| this.varint64(); | ||
| return proto_int64_js_1.protoInt64.uDec(this.varint64Lo, this.varint64Hi); | ||
| } | ||
@@ -433,3 +607,5 @@ /** | ||
| sint64() { | ||
| let [lo, hi] = this.varint64(); | ||
| this.varint64(); | ||
| let lo = this.varint64Lo; | ||
| let hi = this.varint64Hi; | ||
| // decode zig zag | ||
@@ -445,4 +621,10 @@ let s = -(lo & 1); | ||
| bool() { | ||
| let [lo, hi] = this.varint64(); | ||
| return lo !== 0 || hi !== 0; | ||
| // Fast path: most bools are 0x0 or 0x1. | ||
| const b = this.buf[this.pos]; | ||
| if (b < 0x80) { | ||
| this.pos++; | ||
| return b !== 0; | ||
| } | ||
| this.varint64(); | ||
| return this.varint64Lo !== 0 || this.varint64Hi !== 0; | ||
| } | ||
@@ -503,3 +685,17 @@ /** | ||
| string(strict) { | ||
| return this.decodeUtf8(this.bytes(), strict); | ||
| const bytes = this.bytes(); | ||
| const len = bytes.length; | ||
| // Fast path for ASCII. | ||
| if (len <= ASCII_MAX_LENGTH) { | ||
| const codes = new Array(len); | ||
| for (let i = 0; i < len; i++) { | ||
| const byte = bytes[i]; | ||
| if (byte > 0x7f) { | ||
| return this.decodeUtf8(bytes, strict); | ||
| } | ||
| codes[i] = byte; | ||
| } | ||
| return String.fromCharCode.apply(String, codes); | ||
| } | ||
| return this.decodeUtf8(bytes, strict); | ||
| } | ||
@@ -506,0 +702,0 @@ } |
| export * from "./binary-encoding.js"; | ||
| export * from "./base64-encoding.js"; | ||
| export * from "./text-encoding.js"; | ||
| export { getTextEncoding, configureTextEncoding } from "./text-encoding.js"; | ||
| export * from "./text-format.js"; | ||
| export * from "./size-delimited.js"; |
@@ -30,6 +30,9 @@ "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.configureTextEncoding = exports.getTextEncoding = void 0; | ||
| __exportStar(require("./binary-encoding.js"), exports); | ||
| __exportStar(require("./base64-encoding.js"), exports); | ||
| __exportStar(require("./text-encoding.js"), exports); | ||
| var text_encoding_js_1 = require("./text-encoding.js"); | ||
| Object.defineProperty(exports, "getTextEncoding", { enumerable: true, get: function () { return text_encoding_js_1.getTextEncoding; } }); | ||
| Object.defineProperty(exports, "configureTextEncoding", { enumerable: true, get: function () { return text_encoding_js_1.configureTextEncoding; } }); | ||
| __exportStar(require("./text-format.js"), exports); | ||
| __exportStar(require("./size-delimited.js"), exports); |
@@ -11,2 +11,8 @@ interface TextEncoding { | ||
| /** | ||
| * Encode UTF-8 text to a Uint8Array. The destination must be large enough. | ||
| */ | ||
| encodeUtf8Into: (text: string, dest: Uint8Array) => { | ||
| written: number; | ||
| }; | ||
| /** | ||
| * Decode UTF-8 text from binary. If `strict` is true, throw on invalid byte | ||
@@ -18,2 +24,3 @@ * sequences instead of silently substituting U+FFFD. Implementations that | ||
| } | ||
| type TextEncodingConfig = Omit<TextEncoding, "encodeUtf8Into"> & Partial<Pick<TextEncoding, "encodeUtf8Into">>; | ||
| /** | ||
@@ -25,7 +32,17 @@ * Protobuf-ES requires the Text Encoding API to convert UTF-8 from and to | ||
| * | ||
| * Providing `encodeUtf8Into` is optional for backwards compatibility. If it | ||
| * is omitted, we emulate it with a wrapper that calls `encodeUtf8`. | ||
| * | ||
| * Note that the Text Encoding API does not provide a way to validate UTF-8. | ||
| * Our implementation falls back to use encodeURIComponent(). | ||
| * Our implementation uses String.prototype.isWellFormed, and falls back | ||
| * to use encodeURIComponent(). | ||
| */ | ||
| export declare function configureTextEncoding(textEncoding: TextEncoding): void; | ||
| export declare function configureTextEncoding(textEncoding: TextEncodingConfig): void; | ||
| export declare function getTextEncoding(): TextEncoding; | ||
| /** | ||
| * Simplistic polyfill for encodeUtf8Into. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare function emulateEncodeInto(encodeUtf8: (str: string) => Uint8Array): TextEncoding["encodeUtf8Into"]; | ||
| export {}; |
@@ -18,2 +18,3 @@ "use strict"; | ||
| exports.getTextEncoding = getTextEncoding; | ||
| exports.emulateEncodeInto = emulateEncodeInto; | ||
| const symbol = Symbol.for("@bufbuild/protobuf/text-encoding"); | ||
@@ -26,25 +27,33 @@ /** | ||
| * | ||
| * Providing `encodeUtf8Into` is optional for backwards compatibility. If it | ||
| * is omitted, we emulate it with a wrapper that calls `encodeUtf8`. | ||
| * | ||
| * Note that the Text Encoding API does not provide a way to validate UTF-8. | ||
| * Our implementation falls back to use encodeURIComponent(). | ||
| * Our implementation uses String.prototype.isWellFormed, and falls back | ||
| * to use encodeURIComponent(). | ||
| */ | ||
| function configureTextEncoding(textEncoding) { | ||
| globalThis[symbol] = textEncoding; | ||
| var _a; | ||
| globalThis[symbol] = Object.assign(Object.assign({}, textEncoding), { encodeUtf8Into: (_a = textEncoding.encodeUtf8Into) !== null && _a !== void 0 ? _a : emulateEncodeInto(textEncoding.encodeUtf8.bind(textEncoding)) }); | ||
| } | ||
| function getTextEncoding() { | ||
| if (globalThis[symbol] == undefined) { | ||
| const te = new globalThis.TextEncoder(); | ||
| const td = new globalThis.TextDecoder(); | ||
| let tdStrict; | ||
| globalThis[symbol] = { | ||
| const globals = globalThis; | ||
| if (!globals[symbol]) { | ||
| const textEncoder = new globals.TextEncoder(); | ||
| const textDecoder = new globals.TextDecoder(); | ||
| let textDecoderStrict; | ||
| const config = { | ||
| encodeUtf8(text) { | ||
| return te.encode(text); | ||
| return textEncoder.encode(text); | ||
| }, | ||
| decodeUtf8(bytes, strict) { | ||
| if (strict) { | ||
| if (tdStrict === undefined) { | ||
| tdStrict = new globalThis.TextDecoder("utf-8", { fatal: true }); | ||
| if (!textDecoderStrict) { | ||
| textDecoderStrict = new globals.TextDecoder("utf-8", { | ||
| fatal: true, | ||
| }); | ||
| } | ||
| return tdStrict.decode(bytes); | ||
| return textDecoderStrict.decode(bytes); | ||
| } | ||
| return td.decode(bytes); | ||
| return textDecoder.decode(bytes); | ||
| }, | ||
@@ -61,4 +70,29 @@ checkUtf8(text) { | ||
| }; | ||
| // If encodeInto is available, use it. Otherwise, configureTextEncoding | ||
| // fills in a slower fallback that uses encodeUtf8. | ||
| if (textEncoder.encodeInto) { | ||
| config.encodeUtf8Into = textEncoder.encodeInto.bind(textEncoder); | ||
| } | ||
| // Native String.prototype.isWellFormed, if the runtime provides it. | ||
| const nativeStringIsWellFormed = String.prototype.isWellFormed; | ||
| if (nativeStringIsWellFormed) { | ||
| config.checkUtf8 = (text) => { | ||
| return nativeStringIsWellFormed.call(text); | ||
| }; | ||
| } | ||
| configureTextEncoding(config); | ||
| } | ||
| return globalThis[symbol]; | ||
| return globals[symbol]; | ||
| } | ||
| /** | ||
| * Simplistic polyfill for encodeUtf8Into. | ||
| * | ||
| * @private | ||
| */ | ||
| function emulateEncodeInto(encodeUtf8) { | ||
| return (text, dest) => { | ||
| const bytes = encodeUtf8(text); | ||
| dest.set(bytes); | ||
| return { written: bytes.byteLength }; | ||
| }; | ||
| } |
| /** | ||
| * Read a 64 bit varint as two JS numbers. | ||
| * | ||
| * Returns tuple: | ||
| * [0]: low bits | ||
| * [1]: high bits | ||
| * Stores the low and high words on the reader. | ||
| * | ||
@@ -12,3 +10,3 @@ * Copyright 2008 Google Inc. All rights reserved. | ||
| */ | ||
| export declare function varint64read<T extends ReaderLike>(this: T): [number, number]; | ||
| export declare function varint64read<T extends ReaderLike>(this: T): void; | ||
| /** | ||
@@ -69,4 +67,6 @@ * Write a 64 bit varint, given as two JS numbers, to the given bytes array. | ||
| len: number; | ||
| varint64Lo: number; | ||
| varint64Hi: number; | ||
| assertBounds(): void; | ||
| }; | ||
| export {}; |
@@ -45,5 +45,3 @@ "use strict"; | ||
| * | ||
| * Returns tuple: | ||
| * [0]: low bits | ||
| * [1]: high bits | ||
| * Stores the low and high words on the reader. | ||
| * | ||
@@ -55,27 +53,38 @@ * Copyright 2008 Google Inc. All rights reserved. | ||
| function varint64read() { | ||
| let lowBits = 0; | ||
| let highBits = 0; | ||
| const buf = this.buf; | ||
| let pos = this.pos; | ||
| let lo = 0; | ||
| let hi = 0; | ||
| for (let shift = 0; shift < 28; shift += 7) { | ||
| let b = this.buf[this.pos++]; | ||
| lowBits |= (b & 0x7f) << shift; | ||
| const b = buf[pos++]; | ||
| lo |= (b & 0x7f) << shift; | ||
| if ((b & 0x80) == 0) { | ||
| this.pos = pos; | ||
| this.assertBounds(); | ||
| return [lowBits, highBits]; | ||
| this.varint64Lo = lo; | ||
| this.varint64Hi = hi; | ||
| return; | ||
| } | ||
| } | ||
| let middleByte = this.buf[this.pos++]; | ||
| const middleByte = buf[pos++]; | ||
| // last four bits of the first 32 bit number | ||
| lowBits |= (middleByte & 0x0f) << 28; | ||
| lo |= (middleByte & 0x0f) << 28; | ||
| // 3 upper bits are part of the next 32 bit number | ||
| highBits = (middleByte & 0x70) >> 4; | ||
| hi = (middleByte & 0x70) >> 4; | ||
| if ((middleByte & 0x80) == 0) { | ||
| this.pos = pos; | ||
| this.assertBounds(); | ||
| return [lowBits, highBits]; | ||
| this.varint64Lo = lo; | ||
| this.varint64Hi = hi; | ||
| return; | ||
| } | ||
| for (let shift = 3; shift <= 31; shift += 7) { | ||
| let b = this.buf[this.pos++]; | ||
| highBits |= (b & 0x7f) << shift; | ||
| const b = buf[pos++]; | ||
| hi |= (b & 0x7f) << shift; | ||
| if ((b & 0x80) == 0) { | ||
| this.pos = pos; | ||
| this.assertBounds(); | ||
| return [lowBits, highBits]; | ||
| this.varint64Lo = lo; | ||
| this.varint64Hi = hi; | ||
| return; | ||
| } | ||
@@ -268,2 +277,6 @@ } | ||
| function varint32write(value, bytes) { | ||
| if (value >>> 0 < 0x80) { | ||
| bytes.push(value); | ||
| return; | ||
| } | ||
| if (value >= 0) { | ||
@@ -292,10 +305,10 @@ // write value as varint 32 | ||
| let b = this.buf[this.pos++]; | ||
| let result = b & 0x7f; | ||
| if ((b & 0x80) == 0) { | ||
| if ((b & 0x80) === 0) { | ||
| this.assertBounds(); | ||
| return result; | ||
| return b; | ||
| } | ||
| let result = b & 0x7f; | ||
| b = this.buf[this.pos++]; | ||
| result |= (b & 0x7f) << 7; | ||
| if ((b & 0x80) == 0) { | ||
| if ((b & 0x80) === 0) { | ||
| this.assertBounds(); | ||
@@ -306,3 +319,3 @@ return result; | ||
| result |= (b & 0x7f) << 14; | ||
| if ((b & 0x80) == 0) { | ||
| if ((b & 0x80) === 0) { | ||
| this.assertBounds(); | ||
@@ -313,3 +326,3 @@ return result; | ||
| result |= (b & 0x7f) << 21; | ||
| if ((b & 0x80) == 0) { | ||
| if ((b & 0x80) === 0) { | ||
| this.assertBounds(); | ||
@@ -323,7 +336,6 @@ return result; | ||
| b = this.buf[this.pos++]; | ||
| if ((b & 0x80) != 0) | ||
| if ((b & 0x80) !== 0) | ||
| throw new Error("invalid varint"); | ||
| this.assertBounds(); | ||
| // Result can have 32 bits, convert it to unsigned | ||
| return result >>> 0; | ||
| } |
@@ -53,15 +53,15 @@ "use strict"; | ||
| } | ||
| const wrapperTypeNames = /*@__PURE__*/ new Set([ | ||
| "google.protobuf.DoubleValue", | ||
| "google.protobuf.FloatValue", | ||
| "google.protobuf.Int64Value", | ||
| "google.protobuf.UInt64Value", | ||
| "google.protobuf.Int32Value", | ||
| "google.protobuf.UInt32Value", | ||
| "google.protobuf.BoolValue", | ||
| "google.protobuf.StringValue", | ||
| "google.protobuf.BytesValue", | ||
| ]); | ||
| function isWrapperTypeName(name) { | ||
| return (name.startsWith("google.protobuf.") && | ||
| [ | ||
| "DoubleValue", | ||
| "FloatValue", | ||
| "Int64Value", | ||
| "UInt64Value", | ||
| "Int32Value", | ||
| "UInt32Value", | ||
| "BoolValue", | ||
| "StringValue", | ||
| "BytesValue", | ||
| ].includes(name.substring(16))); | ||
| return wrapperTypeNames.has(name); | ||
| } |
+199
-162
@@ -18,3 +18,2 @@ // Copyright 2021-2026 Buf Technologies, Inc. | ||
| import { isObject } from "./reflect/guard.js"; | ||
| import { unsafeGet, unsafeOneofCase, unsafeSet } from "./reflect/unsafe.js"; | ||
| import { isWrapperDesc } from "./wkt/wrappers.js"; | ||
@@ -37,77 +36,197 @@ // bootstrap-inject google.protobuf.Edition.EDITION_PROTO3: const $name = $number; | ||
| } | ||
| const message = createZeroMessage(schema); | ||
| if (init !== undefined) { | ||
| initMessage(schema, message, init); | ||
| return compiledCreate(schema)(init); | ||
| } | ||
| const compiledCreates = new WeakMap(); | ||
| /** | ||
| * Return the compiled create function for a message, compiling it on first use. */ | ||
| function compiledCreate(desc) { | ||
| let compiled = compiledCreates.get(desc); | ||
| if (compiled === undefined) { | ||
| compiled = compileCreate(desc); | ||
| compiledCreates.set(desc, compiled); | ||
| } | ||
| return message; | ||
| return compiled; | ||
| } | ||
| /** Singular field: scalar, enum, or message. */ | ||
| const INIT_SINGULAR = 0; | ||
| /** List field: a zero message has a fresh empty array. */ | ||
| const INIT_LIST = 1; | ||
| /** Map field: a zero message has a fresh empty object. */ | ||
| const INIT_MAP = 2; | ||
| /** Oneof group: the ADT is always stored, cases convert by case name. */ | ||
| const INIT_ONEOF = 3; | ||
| /* Compile the create function for this message type. */ | ||
| function compileCreate(desc) { | ||
| const typeName = desc.typeName; | ||
| const { properties, prototype } = compileInitMessage(desc); | ||
| return (init) => { | ||
| let message; | ||
| if (prototype !== undefined) { | ||
| message = Object.create(prototype); | ||
| message.$typeName = typeName; | ||
| } | ||
| else { | ||
| message = { $typeName: typeName }; | ||
| } | ||
| for (let i = 0; i < properties.length; i++) { | ||
| const property = properties[i]; | ||
| const name = property.name; | ||
| const initValue = init === null || init === void 0 ? void 0 : init[name]; | ||
| switch (property.kind) { | ||
| case INIT_SINGULAR: | ||
| if (initValue != null) { | ||
| message[name] = | ||
| property.convert !== undefined | ||
| ? property.convert(initValue) | ||
| : initValue; | ||
| } | ||
| else if (property.constant !== undefined) { | ||
| message[name] = property.constant; | ||
| } | ||
| break; | ||
| case INIT_LIST: | ||
| message[name] = | ||
| property.convert !== undefined && Array.isArray(initValue) | ||
| ? initValue.map(property.convert) | ||
| : (initValue !== null && initValue !== void 0 ? initValue : []); | ||
| break; | ||
| case INIT_MAP: | ||
| // Object.create(null) would be desirable for the fresh map, but is | ||
| // unsupported by React: | ||
| // https://react.dev/reference/react/use-server#serializable-parameters-and-return-values | ||
| if (property.convert === undefined || !isObject(initValue)) { | ||
| message[name] = initValue !== null && initValue !== void 0 ? initValue : {}; | ||
| } | ||
| else { | ||
| const converted = {}; | ||
| const keys = Object.keys(initValue); | ||
| for (let k = 0; k < keys.length; k++) { | ||
| converted[keys[k]] = property.convert(initValue[keys[k]]); | ||
| } | ||
| message[name] = converted; | ||
| } | ||
| break; | ||
| case INIT_ONEOF: { | ||
| const oneofValue = initValue; | ||
| if ((oneofValue === null || oneofValue === void 0 ? void 0 : oneofValue.case) != null) { | ||
| const convert = property.convert.get(oneofValue.case); | ||
| if (convert !== undefined) { | ||
| message[name] = { | ||
| case: oneofValue.case, | ||
| value: convert(oneofValue.value), | ||
| }; | ||
| break; | ||
| } | ||
| } | ||
| message[name] = { case: undefined }; | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| return message; | ||
| }; | ||
| } | ||
| /** | ||
| * Sets field values from a MessageInitShape on a zero message. | ||
| * Classify every member once, so that creating a message is a walk over a | ||
| * compact list instead of a walk over the descriptor. | ||
| */ | ||
| function initMessage(messageDesc, message, init) { | ||
| for (const member of messageDesc.members) { | ||
| let value = init[member.localName]; | ||
| if (value == null) { | ||
| // intentionally ignore undefined and null | ||
| function compileInitMessage(desc) { | ||
| var _a, _b; | ||
| const properties = []; | ||
| const prototype = {}; | ||
| const usePrototype = needsPrototypeChain(desc); | ||
| for (const member of desc.members) { | ||
| const name = member.localName; | ||
| if (member.kind == "oneof") { | ||
| properties.push({ | ||
| name, | ||
| kind: INIT_ONEOF, | ||
| constant: undefined, | ||
| convert: compileConvertOneof(member), | ||
| }); | ||
| continue; | ||
| } | ||
| let field; | ||
| if (member.kind == "oneof") { | ||
| const oneofField = unsafeOneofCase(init, member); | ||
| if (!oneofField) { | ||
| continue; | ||
| switch (member.fieldKind) { | ||
| case "message": { | ||
| // Singular message fields are absent from a zero message. | ||
| properties.push({ | ||
| name, | ||
| kind: INIT_SINGULAR, | ||
| constant: undefined, | ||
| convert: compileConvertMessage(member), | ||
| }); | ||
| break; | ||
| } | ||
| field = oneofField; | ||
| value = unsafeGet(init, oneofField); | ||
| } | ||
| else { | ||
| field = member; | ||
| } | ||
| switch (field.fieldKind) { | ||
| case "message": | ||
| value = toMessage(field, value); | ||
| case "list": { | ||
| properties.push({ | ||
| name, | ||
| kind: INIT_LIST, | ||
| constant: undefined, | ||
| convert: member.listKind == "message" | ||
| ? ((_a = compileConvertMessage(member)) !== null && _a !== void 0 ? _a : ((value) => value)) | ||
| : member.scalar == ScalarType.BYTES | ||
| ? toU8Arr | ||
| : undefined, | ||
| }); | ||
| break; | ||
| case "scalar": | ||
| value = initScalar(field, value); | ||
| } | ||
| case "map": { | ||
| properties.push({ | ||
| name, | ||
| kind: INIT_MAP, | ||
| constant: undefined, | ||
| convert: member.mapKind == "message" | ||
| ? ((_b = compileConvertMessage(member)) !== null && _b !== void 0 ? _b : ((value) => value)) | ||
| : member.scalar == ScalarType.BYTES | ||
| ? toU8Arr | ||
| : undefined, | ||
| }); | ||
| break; | ||
| case "list": | ||
| value = initList(field, value); | ||
| } | ||
| default: { | ||
| const zeroValue = createZeroValue(member); | ||
| properties.push({ | ||
| name, | ||
| kind: INIT_SINGULAR, | ||
| constant: member.presence == IMPLICIT ? zeroValue : undefined, | ||
| convert: member.fieldKind == "scalar" && member.scalar == ScalarType.BYTES | ||
| ? toU8Arr | ||
| : undefined, | ||
| }); | ||
| if (usePrototype) { | ||
| prototype[name] = zeroValue; | ||
| } | ||
| break; | ||
| case "map": | ||
| value = initMap(field, value); | ||
| break; | ||
| } | ||
| } | ||
| unsafeSet(message, field, value); | ||
| } | ||
| return message; | ||
| return { | ||
| properties, | ||
| prototype: usePrototype ? prototype : undefined, | ||
| }; | ||
| } | ||
| function initScalar(field, value) { | ||
| if (field.scalar == ScalarType.BYTES) { | ||
| return toU8Arr(value); | ||
| } | ||
| return value; | ||
| } | ||
| function initMap(field, value) { | ||
| if (isObject(value)) { | ||
| if (field.scalar == ScalarType.BYTES) { | ||
| return convertObjectValues(value, toU8Arr); | ||
| /** | ||
| * Compile the conversion of each case of a oneof group, keyed by case name. | ||
| */ | ||
| function compileConvertOneof(oneof) { | ||
| const converters = new Map(); | ||
| for (const field of oneof.fields) { | ||
| let convert; | ||
| if (field.fieldKind == "message") { | ||
| convert = compileConvertMessage(field); | ||
| } | ||
| if (field.mapKind == "message") { | ||
| return convertObjectValues(value, (val) => toMessage(field, val)); | ||
| else if (field.fieldKind == "scalar" && | ||
| field.scalar == ScalarType.BYTES) { | ||
| convert = toU8Arr; | ||
| } | ||
| converters.set(field.localName, convert !== null && convert !== void 0 ? convert : ((value) => value)); | ||
| } | ||
| return value; | ||
| return converters; | ||
| } | ||
| function initList(field, value) { | ||
| if (Array.isArray(value)) { | ||
| if (field.scalar == ScalarType.BYTES) { | ||
| return value.map(toU8Arr); | ||
| } | ||
| if (field.listKind == "message") { | ||
| return value.map((item) => toMessage(field, item)); | ||
| } | ||
| } | ||
| return value; | ||
| } | ||
| function toMessage(field, value) { | ||
| /** | ||
| * Compile the conversion of an init value for a message field, a message | ||
| * list item, or a message map value. Returns undefined if values are used | ||
| * as-is. | ||
| */ | ||
| function compileConvertMessage(field) { | ||
| if (field.fieldKind == "message" && | ||
@@ -118,16 +237,23 @@ !field.oneof && | ||
| // a singular field that is not part of a oneof group. | ||
| return initScalar(field.message.fields[0], value); | ||
| return field.message.fields[0].scalar == ScalarType.BYTES | ||
| ? toU8Arr | ||
| : undefined; | ||
| } | ||
| if (isObject(value)) { | ||
| if (field.message.typeName == "google.protobuf.Struct" && | ||
| field.parent.typeName !== "google.protobuf.Value") { | ||
| // google.protobuf.Struct is represented with JsonObject when used in a | ||
| // field, except when used in google.protobuf.Value. | ||
| if (field.message.typeName == "google.protobuf.Struct" && | ||
| field.parent.typeName !== "google.protobuf.Value") { | ||
| // google.protobuf.Struct is represented with JsonObject when used in a | ||
| // field, except when used in google.protobuf.Value. | ||
| return undefined; | ||
| } | ||
| const messageDesc = field.message; | ||
| // Resolved on first use, not here: the message type can be this very field's | ||
| // parent, whose create function is still being compiled. | ||
| let compiled; | ||
| return (value) => { | ||
| if (!isObject(value) || isMessage(value, messageDesc)) { | ||
| return value; | ||
| } | ||
| if (!isMessage(value, field.message)) { | ||
| return create(field.message, value); | ||
| } | ||
| } | ||
| return value; | ||
| compiled !== null && compiled !== void 0 ? compiled : (compiled = compiledCreate(messageDesc)); | ||
| return compiled(value); | ||
| }; | ||
| } | ||
@@ -138,80 +264,3 @@ // converts any ArrayLike<number> to Uint8Array if necessary. | ||
| } | ||
| function convertObjectValues(obj, fn) { | ||
| const ret = {}; | ||
| for (const entry of Object.entries(obj)) { | ||
| ret[entry[0]] = fn(entry[1]); | ||
| } | ||
| return ret; | ||
| } | ||
| const tokenZeroMessageField = Symbol(); | ||
| const messagePrototypes = new WeakMap(); | ||
| /** | ||
| * Create a zero message. | ||
| */ | ||
| function createZeroMessage(desc) { | ||
| let msg; | ||
| if (!needsPrototypeChain(desc)) { | ||
| msg = { | ||
| $typeName: desc.typeName, | ||
| }; | ||
| for (const member of desc.members) { | ||
| if (member.kind == "oneof" || member.presence == IMPLICIT) { | ||
| msg[member.localName] = createZeroField(member); | ||
| } | ||
| } | ||
| } | ||
| else { | ||
| // Support default values and track presence via the prototype chain | ||
| const cached = messagePrototypes.get(desc); | ||
| let prototype; | ||
| let members; | ||
| if (cached) { | ||
| ({ prototype, members } = cached); | ||
| } | ||
| else { | ||
| prototype = {}; | ||
| members = new Set(); | ||
| for (const member of desc.members) { | ||
| if (member.kind == "oneof") { | ||
| // we can only put immutable values on the prototype, | ||
| // oneof ADTs are mutable | ||
| continue; | ||
| } | ||
| if (member.fieldKind != "scalar" && member.fieldKind != "enum") { | ||
| // only scalar and enum values are immutable, map, list, and message | ||
| // are not | ||
| continue; | ||
| } | ||
| if (member.presence == IMPLICIT) { | ||
| // implicit presence tracks field presence by zero values - e.g. 0, false, "", are unset, 1, true, "x" are set. | ||
| // message, map, list fields are mutable, and also have IMPLICIT presence. | ||
| continue; | ||
| } | ||
| members.add(member); | ||
| prototype[member.localName] = createZeroField(member); | ||
| } | ||
| messagePrototypes.set(desc, { prototype, members }); | ||
| } | ||
| msg = Object.create(prototype); | ||
| msg.$typeName = desc.typeName; | ||
| for (const member of desc.members) { | ||
| if (members.has(member)) { | ||
| continue; | ||
| } | ||
| if (member.kind == "field") { | ||
| if (member.fieldKind == "message") { | ||
| continue; | ||
| } | ||
| if (member.fieldKind == "scalar" || member.fieldKind == "enum") { | ||
| if (member.presence != IMPLICIT) { | ||
| continue; | ||
| } | ||
| } | ||
| } | ||
| msg[member.localName] = createZeroField(member); | ||
| } | ||
| } | ||
| return msg; | ||
| } | ||
| /** | ||
| * Do we need the prototype chain to track field presence? | ||
@@ -235,18 +284,6 @@ */ | ||
| /** | ||
| * Returns a zero value for oneof groups, and for every field kind except | ||
| * messages. Scalar and enum fields can have default values. | ||
| * Returns the zero value for a scalar or enum field. Scalar and enum fields | ||
| * can have default values. | ||
| */ | ||
| function createZeroField(field) { | ||
| if (field.kind == "oneof") { | ||
| return { case: undefined }; | ||
| } | ||
| if (field.fieldKind == "list") { | ||
| return []; | ||
| } | ||
| if (field.fieldKind == "map") { | ||
| return {}; // Object.create(null) would be desirable here, but is unsupported by react https://react.dev/reference/react/use-server#serializable-parameters-and-return-values | ||
| } | ||
| if (field.fieldKind == "message") { | ||
| return tokenZeroMessageField; | ||
| } | ||
| function createZeroValue(field) { | ||
| const defaultValue = field.getDefaultValue(); | ||
@@ -253,0 +290,0 @@ if (defaultValue !== undefined) { |
+321
-153
@@ -16,3 +16,6 @@ // Copyright 2021-2026 Buf Technologies, Inc. | ||
| import { scalarZeroValue } from "./reflect/scalar.js"; | ||
| import { reflect } from "./reflect/reflect.js"; | ||
| import { FieldError } from "./reflect/error.js"; | ||
| import { unsafeLocal } from "./reflect/unsafe.js"; | ||
| import { localMessageMapper } from "./reflect/message.js"; | ||
| import { create } from "./create.js"; | ||
| import { BinaryReader, WireType } from "./wire/binary-encoding.js"; | ||
@@ -30,5 +33,5 @@ import { varint32write } from "./wire/varint.js"; | ||
| export function fromBinary(schema, bytes, options) { | ||
| const msg = reflect(schema, undefined, false); | ||
| readMessage(msg, new BinaryReader(bytes), makeReadContext(options), false, bytes.byteLength); | ||
| return msg.message; | ||
| const message = create(schema); | ||
| compiledReader(schema).read(message, new BinaryReader(bytes), makeReadContext(options), bytes.byteLength); | ||
| return message; | ||
| } | ||
@@ -45,48 +48,103 @@ /** | ||
| export function mergeFromBinary(schema, target, bytes, options) { | ||
| readMessage(reflect(schema, target, false), new BinaryReader(bytes), makeReadContext(options), false, bytes.byteLength); | ||
| if (target.$typeName !== schema.typeName && | ||
| schema.fields.length > 0) { | ||
| throw new FieldError(schema.fields[0], `cannot use ${schema.fields[0]} with message ${target.$typeName}`, "ForeignFieldError"); | ||
| } | ||
| compiledReader(schema).read(target, new BinaryReader(bytes), makeReadContext(options), bytes.byteLength); | ||
| return target; | ||
| } | ||
| const compiledReaders = new WeakMap(); | ||
| /** | ||
| * If `delimited` is false, read the length given in `lengthOrDelimitedFieldNo`. | ||
| * | ||
| * If `delimited` is true, read until an EndGroup tag. `lengthOrDelimitedFieldNo` | ||
| * is the expected field number. | ||
| * | ||
| * @private | ||
| * Return the compiled decoder for a message, compiling it on first use. | ||
| */ | ||
| function readMessage(message, reader, ctx, delimited, lengthOrDelimitedFieldNo) { | ||
| var _a; | ||
| if (++ctx.depth > ctx.recursionLimit) { | ||
| throw new Error(`cannot decode ${message.desc} from binary: maximum recursion depth of ${ctx.recursionLimit} reached`); | ||
| function compiledReader(desc) { | ||
| let compiled = compiledReaders.get(desc); | ||
| if (compiled === undefined) { | ||
| compiled = compileMessage(desc); | ||
| } | ||
| const end = delimited ? reader.len : reader.pos + lengthOrDelimitedFieldNo; | ||
| let fieldNo; | ||
| let wireType; | ||
| const unknownFields = (_a = message.getUnknown()) !== null && _a !== void 0 ? _a : []; | ||
| while (reader.pos < end) { | ||
| [fieldNo, wireType] = reader.tag(); | ||
| if (delimited && wireType == WireType.EndGroup) { | ||
| break; | ||
| return compiled; | ||
| } | ||
| function compileMessage(desc) { | ||
| const descString = String(desc); | ||
| const fieldReaders = new Map(); | ||
| const compiled = { | ||
| read: compileMessageReader(descString, fieldReaders), | ||
| readGroup: compileGroupReader(descString, fieldReaders), | ||
| }; | ||
| // Register before compiling fields, so that recursive message types | ||
| // resolve to this instance instead of compiling endlessly. | ||
| compiledReaders.set(desc, compiled); | ||
| for (const field of desc.fields) { | ||
| fieldReaders.set(field.number, compileFieldReader(field)); | ||
| } | ||
| return compiled; | ||
| } | ||
| /** | ||
| * Create a decoder for a length-prefixed message body, dispatching wire | ||
| * records to the compiled field decoders by field number. | ||
| */ | ||
| function compileMessageReader(descString, fieldReaders) { | ||
| return (message, reader, ctx, length) => { | ||
| var _a; | ||
| if (++ctx.depth > ctx.recursionLimit) { | ||
| throw new Error(`cannot decode ${descString} from binary: maximum recursion depth of ${ctx.recursionLimit} reached`); | ||
| } | ||
| const field = message.findNumber(fieldNo); | ||
| if (!field) { | ||
| // Use remaining recursion budget for skipping nested groups | ||
| const recursionLimit = ctx.recursionLimit - ctx.depth; | ||
| const data = reader.skip(wireType, fieldNo, recursionLimit); | ||
| if (ctx.readUnknownFields) { | ||
| unknownFields.push({ no: fieldNo, wireType, data }); | ||
| const end = reader.pos + length; | ||
| const unknownFields = (_a = message.$unknown) !== null && _a !== void 0 ? _a : []; | ||
| while (reader.pos < end) { | ||
| const [fieldNo, wireType] = reader.tag(); | ||
| const fieldReader = fieldReaders.get(fieldNo); | ||
| if (fieldReader === undefined) { | ||
| // Use remaining recursion budget for skipping nested groups | ||
| const data = reader.skip(wireType, fieldNo, ctx.recursionLimit - ctx.depth); | ||
| if (ctx.readUnknownFields) { | ||
| unknownFields.push({ no: fieldNo, wireType, data }); | ||
| } | ||
| continue; | ||
| } | ||
| continue; | ||
| fieldReader(message, reader, ctx, wireType); | ||
| } | ||
| readField(message, reader, field, wireType, ctx); | ||
| } | ||
| if (delimited) { | ||
| if (wireType != WireType.EndGroup || fieldNo !== lengthOrDelimitedFieldNo) { | ||
| if (unknownFields.length > 0) { | ||
| message.$unknown = unknownFields; | ||
| } | ||
| ctx.depth--; | ||
| }; | ||
| } | ||
| /** | ||
| * Create a decoder for a message with the delimited encoding (group), | ||
| * reading until the EndGroup tag, like compileMessageReader. | ||
| */ | ||
| function compileGroupReader(descString, fieldReaders) { | ||
| return (message, reader, ctx, fieldNo) => { | ||
| var _a; | ||
| if (++ctx.depth > ctx.recursionLimit) { | ||
| throw new Error(`cannot decode ${descString} from binary: maximum recursion depth of ${ctx.recursionLimit} reached`); | ||
| } | ||
| let recordFieldNo; | ||
| let wireType; | ||
| const unknownFields = (_a = message.$unknown) !== null && _a !== void 0 ? _a : []; | ||
| while (reader.pos < reader.len) { | ||
| [recordFieldNo, wireType] = reader.tag(); | ||
| if (wireType == WireType.EndGroup) { | ||
| break; | ||
| } | ||
| const fieldReader = fieldReaders.get(recordFieldNo); | ||
| if (fieldReader === undefined) { | ||
| // Use remaining recursion budget for skipping nested groups | ||
| const data = reader.skip(wireType, recordFieldNo, ctx.recursionLimit - ctx.depth); | ||
| if (ctx.readUnknownFields) { | ||
| unknownFields.push({ no: recordFieldNo, wireType, data }); | ||
| } | ||
| continue; | ||
| } | ||
| fieldReader(message, reader, ctx, wireType); | ||
| } | ||
| if (wireType != WireType.EndGroup || recordFieldNo !== fieldNo) { | ||
| throw new Error("invalid end group tag"); | ||
| } | ||
| } | ||
| if (unknownFields.length > 0) { | ||
| message.setUnknown(unknownFields); | ||
| } | ||
| ctx.depth--; | ||
| if (unknownFields.length > 0) { | ||
| message.$unknown = unknownFields; | ||
| } | ||
| ctx.depth--; | ||
| }; | ||
| } | ||
@@ -97,149 +155,259 @@ /** | ||
| export function readField(message, reader, field, wireType, ctx) { | ||
| var _a; | ||
| compileFieldReader(field)(message[unsafeLocal], reader, ctx, wireType); | ||
| } | ||
| function compileFieldReader(field) { | ||
| switch (field.fieldKind) { | ||
| case "scalar": | ||
| message.set(field, readScalar(reader, field.scalar, field.utf8Validation)); | ||
| break; | ||
| return compileScalarFieldReader(field); | ||
| case "enum": | ||
| const val = readScalar(reader, ScalarType.INT32); | ||
| if (field.enum.open) { | ||
| message.set(field, val); | ||
| } | ||
| else { | ||
| const ok = field.enum.values.some((v) => v.number === val); | ||
| if (ok) { | ||
| message.set(field, val); | ||
| } | ||
| else if (ctx.readUnknownFields) { | ||
| const bytes = []; | ||
| varint32write(val, bytes); | ||
| const unknownFields = (_a = message.getUnknown()) !== null && _a !== void 0 ? _a : []; | ||
| unknownFields.push({ | ||
| no: field.number, | ||
| wireType, | ||
| data: new Uint8Array(bytes), | ||
| }); | ||
| message.setUnknown(unknownFields); | ||
| } | ||
| } | ||
| break; | ||
| return compileEnumFieldReader(field); | ||
| case "message": | ||
| message.set(field, readMessageField(reader, ctx, field, message.get(field))); | ||
| break; | ||
| return compileMessageFieldReader(field); | ||
| case "list": | ||
| readListField(reader, wireType, message.get(field), ctx); | ||
| break; | ||
| return compileListFieldReader(field); | ||
| case "map": | ||
| readMapEntry(reader, message.get(field), ctx); | ||
| break; | ||
| return compileMapFieldReader(field); | ||
| } | ||
| } | ||
| // Read a map field, expecting key field = 1, value field = 2 | ||
| function readMapEntry(reader, map, ctx) { | ||
| const field = map.field(); | ||
| let key; | ||
| let val; | ||
| // Read the length of the map entry, which is a varint. | ||
| const len = reader.uint32(); | ||
| // WARNING: Calculate end AFTER advancing reader.pos (above), so that | ||
| // reader.pos is at the start of the map entry. | ||
| const end = reader.pos + len; | ||
| while (reader.pos < end) { | ||
| const [fieldNo] = reader.tag(); | ||
| switch (fieldNo) { | ||
| case 1: | ||
| key = readScalar(reader, field.mapKey, field.utf8Validation); | ||
| break; | ||
| case 2: | ||
| switch (field.mapKind) { | ||
| case "scalar": | ||
| val = readScalar(reader, field.scalar, field.utf8Validation); | ||
| break; | ||
| case "enum": | ||
| val = reader.int32(); | ||
| break; | ||
| case "message": | ||
| val = readMessageField(reader, ctx, field); | ||
| break; | ||
| } | ||
| break; | ||
| function compileScalarFieldReader(field) { | ||
| const readScalar = compileScalarReader(field.scalar, field.utf8Validation, field.longAsString); | ||
| const localName = field.localName; | ||
| if (field.oneof) { | ||
| const oneofLocalName = field.oneof.localName; | ||
| return (message, reader) => { | ||
| message[oneofLocalName] = { | ||
| case: localName, | ||
| value: readScalar(reader), | ||
| }; | ||
| }; | ||
| } | ||
| return (message, reader) => { | ||
| message[localName] = readScalar(reader); | ||
| }; | ||
| } | ||
| function compileEnumFieldReader(field) { | ||
| var _a; | ||
| const localName = field.localName; | ||
| const oneofLocalName = (_a = field.oneof) === null || _a === void 0 ? void 0 : _a.localName; | ||
| if (field.enum.open) { | ||
| if (oneofLocalName !== undefined) { | ||
| return (message, reader) => { | ||
| message[oneofLocalName] = { case: localName, value: reader.int32() }; | ||
| }; | ||
| } | ||
| return (message, reader) => { | ||
| message[localName] = reader.int32(); | ||
| }; | ||
| } | ||
| if (key === undefined) { | ||
| key = scalarZeroValue(field.mapKey, false); | ||
| } | ||
| if (val === undefined) { | ||
| switch (field.mapKind) { | ||
| case "scalar": | ||
| val = scalarZeroValue(field.scalar, false); | ||
| break; | ||
| case "enum": | ||
| val = field.enum.values[0].number; | ||
| break; | ||
| case "message": | ||
| val = reflect(field.message, undefined, false); | ||
| break; | ||
| // Closed enums: unknown values are stored as unknown fields. | ||
| const values = field.enum.values; | ||
| const fieldNo = field.number; | ||
| return (message, reader, ctx, wireType) => { | ||
| var _a; | ||
| const val = reader.int32(); | ||
| if (values.some((v) => v.number === val)) { | ||
| if (oneofLocalName !== undefined) { | ||
| message[oneofLocalName] = { case: localName, value: val }; | ||
| } | ||
| else { | ||
| message[localName] = val; | ||
| } | ||
| } | ||
| else if (ctx.readUnknownFields) { | ||
| const bytes = []; | ||
| varint32write(val, bytes); | ||
| const unknownFields = (_a = message.$unknown) !== null && _a !== void 0 ? _a : []; | ||
| unknownFields.push({ | ||
| no: fieldNo, | ||
| wireType, | ||
| data: new Uint8Array(bytes), | ||
| }); | ||
| message.$unknown = unknownFields; | ||
| } | ||
| }; | ||
| } | ||
| function compileMessageFieldReader(field) { | ||
| const localName = field.localName; | ||
| const { toMessage, toLocal } = localMessageMapper(field); | ||
| const readChild = compileChildReader(field); | ||
| if (field.oneof) { | ||
| const oneofLocalName = field.oneof.localName; | ||
| return (message, reader, ctx) => { | ||
| const oneof = message[oneofLocalName]; | ||
| const child = toMessage(oneof.case === localName ? oneof.value : undefined); | ||
| readChild(child, reader, ctx); | ||
| message[oneofLocalName] = { case: localName, value: toLocal(child) }; | ||
| }; | ||
| } | ||
| map.set(key, val); | ||
| return (message, reader, ctx) => { | ||
| const child = toMessage(message[localName]); | ||
| readChild(child, reader, ctx); | ||
| message[localName] = toLocal(child); | ||
| }; | ||
| } | ||
| function readListField(reader, wireType, list, ctx) { | ||
| var _a; | ||
| const field = list.field(); | ||
| if (field.listKind === "message") { | ||
| list.add(readMessageField(reader, ctx, field)); | ||
| return; | ||
| /** | ||
| * Compile a decoder for the wire format of a message field, honoring the | ||
| * delimited encoding of the field. | ||
| */ | ||
| function compileChildReader(field) { | ||
| const compiledChild = compiledReader(field.message); | ||
| if (field.delimitedEncoding) { | ||
| const fieldNo = field.number; | ||
| return (child, reader, ctx) => compiledChild.readGroup(child, reader, ctx, fieldNo); | ||
| } | ||
| const scalarType = (_a = field.scalar) !== null && _a !== void 0 ? _a : ScalarType.INT32; | ||
| const packed = wireType == WireType.LengthDelimited && | ||
| scalarType != ScalarType.STRING && | ||
| scalarType != ScalarType.BYTES; | ||
| if (!packed) { | ||
| list.add(readScalar(reader, scalarType, field.utf8Validation)); | ||
| return; | ||
| return (child, reader, ctx) => compiledChild.read(child, reader, ctx, reader.uint32()); | ||
| } | ||
| function compileListFieldReader(field) { | ||
| const localName = field.localName; | ||
| if (field.listKind == "message") { | ||
| const { toMessage, toLocal } = localMessageMapper(field); | ||
| const readChild = compileChildReader(field); | ||
| return (message, reader, ctx) => { | ||
| const child = toMessage(undefined); | ||
| readChild(child, reader, ctx); | ||
| message[localName].push(toLocal(child)); | ||
| }; | ||
| } | ||
| const e = reader.uint32() + reader.pos; | ||
| while (reader.pos < e) { | ||
| list.add(readScalar(reader, scalarType, field.utf8Validation)); | ||
| const scalarType = field.listKind == "enum" ? ScalarType.INT32 : field.scalar; | ||
| const longAsString = field.listKind == "scalar" ? field.longAsString : false; | ||
| const readScalar = compileScalarReader(scalarType, field.utf8Validation, longAsString); | ||
| const packedPossible = scalarType != ScalarType.STRING && scalarType != ScalarType.BYTES; | ||
| return (message, reader, ctx, wireType) => { | ||
| const items = message[localName]; | ||
| if (wireType == WireType.LengthDelimited && packedPossible) { | ||
| const end = reader.uint32() + reader.pos; | ||
| while (reader.pos < end) { | ||
| items.push(readScalar(reader)); | ||
| } | ||
| } | ||
| else { | ||
| items.push(readScalar(reader)); | ||
| } | ||
| }; | ||
| } | ||
| function compileMapFieldReader(field) { | ||
| const localName = field.localName; | ||
| const readKey = compileScalarReader(field.mapKey, field.utf8Validation, false); | ||
| const keyZero = scalarZeroValue(field.mapKey, false); | ||
| let readValue; | ||
| let valueDefault; | ||
| switch (field.mapKind) { | ||
| case "scalar": { | ||
| const scalar = field.scalar; | ||
| const readScalar = compileScalarReader(scalar, field.utf8Validation, false); | ||
| readValue = (reader) => readScalar(reader); | ||
| // Bytes zero values are created per entry, so that entries do not share | ||
| // one instance. | ||
| if (scalar == ScalarType.BYTES) { | ||
| valueDefault = () => new Uint8Array(0); | ||
| } | ||
| else { | ||
| const zero = scalarZeroValue(scalar, false); | ||
| valueDefault = () => zero; | ||
| } | ||
| break; | ||
| } | ||
| case "enum": { | ||
| const zero = field.enum.values[0].number; | ||
| readValue = (reader) => reader.int32(); | ||
| valueDefault = () => zero; | ||
| break; | ||
| } | ||
| case "message": { | ||
| const { toMessage, toLocal } = localMessageMapper(field); | ||
| const readChild = compiledReader(field.message).read; | ||
| readValue = (reader, ctx) => { | ||
| const child = toMessage(undefined); | ||
| readChild(child, reader, ctx, reader.uint32()); | ||
| return toLocal(child); | ||
| }; | ||
| valueDefault = () => toLocal(toMessage(undefined)); | ||
| break; | ||
| } | ||
| } | ||
| return (message, reader, ctx) => { | ||
| const record = message[localName]; | ||
| let key; | ||
| let val; | ||
| // Read the length of the map entry, which is a varint. | ||
| const len = reader.uint32(); | ||
| // Calculate end AFTER advancing reader.pos (above), so that reader.pos is | ||
| // at the start of the map entry. | ||
| const end = reader.pos + len; | ||
| while (reader.pos < end) { | ||
| // Map entries have the key in field 1, and the value in field 2. | ||
| const [fieldNo] = reader.tag(); | ||
| switch (fieldNo) { | ||
| case 1: | ||
| key = readKey(reader); | ||
| break; | ||
| case 2: | ||
| val = readValue(reader, ctx); | ||
| break; | ||
| } | ||
| } | ||
| if (key === undefined) { | ||
| key = keyZero; | ||
| } | ||
| if (val === undefined) { | ||
| val = valueDefault(); | ||
| } | ||
| // Object property keys are always strings or symbols. Assigning with a | ||
| // boolean, number, or bigint key implicitly converts it to a string. | ||
| record[key] = val; | ||
| }; | ||
| } | ||
| function readMessageField(reader, ctx, field, mergeMessage) { | ||
| const delimited = field.delimitedEncoding; | ||
| const message = mergeMessage !== null && mergeMessage !== void 0 ? mergeMessage : reflect(field.message, undefined, false); | ||
| readMessage(message, reader, ctx, delimited, delimited ? field.number : reader.uint32()); | ||
| return message; | ||
| } | ||
| function readScalar(reader, type, validateUtf8 = false) { | ||
| /** | ||
| * Returns a reader for a scalar value. For 64-bit integers, BinaryReader | ||
| * already returns the local representation (bigint or string), so, unlike in | ||
| * the reflection layer, no validation is needed here. | ||
| */ | ||
| function compileScalarReader(type, utf8Validation, longAsString) { | ||
| switch (type) { | ||
| case ScalarType.STRING: | ||
| return reader.string(validateUtf8); | ||
| return (reader) => reader.string(utf8Validation); | ||
| case ScalarType.BOOL: | ||
| return reader.bool(); | ||
| return (reader) => reader.bool(); | ||
| case ScalarType.DOUBLE: | ||
| return reader.double(); | ||
| return (reader) => reader.double(); | ||
| case ScalarType.FLOAT: | ||
| return reader.float(); | ||
| return (reader) => reader.float(); | ||
| case ScalarType.INT32: | ||
| return reader.int32(); | ||
| return (reader) => reader.int32(); | ||
| case ScalarType.INT64: | ||
| return reader.int64(); | ||
| if (longAsString) { | ||
| return (reader) => String(reader.int64()); | ||
| } | ||
| return (reader) => reader.int64(); | ||
| case ScalarType.UINT64: | ||
| return reader.uint64(); | ||
| if (longAsString) { | ||
| return (reader) => String(reader.uint64()); | ||
| } | ||
| return (reader) => reader.uint64(); | ||
| case ScalarType.FIXED64: | ||
| return reader.fixed64(); | ||
| if (longAsString) { | ||
| return (reader) => String(reader.fixed64()); | ||
| } | ||
| return (reader) => reader.fixed64(); | ||
| case ScalarType.BYTES: | ||
| return reader.bytes(); | ||
| return (reader) => reader.bytes(); | ||
| case ScalarType.FIXED32: | ||
| return reader.fixed32(); | ||
| return (reader) => reader.fixed32(); | ||
| case ScalarType.SFIXED32: | ||
| return reader.sfixed32(); | ||
| return (reader) => reader.sfixed32(); | ||
| case ScalarType.SFIXED64: | ||
| return reader.sfixed64(); | ||
| if (longAsString) { | ||
| return (reader) => String(reader.sfixed64()); | ||
| } | ||
| return (reader) => reader.sfixed64(); | ||
| case ScalarType.SINT64: | ||
| return reader.sint64(); | ||
| if (longAsString) { | ||
| return (reader) => String(reader.sint64()); | ||
| } | ||
| return (reader) => reader.sint64(); | ||
| case ScalarType.UINT32: | ||
| return reader.uint32(); | ||
| return (reader) => reader.uint32(); | ||
| case ScalarType.SINT32: | ||
| return reader.sint32(); | ||
| return (reader) => reader.sint32(); | ||
| } | ||
| } |
+602
-304
@@ -17,9 +17,14 @@ // Copyright 2021-2026 Buf Technologies, Inc. | ||
| import { create } from "./create.js"; | ||
| import { reflect } from "./reflect/reflect.js"; | ||
| import { FieldError, isFieldError } from "./reflect/error.js"; | ||
| import { formatVal } from "./reflect/reflect-check.js"; | ||
| import { formatVal, reasonSingular, checkScalarValue, } from "./reflect/reflect-check.js"; | ||
| import { protoSnakeCase } from "./reflect/names.js"; | ||
| import { scalarZeroValue } from "./reflect/scalar.js"; | ||
| import { unsafeLocal } from "./reflect/unsafe.js"; | ||
| import { localMessageMapper } from "./reflect/message.js"; | ||
| import { base64Decode } from "./wire/base64-encoding.js"; | ||
| import { hasCustomJsonRepresentation, isWrapperDesc, anyPack, ListValueSchema, NullValue, StructSchema, ValueSchema, } from "./wkt/index.js"; | ||
| import { createExtensionContainer, setExtension } from "./extensions.js"; | ||
| import { durationSecondsMax, durationSecondsMin, timestampMsMax, timestampMsMin, } from "./wkt/json.js"; | ||
| // bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name = $number; | ||
| const IMPLICIT = 2; | ||
| function makeReadContext(options) { | ||
@@ -58,16 +63,5 @@ return Object.assign(Object.assign({ ignoreUnknownFields: false, recursionLimit: 100 }, options), { depth: 0 }); | ||
| export function fromJson(schema, json, options) { | ||
| const msg = reflect(schema); | ||
| try { | ||
| readMessage(msg, json, makeReadContext(options)); | ||
| } | ||
| catch (e) { | ||
| if (isFieldError(e)) { | ||
| // @ts-expect-error we use the ES2022 error CTOR option "cause" for better stack traces | ||
| throw new Error(`cannot decode ${e.field()} from JSON: ${e.message}`, { | ||
| cause: e, | ||
| }); | ||
| } | ||
| throw e; | ||
| } | ||
| return msg.message; | ||
| const message = create(schema); | ||
| readMessage(schema, message, json, options); | ||
| return message; | ||
| } | ||
@@ -87,4 +81,16 @@ /** | ||
| export function mergeFromJson(schema, target, json, options) { | ||
| if (target.$typeName !== schema.typeName && | ||
| schema.fields.length > 0) { | ||
| throw new FieldError(schema.fields[0], `cannot use ${schema.fields[0]} with message ${target.$typeName}`, "ForeignFieldError"); | ||
| } | ||
| readMessage(schema, target, json, options); | ||
| return target; | ||
| } | ||
| /** | ||
| * Run the compiled decoder for the message, wrapping FieldErrors with the | ||
| * standard error message. | ||
| */ | ||
| function readMessage(schema, message, json, options) { | ||
| try { | ||
| readMessage(reflect(schema, target), json, makeReadContext(options)); | ||
| compiledReader(schema)(message, json, makeReadContext(options)); | ||
| } | ||
@@ -100,3 +106,2 @@ catch (e) { | ||
| } | ||
| return target; | ||
| } | ||
@@ -107,3 +112,5 @@ /** | ||
| export function enumFromJson(descEnum, json) { | ||
| return readEnum(descEnum, json, false); | ||
| // With ignoreUnknownFields false, the converter never returns the token | ||
| // for ignored unknown enum values. | ||
| return compileEnumConverter(descEnum)(json, false); | ||
| } | ||
@@ -116,214 +123,506 @@ /** | ||
| } | ||
| const messageJsonFields = new WeakMap(); | ||
| function getJsonField(desc, jsonKey) { | ||
| var _a; | ||
| if (!messageJsonFields.has(desc)) { | ||
| const jsonNames = new Map(); | ||
| for (const field of desc.fields) { | ||
| jsonNames.set(field.name, field).set(field.jsonName, field); | ||
| } | ||
| messageJsonFields.set(desc, jsonNames); | ||
| const compiledReaders = new WeakMap(); | ||
| /** | ||
| * Return the compiled decoder for a message, compiling it on first use. | ||
| */ | ||
| function compiledReader(desc) { | ||
| let compiled = compiledReaders.get(desc); | ||
| if (compiled === undefined) { | ||
| compiled = compileMessage(desc); | ||
| } | ||
| return (_a = messageJsonFields.get(desc)) === null || _a === void 0 ? void 0 : _a.get(jsonKey); | ||
| return compiled; | ||
| } | ||
| function readMessage(msg, json, ctx) { | ||
| var _a; | ||
| if (++ctx.depth > ctx.recursionLimit) { | ||
| throw new Error(`cannot decode ${msg.desc} from JSON: maximum recursion depth of ${ctx.recursionLimit} reached`); | ||
| function compileMessage(desc) { | ||
| const descString = String(desc); | ||
| const readWkt = compileWkt(desc); | ||
| if (readWkt !== undefined) { | ||
| // All message decoders count against the recursion limit, including | ||
| // well-known types with a custom JSON representation. | ||
| const compiled = (message, json, ctx) => { | ||
| if (++ctx.depth > ctx.recursionLimit) { | ||
| throw new Error(`cannot decode ${descString} from JSON: maximum recursion depth of ${ctx.recursionLimit} reached`); | ||
| } | ||
| readWkt(message, json, ctx); | ||
| ctx.depth--; | ||
| }; | ||
| compiledReaders.set(desc, compiled); | ||
| return compiled; | ||
| } | ||
| if (tryWktFromJson(msg, json, ctx)) { | ||
| ctx.depth--; | ||
| return; | ||
| } | ||
| if (json == null || Array.isArray(json) || typeof json != "object") { | ||
| throw new Error(`cannot decode ${msg.desc} from JSON: ${formatVal(json)}`); | ||
| } | ||
| const oneofSeen = new Map(); | ||
| const fieldSeen = new Set(); | ||
| for (const [jsonKey, jsonValue] of Object.entries(json)) { | ||
| const field = getJsonField(msg.desc, jsonKey); | ||
| if (field) { | ||
| if (fieldSeen.has(field)) { | ||
| // The same field may be set by its proto name and its JSON name, or by | ||
| // a duplicate or unicode-escaped key that JSON.parse already collapsed. | ||
| // Checked before the null-skip below so that a null entry still counts. | ||
| throw new FieldError(field, "set multiple times"); | ||
| const typeName = desc.typeName; | ||
| // Fields are looked up by their proto name and their JSON name. | ||
| const fieldsByJsonKey = new Map(); | ||
| const compiled = (message, json, ctx) => { | ||
| var _a; | ||
| if (++ctx.depth > ctx.recursionLimit) { | ||
| throw new Error(`cannot decode ${descString} from JSON: maximum recursion depth of ${ctx.recursionLimit} reached`); | ||
| } | ||
| if (json == null || Array.isArray(json) || typeof json != "object") { | ||
| throw new Error(`cannot decode ${descString} from JSON: ${formatVal(json)}`); | ||
| } | ||
| const oneofSeen = new Map(); | ||
| const fieldSeen = new Set(); | ||
| const jsonKeys = Object.keys(json); | ||
| for (let i = 0; i < jsonKeys.length; i++) { | ||
| const jsonKey = jsonKeys[i]; | ||
| const jsonValue = json[jsonKey]; | ||
| const entry = fieldsByJsonKey.get(jsonKey); | ||
| if (entry !== undefined) { | ||
| const field = entry.field; | ||
| if (fieldSeen.has(field)) { | ||
| // The same field may be set by its proto name and its JSON name, or by | ||
| // a duplicate or unicode-escaped key that JSON.parse already collapsed. | ||
| // Checked before the null-skip below so that a null entry still counts. | ||
| throw new FieldError(field, "set multiple times"); | ||
| } | ||
| fieldSeen.add(field); | ||
| if (entry.oneofScalarNullSkip && jsonValue === null) { | ||
| continue; | ||
| } | ||
| if (entry.oneof) { | ||
| const seen = oneofSeen.get(entry.oneof); | ||
| if (seen !== undefined) { | ||
| throw new FieldError(entry.oneof, `oneof set multiple times by ${seen.name} and ${field.name}`); | ||
| } | ||
| oneofSeen.set(entry.oneof, field); | ||
| } | ||
| entry.read(message, jsonValue, ctx); | ||
| } | ||
| fieldSeen.add(field); | ||
| if (field.oneof && jsonValue === null && field.fieldKind == "scalar") { | ||
| // see conformance test Required.Proto3.JsonInput.OneofFieldNull{First,Second} | ||
| continue; | ||
| } | ||
| if (field.oneof) { | ||
| const seen = oneofSeen.get(field.oneof); | ||
| if (seen !== undefined) { | ||
| throw new FieldError(field.oneof, `oneof set multiple times by ${seen.name} and ${field.name}`); | ||
| else { | ||
| const extension = jsonKey.startsWith("[") && jsonKey.endsWith("]") | ||
| ? (_a = ctx.registry) === null || _a === void 0 ? void 0 : _a.getExtension(jsonKey.substring(1, jsonKey.length - 1)) | ||
| : undefined; | ||
| if ((extension === null || extension === void 0 ? void 0 : extension.extendee.typeName) == typeName) { | ||
| const [container, field, get] = createExtensionContainer(extension); | ||
| compileFieldReader(field)(container[unsafeLocal], jsonValue, ctx); | ||
| setExtension(message, extension, get()); | ||
| } | ||
| oneofSeen.set(field.oneof, field); | ||
| if (extension === undefined && !ctx.ignoreUnknownFields) { | ||
| throw new Error(`cannot decode ${descString} from JSON: key "${jsonKey}" is unknown`); | ||
| } | ||
| } | ||
| readField(msg, field, jsonValue, ctx); | ||
| } | ||
| else { | ||
| let extension = undefined; | ||
| if (jsonKey.startsWith("[") && | ||
| jsonKey.endsWith("]") && | ||
| // biome-ignore lint/suspicious/noAssignInExpressions: no | ||
| (extension = (_a = ctx.registry) === null || _a === void 0 ? void 0 : _a.getExtension(jsonKey.substring(1, jsonKey.length - 1))) && | ||
| extension.extendee.typeName === msg.desc.typeName) { | ||
| const [container, field, get] = createExtensionContainer(extension); | ||
| readField(container, field, jsonValue, ctx); | ||
| setExtension(msg.message, extension, get()); | ||
| ctx.depth--; | ||
| }; | ||
| // Register before compiling fields, so that recursive message types | ||
| // resolve to this instance instead of compiling endlessly. | ||
| compiledReaders.set(desc, compiled); | ||
| for (const field of desc.fields) { | ||
| const entry = { | ||
| read: compileFieldReader(field), | ||
| field, | ||
| oneof: field.oneof, | ||
| oneofScalarNullSkip: field.oneof !== undefined && field.fieldKind == "scalar", | ||
| }; | ||
| fieldsByJsonKey.set(field.name, entry).set(field.jsonName, entry); | ||
| } | ||
| return compiled; | ||
| } | ||
| /** | ||
| * Compile a decoder for a well-known type with a custom JSON representation, | ||
| * or return undefined for other messages. The recursion limit is enforced by | ||
| * the caller. | ||
| */ | ||
| function compileWkt(desc) { | ||
| if (!desc.typeName.startsWith("google.protobuf.")) { | ||
| return undefined; | ||
| } | ||
| switch (desc.typeName) { | ||
| case "google.protobuf.Any": | ||
| return (message, json, ctx) => anyFromJson(message, json, ctx); | ||
| case "google.protobuf.Timestamp": | ||
| return (message, json) => timestampFromJson(message, json); | ||
| case "google.protobuf.Duration": | ||
| return (message, json) => durationFromJson(message, json); | ||
| case "google.protobuf.FieldMask": | ||
| return (message, json) => fieldMaskFromJson(message, json); | ||
| case "google.protobuf.Struct": | ||
| return (message, json, ctx) => structFromJson(message, json, ctx); | ||
| case "google.protobuf.Value": | ||
| return (message, json, ctx) => valueFromJson(message, json, ctx); | ||
| case "google.protobuf.ListValue": | ||
| return (message, json, ctx) => listValueFromJson(message, json, ctx); | ||
| default: | ||
| if (isWrapperDesc(desc)) { | ||
| const valueField = desc.fields[0]; | ||
| const localName = valueField.localName; | ||
| const scalar = valueField.scalar; | ||
| const longAsString = valueField.longAsString; | ||
| const readScalar = compileScalarConverter(valueField); | ||
| return (message, json) => { | ||
| if (json === null) { | ||
| message[localName] = scalarZeroValue(scalar, longAsString); | ||
| } | ||
| else { | ||
| message[localName] = readScalar(json); | ||
| } | ||
| }; | ||
| } | ||
| if (!extension && !ctx.ignoreUnknownFields) { | ||
| throw new Error(`cannot decode ${msg.desc} from JSON: key "${jsonKey}" is unknown`); | ||
| } | ||
| } | ||
| return undefined; | ||
| } | ||
| ctx.depth--; | ||
| } | ||
| function readField(msg, field, json, ctx) { | ||
| function compileFieldReader(field) { | ||
| switch (field.fieldKind) { | ||
| case "scalar": | ||
| readScalarField(msg, field, json); | ||
| break; | ||
| return compileScalarFieldReader(field); | ||
| case "enum": | ||
| readEnumField(msg, field, json, ctx); | ||
| break; | ||
| return compileEnumFieldReader(field); | ||
| case "message": | ||
| readMessageField(msg, field, json, ctx); | ||
| break; | ||
| return compileMessageFieldReader(field); | ||
| case "list": | ||
| readListField(msg.get(field), json, ctx); | ||
| break; | ||
| return compileListFieldReader(field); | ||
| case "map": | ||
| readMapField(msg.get(field), json, ctx); | ||
| break; | ||
| return compileMapFieldReader(field); | ||
| } | ||
| } | ||
| function readListOrMapItem(field, json, ctx) { | ||
| if (field.scalar && json !== null) { | ||
| return scalarFromJson(field, json); | ||
| function compileScalarFieldReader(field) { | ||
| const readScalar = compileScalarConverter(field); | ||
| const localName = field.localName; | ||
| if (field.oneof) { | ||
| // JSON null for a oneof scalar member is skipped by the message decoder. | ||
| const oneofLocalName = field.oneof.localName; | ||
| return (message, json) => { | ||
| message[oneofLocalName] = { | ||
| case: localName, | ||
| value: readScalar(json), | ||
| }; | ||
| }; | ||
| } | ||
| if (field.message && !isResetSentinelNullValue(field, json)) { | ||
| const msgValue = reflect(field.message); | ||
| readMessage(msgValue, json, ctx); | ||
| return msgValue; | ||
| const clear = compileClear(field); | ||
| return (message, json) => { | ||
| if (json === null) { | ||
| clear(message); | ||
| } | ||
| else { | ||
| message[localName] = readScalar(json); | ||
| } | ||
| }; | ||
| } | ||
| /** | ||
| * Compile a function that resets the field to unset, mirroring the clear | ||
| * operation of the reflect API for fields that are not part of a oneof. | ||
| */ | ||
| function compileClear(field) { | ||
| const localName = field.localName; | ||
| if (field.presence != IMPLICIT) { | ||
| // Fields with explicit presence have properties on the prototype chain | ||
| // for default / zero values (except for proto3). By deleting their own | ||
| // property, the field is reset. | ||
| return (message) => { | ||
| delete message[localName]; | ||
| }; | ||
| } | ||
| if (field.enum && !isResetSentinelNullValue(field, json)) { | ||
| return readEnum(field.enum, json, ctx.ignoreUnknownFields); | ||
| if (field.fieldKind == "enum") { | ||
| const zero = field.enum.values[0].number; | ||
| return (message) => { | ||
| message[localName] = zero; | ||
| }; | ||
| } | ||
| throw new FieldError(field, `${field.fieldKind === "list" ? "list item" : "map value"} must not be null`); | ||
| const scalar = field.scalar; | ||
| const longAsString = field.longAsString; | ||
| return (message) => { | ||
| message[localName] = scalarZeroValue(scalar, longAsString); | ||
| }; | ||
| } | ||
| function readMapField(map, json, ctx) { | ||
| if (json === null) { | ||
| return; | ||
| function compileEnumFieldReader(field) { | ||
| const readEnumValue = compileEnumConverter(field.enum); | ||
| const checkEnum = compileEnumCheck(field.enum); | ||
| const localName = field.localName; | ||
| // Fields with enum google.protobuf.NullValue permit a Protobuf-serializable | ||
| // null; for all other enums, JSON null resets the field. | ||
| const nullResets = field.enum.typeName != "google.protobuf.NullValue"; | ||
| if (field.oneof) { | ||
| const oneofLocalName = field.oneof.localName; | ||
| return (message, json, ctx) => { | ||
| if (json === null && nullResets) { | ||
| const oneof = message[oneofLocalName]; | ||
| if (oneof.case === localName) { | ||
| message[oneofLocalName] = { case: undefined }; | ||
| } | ||
| return; | ||
| } | ||
| const value = readEnumValue(json, ctx.ignoreUnknownFields); | ||
| if (value === tokenIgnoredUnknownEnum) { | ||
| return; | ||
| } | ||
| const check = checkEnum(value); | ||
| if (check !== true) { | ||
| throw new FieldError(field, reasonSingular(field, value, check)); | ||
| } | ||
| message[oneofLocalName] = { case: localName, value }; | ||
| }; | ||
| } | ||
| const field = map.field(); | ||
| if (typeof json != "object" || Array.isArray(json)) { | ||
| throw new FieldError(field, "expected object, got " + formatVal(json)); | ||
| } | ||
| const seen = new Set(); | ||
| for (const [jsonMapKey, jsonMapValue] of Object.entries(json)) { | ||
| const key = mapKeyFromJson(field.mapKey, jsonMapKey); | ||
| if (seen.has(key)) { | ||
| throw new FieldError(field, `duplicate map key "${jsonMapKey}"`); | ||
| const clear = compileClear(field); | ||
| return (message, json, ctx) => { | ||
| if (json === null && nullResets) { | ||
| clear(message); | ||
| return; | ||
| } | ||
| seen.add(key); | ||
| const value = readListOrMapItem(field, jsonMapValue, ctx); | ||
| if (value !== tokenIgnoredUnknownEnum) { | ||
| map.set(key, value); | ||
| const value = readEnumValue(json, ctx.ignoreUnknownFields); | ||
| if (value === tokenIgnoredUnknownEnum) { | ||
| return; | ||
| } | ||
| } | ||
| const check = checkEnum(value); | ||
| if (check !== true) { | ||
| throw new FieldError(field, reasonSingular(field, value, check)); | ||
| } | ||
| message[localName] = value; | ||
| }; | ||
| } | ||
| function readListField(list, json, ctx) { | ||
| if (json === null) { | ||
| return; | ||
| function compileMessageFieldReader(field) { | ||
| const localName = field.localName; | ||
| const { toMessage, toLocal } = localMessageMapper(field); | ||
| const readChild = compiledReader(field.message); | ||
| // Fields with message google.protobuf.Value permit a Protobuf-serializable | ||
| // null; for all other messages, JSON null resets the field. | ||
| const nullResets = field.message.typeName != "google.protobuf.Value"; | ||
| if (field.oneof) { | ||
| const oneofLocalName = field.oneof.localName; | ||
| return (message, json, ctx) => { | ||
| const oneof = message[oneofLocalName]; | ||
| if (json === null && nullResets) { | ||
| if (oneof.case === localName) { | ||
| message[oneofLocalName] = { case: undefined }; | ||
| } | ||
| return; | ||
| } | ||
| const child = toMessage(oneof.case === localName ? oneof.value : undefined); | ||
| readChild(child, json, ctx); | ||
| message[oneofLocalName] = { case: localName, value: toLocal(child) }; | ||
| }; | ||
| } | ||
| const field = list.field(); | ||
| if (!Array.isArray(json)) { | ||
| throw new FieldError(field, "expected Array, got " + formatVal(json)); | ||
| } | ||
| for (const jsonItem of json) { | ||
| const value = readListOrMapItem(field, jsonItem, ctx); | ||
| if (value !== tokenIgnoredUnknownEnum) { | ||
| list.add(value); | ||
| return (message, json, ctx) => { | ||
| if (json === null && nullResets) { | ||
| delete message[localName]; | ||
| return; | ||
| } | ||
| } | ||
| const child = toMessage(message[localName]); | ||
| readChild(child, json, ctx); | ||
| message[localName] = toLocal(child); | ||
| }; | ||
| } | ||
| function readMessageField(msg, field, json, ctx) { | ||
| if (isResetSentinelNullValue(field, json)) { | ||
| msg.clear(field); | ||
| return; | ||
| } | ||
| const msgValue = msg.isSet(field) ? msg.get(field) : reflect(field.message); | ||
| readMessage(msgValue, json, ctx); | ||
| msg.set(field, msgValue); | ||
| function compileListFieldReader(field) { | ||
| const localName = field.localName; | ||
| const readItem = compileListItemReader(field); | ||
| return (message, json, ctx) => { | ||
| if (json === null) { | ||
| return; | ||
| } | ||
| if (!Array.isArray(json)) { | ||
| throw new FieldError(field, "expected Array, got " + formatVal(json)); | ||
| } | ||
| const items = message[localName]; | ||
| for (let i = 0; i < json.length; i++) { | ||
| const value = readItem(json[i], ctx, items.length); | ||
| if (value !== tokenIgnoredUnknownEnum) { | ||
| items.push(value); | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| function readEnumField(msg, field, json, ctx) { | ||
| if (isResetSentinelNullValue(field, json)) { | ||
| msg.clear(field); | ||
| return; | ||
| /** | ||
| * Compile a decoder for a list item. The index is only used in errors, and | ||
| * accounts for previously merged items. | ||
| */ | ||
| function compileListItemReader(field) { | ||
| switch (field.listKind) { | ||
| case "scalar": { | ||
| const parseScalar = compileScalarParse(field); | ||
| const checkValue = checkScalarValue(field.scalar); | ||
| const toLocal = compileScalarToLocal(field); | ||
| return (json, ctx, index) => { | ||
| if (json === null) { | ||
| throw new FieldError(field, "list item must not be null"); | ||
| } | ||
| const value = parseScalar(json); | ||
| const check = checkValue(value); | ||
| if (check !== true) { | ||
| throw new FieldError(field, `list item #${index + 1}: ${reasonSingular(field, value, check)}`); | ||
| } | ||
| return toLocal(value); | ||
| }; | ||
| } | ||
| case "enum": { | ||
| const readEnumValue = compileEnumConverter(field.enum); | ||
| const checkEnum = compileEnumCheck(field.enum); | ||
| const nullResets = field.enum.typeName != "google.protobuf.NullValue"; | ||
| return (json, ctx, index) => { | ||
| if (json === null && nullResets) { | ||
| throw new FieldError(field, "list item must not be null"); | ||
| } | ||
| const value = readEnumValue(json, ctx.ignoreUnknownFields); | ||
| if (value === tokenIgnoredUnknownEnum) { | ||
| return value; | ||
| } | ||
| const check = checkEnum(value); | ||
| if (check !== true) { | ||
| throw new FieldError(field, `list item #${index + 1}: ${reasonSingular(field, value, check)}`); | ||
| } | ||
| return value; | ||
| }; | ||
| } | ||
| case "message": { | ||
| const { toMessage, toLocal } = localMessageMapper(field); | ||
| const readChild = compiledReader(field.message); | ||
| const nullResets = field.message.typeName != "google.protobuf.Value"; | ||
| return (json, ctx) => { | ||
| if (json === null && nullResets) { | ||
| throw new FieldError(field, "list item must not be null"); | ||
| } | ||
| const child = toMessage(undefined); | ||
| readChild(child, json, ctx); | ||
| return toLocal(child); | ||
| }; | ||
| } | ||
| } | ||
| const enumValue = readEnum(field.enum, json, ctx.ignoreUnknownFields); | ||
| if (enumValue !== tokenIgnoredUnknownEnum) { | ||
| msg.set(field, enumValue); | ||
| } | ||
| } | ||
| function readScalarField(msg, field, json) { | ||
| if (json === null) { | ||
| msg.clear(field); | ||
| function compileMapFieldReader(field) { | ||
| const localName = field.localName; | ||
| const mapKey = field.mapKey; | ||
| const parseMapKey = compileMapKeyParse(mapKey); | ||
| const checkMapKey = checkScalarValue(mapKey); | ||
| let parseValue; | ||
| // Additional validation for scalar and enum values, matching the checks | ||
| // of the reflect API. Message values need no validation. | ||
| let checkValue; | ||
| let toLocalValue = (value) => value; | ||
| // Fields with google.protobuf.Value or google.protobuf.NullValue values | ||
| // permit a Protobuf-serializable null. | ||
| let nullResets = true; | ||
| switch (field.mapKind) { | ||
| case "scalar": { | ||
| parseValue = compileScalarParse(field); | ||
| checkValue = checkScalarValue(field.scalar); | ||
| toLocalValue = compileScalarToLocal(field); | ||
| break; | ||
| } | ||
| case "enum": { | ||
| const readEnumValue = compileEnumConverter(field.enum); | ||
| parseValue = (json, ctx) => readEnumValue(json, ctx.ignoreUnknownFields); | ||
| checkValue = compileEnumCheck(field.enum); | ||
| nullResets = field.enum.typeName != "google.protobuf.NullValue"; | ||
| break; | ||
| } | ||
| case "message": { | ||
| const { toMessage, toLocal } = localMessageMapper(field); | ||
| const readChild = compiledReader(field.message); | ||
| nullResets = field.message.typeName != "google.protobuf.Value"; | ||
| parseValue = (json, ctx) => { | ||
| const child = toMessage(undefined); | ||
| readChild(child, json, ctx); | ||
| return toLocal(child); | ||
| }; | ||
| break; | ||
| } | ||
| } | ||
| else { | ||
| msg.set(field, scalarFromJson(field, json)); | ||
| } | ||
| return (message, json, ctx) => { | ||
| if (json === null) { | ||
| return; | ||
| } | ||
| if (typeof json != "object" || Array.isArray(json)) { | ||
| throw new FieldError(field, "expected object, got " + formatVal(json)); | ||
| } | ||
| const record = message[localName]; | ||
| const seen = new Set(); | ||
| const jsonMapKeys = Object.keys(json); | ||
| for (let i = 0; i < jsonMapKeys.length; i++) { | ||
| const jsonMapKey = jsonMapKeys[i]; | ||
| const jsonMapValue = json[jsonMapKey]; | ||
| const key = parseMapKey(jsonMapKey); | ||
| if (seen.has(key)) { | ||
| throw new FieldError(field, `duplicate map key "${jsonMapKey}"`); | ||
| } | ||
| seen.add(key); | ||
| if (jsonMapValue === null && nullResets) { | ||
| throw new FieldError(field, "map value must not be null"); | ||
| } | ||
| const value = parseValue(jsonMapValue, ctx); | ||
| if (value === tokenIgnoredUnknownEnum) { | ||
| continue; | ||
| } | ||
| const checkKey = checkMapKey(key); | ||
| if (checkKey !== true) { | ||
| throw new FieldError(field, `invalid map key: ${reasonSingular({ scalar: mapKey }, key, checkKey)}`); | ||
| } | ||
| if (checkValue !== undefined) { | ||
| const check = checkValue(value); | ||
| if (check !== true) { | ||
| throw new FieldError(field, `map entry ${formatVal(key)}: ${reasonSingular(field, value, check)}`); | ||
| } | ||
| } | ||
| // Object property keys are always strings or symbols. Assigning with a | ||
| // boolean, number, or bigint key implicitly converts it to a string. | ||
| record[key] = toLocalValue(value); | ||
| } | ||
| }; | ||
| } | ||
| const tokenIgnoredUnknownEnum = Symbol(); | ||
| /** | ||
| * Indicates whether a value is a sentinel for reseting a field. | ||
| * | ||
| * For this to be true, the value must be a JSON null and the field must not | ||
| * permit a present, Protobuf-serializable null. | ||
| * | ||
| * Only message google.protobuf.Value and enum google.protobuf.NullValue fields | ||
| * permit Protobuf-serializable nulls. | ||
| * | ||
| * Note that field-resetting sentinel nulls are not permitted in lists and maps. | ||
| * Compile a converter from a JSON value to an enum value. JSON null returns | ||
| * the enum's first value. With ignoreUnknownFields false, unknown string | ||
| * values raise an error; with true, they return tokenIgnoredUnknownEnum. | ||
| * The value is not checked against the enum's values, see compileEnumCheck. | ||
| */ | ||
| function isResetSentinelNullValue(field, json) { | ||
| var _a, _b; | ||
| return (json === null && | ||
| ((_a = field.message) === null || _a === void 0 ? void 0 : _a.typeName) != "google.protobuf.Value" && | ||
| ((_b = field.enum) === null || _b === void 0 ? void 0 : _b.typeName) != "google.protobuf.NullValue"); | ||
| function compileEnumConverter(desc) { | ||
| const zero = desc.values[0].number; | ||
| const values = desc.values; | ||
| return (json, ignoreUnknownFields) => { | ||
| if (json === null) { | ||
| return zero; | ||
| } | ||
| switch (typeof json) { | ||
| case "number": | ||
| if (Number.isInteger(json)) { | ||
| return json; | ||
| } | ||
| break; | ||
| case "string": { | ||
| const value = values.find((ev) => ev.name === json); | ||
| if (value !== undefined) { | ||
| return value.number; | ||
| } | ||
| if (ignoreUnknownFields) { | ||
| return tokenIgnoredUnknownEnum; | ||
| } | ||
| break; | ||
| } | ||
| } | ||
| throw new Error(`cannot decode ${desc} from JSON: ${formatVal(json)}`); | ||
| }; | ||
| } | ||
| const tokenIgnoredUnknownEnum = Symbol(); | ||
| function readEnum(desc, json, ignoreUnknownFields) { | ||
| if (json === null) { | ||
| return desc.values[0].number; | ||
| /** | ||
| * Compile the check that the reflect API performs for enum values: open | ||
| * enums accept any int32 value, closed enums accept only declared values. | ||
| */ | ||
| function compileEnumCheck(desc) { | ||
| if (desc.open) { | ||
| return checkScalarValue(ScalarType.INT32); | ||
| } | ||
| switch (typeof json) { | ||
| case "number": | ||
| if (Number.isInteger(json)) { | ||
| return json; | ||
| } | ||
| break; | ||
| case "string": | ||
| const value = desc.values.find((ev) => ev.name === json); | ||
| if (value !== undefined) { | ||
| return value.number; | ||
| } | ||
| if (ignoreUnknownFields) { | ||
| return tokenIgnoredUnknownEnum; | ||
| } | ||
| break; | ||
| } | ||
| throw new Error(`cannot decode ${desc} from JSON: ${formatVal(json)}`); | ||
| const values = desc.values; | ||
| return (value) => values.some((v) => v.number === value); | ||
| } | ||
| /** | ||
| * Try to parse a JSON value to a scalar value for the reflect API. | ||
| * | ||
| * Returns the input if the JSON value cannot be converted. Raises a FieldError | ||
| * if conversion would be ambiguous. | ||
| * Compile a converter from a JSON value to the local representation of a | ||
| * scalar, fusing JSON parsing, the validation of the reflect API, and the | ||
| * conversion to the local 64-bit integer representation. | ||
| */ | ||
| function scalarFromJson(field, json) { | ||
| // int64, sfixed64, sint64, fixed64, uint64: Reflect supports string and number. | ||
| // string, bool: Supported by reflect. | ||
| function compileScalarConverter(field) { | ||
| const parseScalar = compileScalarParse(field); | ||
| const checkValue = checkScalarValue(field.scalar); | ||
| const toLocal = compileScalarToLocal(field); | ||
| return (json) => { | ||
| const value = parseScalar(json); | ||
| const check = checkValue(value); | ||
| if (check !== true) { | ||
| throw new FieldError(field, reasonSingular(field, value, check)); | ||
| } | ||
| return toLocal(value); | ||
| }; | ||
| } | ||
| /** | ||
| * Compile the JSON-specific parsing step for a scalar value: the special | ||
| * string values of float and double, string-encoded numbers, and base64 | ||
| * bytes. Returns the input unchanged if the JSON value cannot be converted; | ||
| * the validation step raises an error for it. | ||
| */ | ||
| function compileScalarParse(field) { | ||
| switch (field.scalar) { | ||
@@ -334,36 +633,38 @@ // float, double: JSON value will be a number or one of the special string values "NaN", "Infinity", and "-Infinity". | ||
| case ScalarType.FLOAT: | ||
| if (json === "NaN") | ||
| return NaN; | ||
| if (json === "Infinity") | ||
| return Number.POSITIVE_INFINITY; | ||
| if (json === "-Infinity") | ||
| return Number.NEGATIVE_INFINITY; | ||
| if (typeof json == "number") { | ||
| if (Number.isNaN(json)) { | ||
| // NaN must be encoded with string constants | ||
| throw new FieldError(field, "unexpected NaN number"); | ||
| return (json) => { | ||
| if (json === "NaN") | ||
| return NaN; | ||
| if (json === "Infinity") | ||
| return Number.POSITIVE_INFINITY; | ||
| if (json === "-Infinity") | ||
| return Number.NEGATIVE_INFINITY; | ||
| if (typeof json == "number") { | ||
| if (Number.isNaN(json)) { | ||
| // NaN must be encoded with string constants | ||
| throw new FieldError(field, "unexpected NaN number"); | ||
| } | ||
| if (!Number.isFinite(json)) { | ||
| // Infinity must be encoded with string constants | ||
| throw new FieldError(field, "unexpected infinite number"); | ||
| } | ||
| return json; | ||
| } | ||
| if (!Number.isFinite(json)) { | ||
| // Infinity must be encoded with string constants | ||
| throw new FieldError(field, "unexpected infinite number"); | ||
| if (typeof json == "string") { | ||
| if (json === "") { | ||
| // empty string is not a number | ||
| return json; | ||
| } | ||
| if (json.trim().length !== json.length) { | ||
| // extra whitespace | ||
| return json; | ||
| } | ||
| const float = Number(json); | ||
| if (!Number.isFinite(float)) { | ||
| // Infinity and NaN must be encoded with string constants | ||
| return json; | ||
| } | ||
| return float; | ||
| } | ||
| break; | ||
| } | ||
| if (typeof json == "string") { | ||
| if (json === "") { | ||
| // empty string is not a number | ||
| break; | ||
| } | ||
| if (json.trim().length !== json.length) { | ||
| // extra whitespace | ||
| break; | ||
| } | ||
| const float = Number(json); | ||
| if (!Number.isFinite(float)) { | ||
| // Infinity and NaN must be encoded with string constants | ||
| break; | ||
| } | ||
| return float; | ||
| } | ||
| break; | ||
| return json; | ||
| }; | ||
| // int32, fixed32, uint32: JSON value will be a decimal number. Either numbers or strings are accepted. | ||
@@ -375,38 +676,74 @@ case ScalarType.INT32: | ||
| case ScalarType.UINT32: | ||
| return int32FromJson(json); | ||
| return int32FromJson; | ||
| // 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 (typeof json == "string") { | ||
| if (json === "") { | ||
| return new Uint8Array(0); | ||
| return (json) => { | ||
| if (typeof json == "string") { | ||
| if (json === "") { | ||
| return new Uint8Array(0); | ||
| } | ||
| try { | ||
| return base64Decode(json); | ||
| } | ||
| catch (e) { | ||
| const message = e instanceof Error ? e.message : String(e); | ||
| throw new FieldError(field, message); | ||
| } | ||
| } | ||
| try { | ||
| return base64Decode(json); | ||
| } | ||
| catch (e) { | ||
| const message = e instanceof Error ? e.message : String(e); | ||
| throw new FieldError(field, message); | ||
| } | ||
| return json; | ||
| }; | ||
| // int64, sfixed64, sint64, fixed64, uint64: The validation step accepts | ||
| // string and number. string, bool: no conversion. | ||
| default: | ||
| return (json) => json; | ||
| } | ||
| } | ||
| /** | ||
| * Compile the conversion of a validated scalar value to its local | ||
| * representation: 64-bit integers become bigint, or string with the | ||
| * longAsString option. | ||
| */ | ||
| function compileScalarToLocal(field) { | ||
| const longAsString = field.fieldKind !== "map" && field.longAsString; | ||
| switch (field.scalar) { | ||
| case ScalarType.INT64: | ||
| case ScalarType.SFIXED64: | ||
| case ScalarType.SINT64: | ||
| if (longAsString) { | ||
| return (value) => String(value); | ||
| } | ||
| break; | ||
| return (value) => typeof value == "string" || typeof value == "number" | ||
| ? protoInt64.parse(value) | ||
| : value; | ||
| case ScalarType.FIXED64: | ||
| case ScalarType.UINT64: | ||
| if (longAsString) { | ||
| return (value) => String(value); | ||
| } | ||
| return (value) => typeof value == "string" || typeof value == "number" | ||
| ? protoInt64.uParse(value) | ||
| : value; | ||
| default: | ||
| return (value) => value; | ||
| } | ||
| return json; | ||
| } | ||
| /** | ||
| * Try to parse a JSON value to a map key for the reflect API. | ||
| * Canonicalizes 64-bit integers given as string, so that "01 and "1" are one | ||
| * key, and duplicates can raise an error. | ||
| * Returns the input if the JSON value cannot be converted. | ||
| * Return a parser from a JSON value to a map key for the given key type. | ||
| * Canonicalizes 64-bit integers given as string, so that "01" and "1" are | ||
| * one key, and duplicates can raise an error. | ||
| * The parser returns the input if the JSON value cannot be converted. | ||
| */ | ||
| function mapKeyFromJson(type, jsonString) { | ||
| function compileMapKeyParse(type) { | ||
| switch (type) { | ||
| case ScalarType.BOOL: | ||
| switch (jsonString) { | ||
| case "true": | ||
| return true; | ||
| case "false": | ||
| return false; | ||
| } | ||
| return jsonString; | ||
| return (jsonString) => { | ||
| switch (jsonString) { | ||
| case "true": | ||
| return true; | ||
| case "false": | ||
| return false; | ||
| } | ||
| return jsonString; | ||
| }; | ||
| case ScalarType.INT32: | ||
@@ -417,3 +754,3 @@ case ScalarType.FIXED32: | ||
| case ScalarType.SINT32: | ||
| return int32FromJson(jsonString); | ||
| return int32FromJson; | ||
| case ScalarType.INT64: | ||
@@ -424,7 +761,8 @@ case ScalarType.SINT64: | ||
| case ScalarType.FIXED64: | ||
| return /^-?0+$/.test(jsonString) | ||
| return (jsonString) => /^-?0+$/.test(jsonString) | ||
| ? "0" | ||
| : jsonString.replace(/^(-?)0+(?=\d)/, "$1"); | ||
| default: | ||
| return jsonString; | ||
| // ScalarType.STRING | ||
| return (jsonString) => jsonString; | ||
| } | ||
@@ -549,42 +887,2 @@ } | ||
| } | ||
| function tryWktFromJson(msg, jsonValue, ctx) { | ||
| if (!msg.desc.typeName.startsWith("google.protobuf.")) { | ||
| return false; | ||
| } | ||
| switch (msg.desc.typeName) { | ||
| case "google.protobuf.Any": | ||
| anyFromJson(msg.message, jsonValue, ctx); | ||
| return true; | ||
| case "google.protobuf.Timestamp": | ||
| timestampFromJson(msg.message, jsonValue); | ||
| return true; | ||
| case "google.protobuf.Duration": | ||
| durationFromJson(msg.message, jsonValue); | ||
| return true; | ||
| case "google.protobuf.FieldMask": | ||
| fieldMaskFromJson(msg.message, jsonValue); | ||
| return true; | ||
| case "google.protobuf.Struct": | ||
| structFromJson(msg.message, jsonValue, ctx); | ||
| return true; | ||
| case "google.protobuf.Value": | ||
| valueFromJson(msg.message, jsonValue, ctx); | ||
| return true; | ||
| case "google.protobuf.ListValue": | ||
| listValueFromJson(msg.message, jsonValue, ctx); | ||
| return true; | ||
| default: | ||
| if (isWrapperDesc(msg.desc)) { | ||
| const valueField = msg.desc.fields[0]; | ||
| if (jsonValue === null) { | ||
| msg.clear(valueField); | ||
| } | ||
| else { | ||
| msg.set(valueField, scalarFromJson(valueField, jsonValue)); | ||
| } | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
| } | ||
| function anyFromJson(any, json, ctx) { | ||
@@ -612,7 +910,6 @@ var _a; | ||
| } | ||
| const msg = reflect(desc); | ||
| const message = create(desc); | ||
| if (hasCustomJsonRepresentation(desc) && | ||
| Object.prototype.hasOwnProperty.call(json, "value")) { | ||
| const value = json.value; | ||
| readMessage(msg, value, ctx); | ||
| compiledReader(desc)(message, json.value, ctx); | ||
| } | ||
@@ -623,5 +920,5 @@ else { | ||
| delete copy["@type"]; | ||
| readMessage(msg, copy, ctx); | ||
| compiledReader(desc)(message, copy, ctx); | ||
| } | ||
| anyPack(msg.desc, msg.message, any); | ||
| anyPack(desc, message, any); | ||
| } | ||
@@ -642,4 +939,3 @@ function timestampFromJson(timestamp, json) { | ||
| } | ||
| if (ms < Date.parse("0001-01-01T00:00:00Z") || | ||
| ms > Date.parse("9999-12-31T23:59:59Z")) { | ||
| if (ms < timestampMsMin || ms > timestampMsMax) { | ||
| throw new Error(`cannot decode message ${timestamp.$typeName} from JSON: must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive`); | ||
@@ -664,3 +960,3 @@ } | ||
| const longSeconds = Number(match[1]); | ||
| if (longSeconds > 315576000000 || longSeconds < -315576000000) { | ||
| if (longSeconds > durationSecondsMax || longSeconds < durationSecondsMin) { | ||
| throw new Error(`cannot decode message ${duration.$typeName} from JSON: ${formatVal(json)}`); | ||
@@ -696,6 +992,8 @@ } | ||
| } | ||
| for (const [k, v] of Object.entries(json)) { | ||
| const parsedV = create(ValueSchema); | ||
| valueFromJson(parsedV, v, ctx); | ||
| struct.fields[k] = parsedV; | ||
| const keys = Object.keys(json); | ||
| for (let i = 0; i < keys.length; i++) { | ||
| const key = keys[i]; | ||
| const parsedValue = create(ValueSchema); | ||
| valueFromJson(parsedValue, json[key], ctx); | ||
| struct.fields[key] = parsedValue; | ||
| } | ||
@@ -742,7 +1040,7 @@ } | ||
| } | ||
| for (const e of json) { | ||
| for (let i = 0; i < json.length; i++) { | ||
| const value = create(ValueSchema); | ||
| valueFromJson(value, e, ctx); | ||
| valueFromJson(value, json[i], ctx); | ||
| listValue.values.push(value); | ||
| } | ||
| } |
@@ -1,2 +0,2 @@ | ||
| import { type DescField } from "../descriptors.js"; | ||
| import { type DescEnum, type DescField, type DescMessage, ScalarType } from "../descriptors.js"; | ||
| import { FieldError } from "./error.js"; | ||
@@ -19,2 +19,28 @@ /** | ||
| }, key: unknown, value: unknown): FieldError | undefined; | ||
| type InvalidScalarValueErr = false | "invalid UTF8" | `${string} out of range`; | ||
| /** | ||
| * Return the check for values of the given scalar type. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare function checkScalarValue(scalar: ScalarType): (value: unknown) => true | InvalidScalarValueErr; | ||
| /** | ||
| * Format the reason why a value is invalid for a singular field. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare function reasonSingular(field: { | ||
| scalar: ScalarType; | ||
| message?: undefined; | ||
| enum?: undefined; | ||
| } | { | ||
| scalar?: undefined; | ||
| message: DescMessage; | ||
| enum?: undefined; | ||
| } | { | ||
| scalar?: undefined; | ||
| message?: undefined; | ||
| enum: DescEnum; | ||
| }, val: unknown, details?: string | false): string; | ||
| export declare function formatVal(val: unknown): string; | ||
| export {}; |
@@ -61,3 +61,3 @@ // Copyright 2021-2026 Buf Technologies, Inc. | ||
| export function checkMapEntry(field, key, value) { | ||
| const checkKey = checkScalarValue(key, field.mapKey); | ||
| const checkKey = checkScalarValue(field.mapKey)(key); | ||
| if (checkKey !== true) { | ||
@@ -74,3 +74,3 @@ return new FieldError(field, `invalid map key: ${reasonSingular({ scalar: field.mapKey }, key, checkKey)}`); | ||
| if (field.scalar !== undefined) { | ||
| return checkScalarValue(value, field.scalar); | ||
| return checkScalarValue(field.scalar)(value); | ||
| } | ||
@@ -81,3 +81,3 @@ if (field.enum !== undefined) { | ||
| // int32 (see https://protobuf.dev/programming-guides/proto3/#enum). | ||
| return checkScalarValue(value, ScalarType.INT32); | ||
| return checkScalarValue(ScalarType.INT32)(value); | ||
| } | ||
@@ -88,17 +88,24 @@ return field.enum.values.some((v) => v.number === value); | ||
| } | ||
| function checkScalarValue(value, scalar) { | ||
| /** | ||
| * Return the check for values of the given scalar type. | ||
| * | ||
| * @private | ||
| */ | ||
| export function checkScalarValue(scalar) { | ||
| switch (scalar) { | ||
| case ScalarType.DOUBLE: | ||
| return typeof value == "number"; | ||
| return (value) => typeof value == "number"; | ||
| case ScalarType.FLOAT: | ||
| if (typeof value != "number") { | ||
| return false; | ||
| } | ||
| if (Number.isNaN(value) || !Number.isFinite(value)) { | ||
| return (value) => { | ||
| if (typeof value != "number") { | ||
| return false; | ||
| } | ||
| if (Number.isNaN(value) || !Number.isFinite(value)) { | ||
| return true; | ||
| } | ||
| if (value > FLOAT32_MAX || value < FLOAT32_MIN) { | ||
| return `${value.toFixed()} out of range`; | ||
| } | ||
| return true; | ||
| } | ||
| if (value > FLOAT32_MAX || value < FLOAT32_MIN) { | ||
| return `${value.toFixed()} out of range`; | ||
| } | ||
| return true; | ||
| }; | ||
| case ScalarType.INT32: | ||
@@ -108,28 +115,34 @@ case ScalarType.SFIXED32: | ||
| // signed | ||
| if (typeof value !== "number" || !Number.isInteger(value)) { | ||
| return false; | ||
| } | ||
| if (value > INT32_MAX || value < INT32_MIN) { | ||
| return `${value.toFixed()} out of range`; | ||
| } | ||
| return true; | ||
| return (value) => { | ||
| if (typeof value !== "number" || !Number.isInteger(value)) { | ||
| return false; | ||
| } | ||
| if (value > INT32_MAX || value < INT32_MIN) { | ||
| return `${value.toFixed()} out of range`; | ||
| } | ||
| return true; | ||
| }; | ||
| case ScalarType.FIXED32: | ||
| case ScalarType.UINT32: | ||
| // unsigned | ||
| if (typeof value !== "number" || !Number.isInteger(value)) { | ||
| return false; | ||
| } | ||
| if (value > UINT32_MAX || value < 0) { | ||
| return `${value.toFixed()} out of range`; | ||
| } | ||
| return true; | ||
| return (value) => { | ||
| if (typeof value !== "number" || !Number.isInteger(value)) { | ||
| return false; | ||
| } | ||
| if (value > UINT32_MAX || value < 0) { | ||
| return `${value.toFixed()} out of range`; | ||
| } | ||
| return true; | ||
| }; | ||
| case ScalarType.BOOL: | ||
| return typeof value == "boolean"; | ||
| return (value) => typeof value == "boolean"; | ||
| case ScalarType.STRING: | ||
| if (typeof value != "string") { | ||
| return false; | ||
| } | ||
| return getTextEncoding().checkUtf8(value) || "invalid UTF8"; | ||
| return (value) => { | ||
| if (typeof value != "string") { | ||
| return false; | ||
| } | ||
| return getTextEncoding().checkUtf8(value) || "invalid UTF8"; | ||
| }; | ||
| case ScalarType.BYTES: | ||
| return value instanceof Uint8Array; | ||
| return (value) => value instanceof Uint8Array; | ||
| case ScalarType.INT64: | ||
@@ -139,32 +152,41 @@ case ScalarType.SFIXED64: | ||
| // signed | ||
| if (typeof value == "bigint" || | ||
| typeof value == "number" || | ||
| (typeof value == "string" && value.length > 0)) { | ||
| try { | ||
| protoInt64.parse(value); | ||
| return true; | ||
| return (value) => { | ||
| if (typeof value == "bigint" || | ||
| typeof value == "number" || | ||
| (typeof value == "string" && value.length > 0)) { | ||
| try { | ||
| protoInt64.parse(value); | ||
| return true; | ||
| } | ||
| catch (_) { | ||
| return `${value} out of range`; | ||
| } | ||
| } | ||
| catch (_) { | ||
| return `${value} out of range`; | ||
| } | ||
| } | ||
| return false; | ||
| return false; | ||
| }; | ||
| case ScalarType.FIXED64: | ||
| case ScalarType.UINT64: | ||
| // unsigned | ||
| if (typeof value == "bigint" || | ||
| typeof value == "number" || | ||
| (typeof value == "string" && value.length > 0)) { | ||
| try { | ||
| protoInt64.uParse(value); | ||
| return true; | ||
| return (value) => { | ||
| if (typeof value == "bigint" || | ||
| typeof value == "number" || | ||
| (typeof value == "string" && value.length > 0)) { | ||
| try { | ||
| protoInt64.uParse(value); | ||
| return true; | ||
| } | ||
| catch (_) { | ||
| return `${value} out of range`; | ||
| } | ||
| } | ||
| catch (_) { | ||
| return `${value} out of range`; | ||
| } | ||
| } | ||
| return false; | ||
| return false; | ||
| }; | ||
| } | ||
| } | ||
| function reasonSingular(field, val, details) { | ||
| /** | ||
| * Format the reason why a value is invalid for a singular field. | ||
| * | ||
| * @private | ||
| */ | ||
| export function reasonSingular(field, val, details) { | ||
| details = | ||
@@ -171,0 +193,0 @@ typeof details == "string" ? `: ${details}` : `, got ${formatVal(val)}`; |
@@ -23,4 +23,3 @@ // Copyright 2021-2026 Buf Technologies, Inc. | ||
| import { isObject, isReflectList, isReflectMap, isReflectMessage, } from "./guard.js"; | ||
| // google.protobuf.NullValue.NULL_VALUE; | ||
| const NULL_VALUE = 0; | ||
| import { wktStructToLocal, wktStructToReflect } from "./message.js"; | ||
| /** | ||
@@ -467,77 +466,1 @@ * Create a ReflectMessage. | ||
| } | ||
| function wktStructToReflect(json) { | ||
| const struct = { | ||
| $typeName: "google.protobuf.Struct", | ||
| fields: {}, | ||
| }; | ||
| if (isObject(json)) { | ||
| for (const [k, v] of Object.entries(json)) { | ||
| struct.fields[k] = wktValueToReflect(v); | ||
| } | ||
| } | ||
| return struct; | ||
| } | ||
| function wktStructToLocal(val) { | ||
| const json = {}; | ||
| for (const [k, v] of Object.entries(val.fields)) { | ||
| json[k] = wktValueToLocal(v); | ||
| } | ||
| return json; | ||
| } | ||
| function wktValueToLocal(val) { | ||
| switch (val.kind.case) { | ||
| case "structValue": | ||
| return wktStructToLocal(val.kind.value); | ||
| case "listValue": | ||
| return val.kind.value.values.map(wktValueToLocal); | ||
| case "nullValue": | ||
| case undefined: | ||
| return null; | ||
| default: | ||
| return val.kind.value; | ||
| } | ||
| } | ||
| function wktValueToReflect(json) { | ||
| const value = { | ||
| $typeName: "google.protobuf.Value", | ||
| kind: { case: undefined }, | ||
| }; | ||
| switch (typeof json) { | ||
| case "number": | ||
| value.kind = { case: "numberValue", value: json }; | ||
| break; | ||
| case "string": | ||
| value.kind = { case: "stringValue", value: json }; | ||
| break; | ||
| case "boolean": | ||
| value.kind = { case: "boolValue", value: json }; | ||
| break; | ||
| case "object": | ||
| if (json === null) { | ||
| value.kind = { case: "nullValue", value: NULL_VALUE }; | ||
| } | ||
| else if (Array.isArray(json)) { | ||
| const listValue = { | ||
| $typeName: "google.protobuf.ListValue", | ||
| values: [], | ||
| }; | ||
| if (Array.isArray(json)) { | ||
| for (const e of json) { | ||
| listValue.values.push(wktValueToReflect(e)); | ||
| } | ||
| } | ||
| value.kind = { | ||
| case: "listValue", | ||
| value: listValue, | ||
| }; | ||
| } | ||
| else { | ||
| value.kind = { | ||
| case: "structValue", | ||
| value: wktStructToReflect(json), | ||
| }; | ||
| } | ||
| break; | ||
| } | ||
| return value; | ||
| } |
+11
-2
@@ -578,2 +578,3 @@ // Copyright 2021-2026 Buf Technologies, Inc. | ||
| }; | ||
| let toStr; | ||
| if (isExtension) { | ||
@@ -590,3 +591,3 @@ // extension field | ||
| field.jsonName = `[${typeName}]`; // option json_name is not allowed on extension fields | ||
| field.toString = () => `extension ${typeName}`; | ||
| toStr = () => `extension ${typeName}`; | ||
| const extendee = reg.getMessage(trimLeadingDot(proto.extendee)); | ||
@@ -606,4 +607,12 @@ assert(extendee, `invalid FieldDescriptorProto: extendee ${proto.extendee} not found`); | ||
| field.jsonName = proto.jsonName; | ||
| field.toString = () => `field ${parent.typeName}.${proto.name}`; | ||
| toStr = () => `field ${parent.typeName}.${proto.name}`; | ||
| } | ||
| // A plain assignment throws where built-in prototypes are frozen. The | ||
| // attributes match what an assignment produces. | ||
| Object.defineProperty(field, "toString", { | ||
| value: toStr, | ||
| writable: true, | ||
| enumerable: true, | ||
| configurable: true, | ||
| }); | ||
| const label = proto.label; | ||
@@ -610,0 +619,0 @@ const type = proto.type; |
@@ -22,4 +22,8 @@ import type { MessageShape } from "./types.js"; | ||
| /** | ||
| * Write a single field to binary format, if it is set. Used to serialize | ||
| * extensions: extensions always have explicit presence, so an extension | ||
| * value that was just set on the container is always written. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare function writeField(writer: BinaryWriter, opts: BinaryWriteOptions, msg: ReflectMessage, field: DescField): void; |
+363
-125
@@ -14,5 +14,10 @@ // Copyright 2021-2026 Buf Technologies, Inc. | ||
| // limitations under the License. | ||
| import { reflect } from "./reflect/reflect.js"; | ||
| import { BinaryWriter, WireType } from "./wire/binary-encoding.js"; | ||
| import { ScalarType } from "./descriptors.js"; | ||
| import { FieldError } from "./reflect/error.js"; | ||
| import { unsafeLocal } from "./reflect/unsafe.js"; | ||
| import { localMessageMapper } from "./reflect/message.js"; | ||
| import { protoInt64 } from "./proto-int64.js"; | ||
| // bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name = $number; | ||
| const IMPLICIT = 2; | ||
| // bootstrap-inject google.protobuf.FeatureSet.FieldPresence.LEGACY_REQUIRED: const $name = $number; | ||
@@ -28,154 +33,387 @@ const LEGACY_REQUIRED = 3; | ||
| export function toBinary(schema, message, options) { | ||
| return writeFields(new BinaryWriter(), makeWriteOptions(options), reflect(schema, message)).finish(); | ||
| const writer = new BinaryWriter(); | ||
| compiledWriter(schema)(writer, makeWriteOptions(options), message); | ||
| return writer.finish(); | ||
| } | ||
| function writeFields(writer, opts, msg) { | ||
| var _a; | ||
| for (const f of msg.sortedFields) { | ||
| if (!msg.isSet(f)) { | ||
| if (f.presence == LEGACY_REQUIRED) { | ||
| throw new Error(`cannot encode ${f} to binary: required field not set`); | ||
| const compiledWriters = new WeakMap(); | ||
| /** | ||
| * Return the compiled encoder for a message, compiling it on first use. | ||
| */ | ||
| function compiledWriter(desc) { | ||
| let compiled = compiledWriters.get(desc); | ||
| if (compiled === undefined) { | ||
| compiled = compileMessage(desc); | ||
| } | ||
| return compiled; | ||
| } | ||
| function compileMessage(desc) { | ||
| const typeName = desc.typeName; | ||
| const sortedFields = desc.fields.concat().sort((a, b) => a.number - b.number); | ||
| // The field reported in ForeignFieldError. | ||
| const foreignField = sortedFields[0]; | ||
| const fieldWriters = []; | ||
| const compiled = (writer, opts, message) => { | ||
| if (message.$typeName !== typeName && foreignField !== undefined) { | ||
| throw new FieldError(foreignField, `cannot use ${foreignField} with message ${message.$typeName}`, "ForeignFieldError"); | ||
| } | ||
| for (let i = 0; i < fieldWriters.length; i++) { | ||
| fieldWriters[i](writer, opts, message); | ||
| } | ||
| const unknown = message.$unknown; | ||
| if (unknown !== undefined && opts.writeUnknownFields) { | ||
| for (let i = 0; i < unknown.length; i++) { | ||
| const { no, wireType, data } = unknown[i]; | ||
| writer.tag(no, wireType).raw(data); | ||
| } | ||
| continue; | ||
| } | ||
| writeField(writer, opts, msg, f); | ||
| }; | ||
| // Register before compiling fields, so that recursive message types | ||
| // resolve to this instance instead of compiling endlessly. | ||
| compiledWriters.set(desc, compiled); | ||
| for (const field of sortedFields) { | ||
| fieldWriters.push(compileField(field)); | ||
| } | ||
| if (opts.writeUnknownFields) { | ||
| for (const { no, wireType, data } of (_a = msg.getUnknown()) !== null && _a !== void 0 ? _a : []) { | ||
| writer.tag(no, wireType).raw(data); | ||
| } | ||
| } | ||
| return writer; | ||
| return compiled; | ||
| } | ||
| /** | ||
| * @private | ||
| */ | ||
| export function writeField(writer, opts, msg, field) { | ||
| var _a; | ||
| function compileField(field) { | ||
| switch (field.fieldKind) { | ||
| case "message": | ||
| case "scalar": | ||
| case "enum": | ||
| writeScalar(writer, msg.desc.typeName, field.name, (_a = field.scalar) !== null && _a !== void 0 ? _a : ScalarType.INT32, field.number, msg.get(field)); | ||
| break; | ||
| return compileSingularField(field); | ||
| case "list": | ||
| writeListField(writer, opts, field, msg.get(field)); | ||
| break; | ||
| case "message": | ||
| writeMessageField(writer, opts, field, msg.get(field)); | ||
| break; | ||
| return compileListField(field); | ||
| case "map": | ||
| for (const [key, val] of msg.get(field)) { | ||
| writeMapEntry(writer, opts, field, key, val); | ||
| } | ||
| break; | ||
| return compileMapField(field); | ||
| } | ||
| } | ||
| function writeScalar(writer, msgName, fieldName, scalarType, fieldNo, value) { | ||
| writeScalarValue(writer.tag(fieldNo, writeTypeOfScalar(scalarType)), msgName, fieldName, scalarType, value); | ||
| } | ||
| function writeMessageField(writer, opts, field, message) { | ||
| if (field.delimitedEncoding) { | ||
| writeFields(writer.tag(field.number, WireType.StartGroup), opts, message).tag(field.number, WireType.EndGroup); | ||
| /** | ||
| * Compile an encoder for a singular field: the presence check, and the | ||
| * value encoder. | ||
| */ | ||
| function compileSingularField(field) { | ||
| const writeValue = compileSingularValue(field); | ||
| const localName = field.localName; | ||
| if (field.oneof) { | ||
| const oneofLocalName = field.oneof.localName; | ||
| return (writer, opts, message) => { | ||
| const oneof = message[oneofLocalName]; | ||
| if (oneof.case === localName) { | ||
| writeValue(writer, opts, oneof.value); | ||
| } | ||
| }; | ||
| } | ||
| else { | ||
| writeFields(writer.tag(field.number, WireType.LengthDelimited).fork(), opts, message).join(); | ||
| if (field.presence != IMPLICIT) { | ||
| const requiredError = field.presence == LEGACY_REQUIRED | ||
| ? `cannot encode ${field} to binary: required field not set` | ||
| : undefined; | ||
| return (writer, opts, message) => { | ||
| const value = message[localName]; | ||
| // Fields with explicit presence have properties on the prototype | ||
| // chain for default / zero values (except for proto3). | ||
| if (value !== undefined && | ||
| Object.prototype.hasOwnProperty.call(message, localName)) { | ||
| writeValue(writer, opts, value); | ||
| } | ||
| else if (requiredError !== undefined) { | ||
| throw new Error(requiredError); | ||
| } | ||
| }; | ||
| } | ||
| // Implicit presence: the field is set when the value is not the zero | ||
| // value. The check is inlined per type, see isScalarZeroValue. | ||
| if (field.fieldKind == "enum") { | ||
| const zero = field.enum.values[0].number; | ||
| return (writer, opts, message) => { | ||
| const value = message[localName]; | ||
| if (value !== zero) { | ||
| writeValue(writer, opts, value); | ||
| } | ||
| }; | ||
| } | ||
| switch (field.scalar) { | ||
| case ScalarType.BOOL: | ||
| return (writer, opts, message) => { | ||
| const value = message[localName]; | ||
| if (value !== false) { | ||
| writeValue(writer, opts, value); | ||
| } | ||
| }; | ||
| case ScalarType.STRING: | ||
| return (writer, opts, message) => { | ||
| const value = message[localName]; | ||
| if (value !== "") { | ||
| writeValue(writer, opts, value); | ||
| } | ||
| }; | ||
| case ScalarType.BYTES: | ||
| return (writer, opts, message) => { | ||
| const value = message[localName]; | ||
| if (!(value instanceof Uint8Array) || value.byteLength > 0) { | ||
| writeValue(writer, opts, value); | ||
| } | ||
| }; | ||
| case ScalarType.DOUBLE: | ||
| case ScalarType.FLOAT: | ||
| return (writer, opts, message) => { | ||
| const value = message[localName]; | ||
| // Object.is distinguishes -0 from 0. | ||
| if (!Object.is(value, 0)) { | ||
| writeValue(writer, opts, value); | ||
| } | ||
| }; | ||
| default: | ||
| return (writer, opts, message) => { | ||
| const value = message[localName]; | ||
| // Loose comparison matches 0n, 0 and "0". | ||
| if (value != 0) { | ||
| writeValue(writer, opts, value); | ||
| } | ||
| }; | ||
| } | ||
| } | ||
| function writeListField(writer, opts, field, list) { | ||
| var _a; | ||
| if (field.listKind == "message") { | ||
| for (const item of list) { | ||
| writeMessageField(writer, opts, field, item); | ||
| /** | ||
| * Compile an encoder for the value of a singular field, including the tag. | ||
| */ | ||
| function compileSingularValue(field) { | ||
| switch (field.fieldKind) { | ||
| case "message": { | ||
| const { toMessage } = localMessageMapper(field); | ||
| const writeChild = compileChildWriter(field); | ||
| return (writer, opts, value) => { | ||
| writeChild(writer, opts, toMessage(value)); | ||
| }; | ||
| } | ||
| return; | ||
| case "scalar": | ||
| case "enum": { | ||
| const scalarType = field.fieldKind == "enum" ? ScalarType.INT32 : field.scalar; | ||
| const fieldNo = field.number; | ||
| const wireType = writeTypeOfScalar(scalarType); | ||
| const writeScalar = compileScalarValue(scalarType, field.parent.typeName, field.name); | ||
| return (writer, opts, value) => { | ||
| writer.tag(fieldNo, wireType); | ||
| writeScalar(writer, value); | ||
| }; | ||
| } | ||
| } | ||
| const scalarType = (_a = field.scalar) !== null && _a !== void 0 ? _a : ScalarType.INT32; | ||
| if (field.packed) { | ||
| if (!list.size) { | ||
| return; | ||
| } | ||
| function compileListField(field) { | ||
| const localName = field.localName; | ||
| const fieldNo = field.number; | ||
| switch (field.listKind) { | ||
| case "message": { | ||
| const { toMessage } = localMessageMapper(field); | ||
| const writeChild = compileChildWriter(field); | ||
| return (writer, opts, message) => { | ||
| const items = message[localName]; | ||
| for (let i = 0; i < items.length; i++) { | ||
| writeChild(writer, opts, toMessage(items[i])); | ||
| } | ||
| }; | ||
| } | ||
| writer.tag(field.number, WireType.LengthDelimited).fork(); | ||
| for (const item of list) { | ||
| writeScalarValue(writer, field.parent.typeName, field.name, scalarType, item); | ||
| case "scalar": | ||
| case "enum": { | ||
| const scalarType = field.listKind == "enum" ? ScalarType.INT32 : field.scalar; | ||
| const writeScalar = compileScalarValue(scalarType, field.parent.typeName, field.name); | ||
| if (field.packed) { | ||
| return (writer, opts, message) => { | ||
| const items = message[localName]; | ||
| if (items.length == 0) { | ||
| return; | ||
| } | ||
| writer.tag(fieldNo, WireType.LengthDelimited).fork(); | ||
| for (let i = 0; i < items.length; i++) { | ||
| writeScalar(writer, items[i]); | ||
| } | ||
| writer.join(); | ||
| }; | ||
| } | ||
| const wireType = writeTypeOfScalar(scalarType); | ||
| return (writer, opts, message) => { | ||
| const items = message[localName]; | ||
| for (let i = 0; i < items.length; i++) { | ||
| writer.tag(fieldNo, wireType); | ||
| writeScalar(writer, items[i]); | ||
| } | ||
| }; | ||
| } | ||
| writer.join(); | ||
| return; | ||
| } | ||
| for (const item of list) { | ||
| writeScalar(writer, field.parent.typeName, field.name, scalarType, field.number, item); | ||
| } | ||
| function compileMapField(field) { | ||
| const localName = field.localName; | ||
| const fieldNo = field.number; | ||
| const writeKey = compileMapKey(field); | ||
| if (field.mapKind == "message") { | ||
| const { toMessage } = localMessageMapper(field); | ||
| const writeMessage = compiledWriter(field.message); | ||
| return (writer, opts, message) => { | ||
| const record = message[localName]; | ||
| const keys = Object.keys(record); | ||
| for (let i = 0; i < keys.length; i++) { | ||
| const key = keys[i]; | ||
| writer.tag(fieldNo, WireType.LengthDelimited).fork(); | ||
| writeKey(writer, key); | ||
| // The value of a map entry is always field number 2. | ||
| writer.tag(2, WireType.LengthDelimited).fork(); | ||
| writeMessage(writer, opts, toMessage(record[key])); | ||
| writer.join(); | ||
| writer.join(); | ||
| } | ||
| }; | ||
| } | ||
| const scalarType = field.mapKind == "enum" ? ScalarType.INT32 : field.scalar; | ||
| const valueWireType = writeTypeOfScalar(scalarType); | ||
| const writeScalar = compileScalarValue(scalarType, field.parent.typeName, field.name); | ||
| return (writer, opts, message) => { | ||
| const record = message[localName]; | ||
| const keys = Object.keys(record); | ||
| for (let i = 0; i < keys.length; i++) { | ||
| const key = keys[i]; | ||
| writer.tag(fieldNo, WireType.LengthDelimited).fork(); | ||
| writeKey(writer, key); | ||
| // The value of a map entry is always field number 2. | ||
| writer.tag(2, valueWireType); | ||
| writeScalar(writer, record[key]); | ||
| writer.join(); | ||
| } | ||
| }; | ||
| } | ||
| function writeMapEntry(writer, opts, field, key, value) { | ||
| var _a; | ||
| writer.tag(field.number, WireType.LengthDelimited).fork(); | ||
| // write key, expecting key field number = 1 | ||
| writeScalar(writer, field.parent.typeName, field.name, field.mapKey, 1, key); | ||
| // write value, expecting value field number = 2 | ||
| switch (field.mapKind) { | ||
| case "scalar": | ||
| case "enum": | ||
| writeScalar(writer, field.parent.typeName, field.name, (_a = field.scalar) !== null && _a !== void 0 ? _a : ScalarType.INT32, 2, value); | ||
| break; | ||
| case "message": | ||
| writeFields(writer.tag(2, WireType.LengthDelimited).fork(), opts, value).join(); | ||
| break; | ||
| /** | ||
| * Compile an encoder for a map key. Map keys are stored as object keys and | ||
| * are always strings locally. Convert them to their scalar type before | ||
| * writing, like the reflect API does when iterating map entries. | ||
| */ | ||
| function compileMapKey(field) { | ||
| const wireType = writeTypeOfScalar(field.mapKey); | ||
| const writeScalar = compileScalarValue(field.mapKey, field.parent.typeName, field.name); | ||
| const convertKey = compileMapKeyConverter(field.mapKey); | ||
| return (writer, key) => { | ||
| // The key of a map entry is always field number 1. | ||
| writer.tag(1, wireType); | ||
| writeScalar(writer, convertKey(key)); | ||
| }; | ||
| } | ||
| /** | ||
| * Returns a converter from an object key (always a string) to the closest | ||
| * possible type for the map key type. Invalid keys are passed through to | ||
| * the scalar writer, which raises an error for them. | ||
| */ | ||
| function compileMapKeyConverter(type) { | ||
| switch (type) { | ||
| case ScalarType.STRING: | ||
| return (key) => key; | ||
| case ScalarType.BOOL: | ||
| return (key) => (key === "true" ? true : key === "false" ? false : key); | ||
| case ScalarType.UINT64: | ||
| case ScalarType.FIXED64: | ||
| return (key) => { | ||
| try { | ||
| return protoInt64.uParse(key); | ||
| } | ||
| catch (_a) { | ||
| return key; | ||
| } | ||
| }; | ||
| case ScalarType.INT64: | ||
| case ScalarType.SFIXED64: | ||
| case ScalarType.SINT64: | ||
| return (key) => { | ||
| try { | ||
| return protoInt64.parse(key); | ||
| } | ||
| catch (_a) { | ||
| return key; | ||
| } | ||
| }; | ||
| default: | ||
| // Handles INT32, UINT32, SINT32, FIXED32, SFIXED32. | ||
| // We do not use individual cases to save a few bytes code size. | ||
| return (key) => { | ||
| const n = Number.parseInt(key); | ||
| return Number.isFinite(n) ? n : key; | ||
| }; | ||
| } | ||
| writer.join(); | ||
| } | ||
| function writeScalarValue(writer, msgName, fieldName, type, value) { | ||
| try { | ||
| switch (type) { | ||
| case ScalarType.STRING: | ||
| writer.string(value); | ||
| break; | ||
| case ScalarType.BOOL: | ||
| writer.bool(value); | ||
| break; | ||
| case ScalarType.DOUBLE: | ||
| writer.double(value); | ||
| break; | ||
| case ScalarType.FLOAT: | ||
| writer.float(value); | ||
| break; | ||
| case ScalarType.INT32: | ||
| writer.int32(value); | ||
| break; | ||
| case ScalarType.INT64: | ||
| writer.int64(value); | ||
| break; | ||
| case ScalarType.UINT64: | ||
| writer.uint64(value); | ||
| break; | ||
| case ScalarType.FIXED64: | ||
| writer.fixed64(value); | ||
| break; | ||
| case ScalarType.BYTES: | ||
| writer.bytes(value); | ||
| break; | ||
| case ScalarType.FIXED32: | ||
| writer.fixed32(value); | ||
| break; | ||
| case ScalarType.SFIXED32: | ||
| writer.sfixed32(value); | ||
| break; | ||
| case ScalarType.SFIXED64: | ||
| writer.sfixed64(value); | ||
| break; | ||
| case ScalarType.SINT64: | ||
| writer.sint64(value); | ||
| break; | ||
| case ScalarType.UINT32: | ||
| writer.uint32(value); | ||
| break; | ||
| case ScalarType.SINT32: | ||
| writer.sint32(value); | ||
| break; | ||
| /** | ||
| * Compile an encoder for a bare scalar value (no tag), wrapping errors from | ||
| * the writer with the message and field name. | ||
| */ | ||
| function compileScalarValue(type, messageName, fieldName) { | ||
| const writeScalar = compileScalarWrite(type); | ||
| return (writer, value) => { | ||
| try { | ||
| writeScalar(writer, value); | ||
| } | ||
| } | ||
| catch (e) { | ||
| if (e instanceof Error) { | ||
| throw new Error(`cannot encode field ${msgName}.${fieldName} to binary: ${e.message}`); | ||
| catch (e) { | ||
| if (e instanceof Error) { | ||
| throw new Error(`cannot encode field ${messageName}.${fieldName} to binary: ${e.message}`); | ||
| } | ||
| throw e; | ||
| } | ||
| throw e; | ||
| }; | ||
| } | ||
| function compileScalarWrite(type) { | ||
| switch (type) { | ||
| case ScalarType.STRING: | ||
| return (writer, value) => writer.string(value); | ||
| case ScalarType.BOOL: | ||
| return (writer, value) => writer.bool(value); | ||
| case ScalarType.DOUBLE: | ||
| return (writer, value) => writer.double(value); | ||
| case ScalarType.FLOAT: | ||
| return (writer, value) => writer.float(value); | ||
| case ScalarType.INT32: | ||
| return (writer, value) => writer.int32(value); | ||
| case ScalarType.INT64: | ||
| return (writer, value) => writer.int64(value); | ||
| case ScalarType.UINT64: | ||
| return (writer, value) => writer.uint64(value); | ||
| case ScalarType.FIXED64: | ||
| return (writer, value) => writer.fixed64(value); | ||
| case ScalarType.BYTES: | ||
| return (writer, value) => writer.bytes(value); | ||
| case ScalarType.FIXED32: | ||
| return (writer, value) => writer.fixed32(value); | ||
| case ScalarType.SFIXED32: | ||
| return (writer, value) => writer.sfixed32(value); | ||
| case ScalarType.SFIXED64: | ||
| return (writer, value) => writer.sfixed64(value); | ||
| case ScalarType.SINT64: | ||
| return (writer, value) => writer.sint64(value); | ||
| case ScalarType.UINT32: | ||
| return (writer, value) => writer.uint32(value); | ||
| case ScalarType.SINT32: | ||
| return (writer, value) => writer.sint32(value); | ||
| } | ||
| } | ||
| /** | ||
| * Write a single field to binary format, if it is set. Used to serialize | ||
| * extensions: extensions always have explicit presence, so an extension | ||
| * value that was just set on the container is always written. | ||
| * | ||
| * @private | ||
| */ | ||
| export function writeField(writer, opts, msg, field) { | ||
| compileField(field)(writer, opts, msg[unsafeLocal]); | ||
| } | ||
| /** | ||
| * Compile an encoder for the wire format of a message field, honoring the | ||
| * delimited encoding of the field. The tag is written by the encoder. | ||
| */ | ||
| function compileChildWriter(field) { | ||
| const fieldNo = field.number; | ||
| const writeMessage = compiledWriter(field.message); | ||
| if (field.delimitedEncoding) { | ||
| return (writer, opts, child) => { | ||
| writer.tag(fieldNo, WireType.StartGroup); | ||
| writeMessage(writer, opts, child); | ||
| writer.tag(fieldNo, WireType.EndGroup); | ||
| }; | ||
| } | ||
| return (writer, opts, child) => { | ||
| writer.tag(fieldNo, WireType.LengthDelimited).fork(); | ||
| writeMessage(writer, opts, child); | ||
| writer.join(); | ||
| }; | ||
| } | ||
| function writeTypeOfScalar(type) { | ||
@@ -182,0 +420,0 @@ switch (type) { |
+393
-159
@@ -16,8 +16,12 @@ // Copyright 2021-2026 Buf Technologies, Inc. | ||
| import { protoCamelCase, protoSnakeCase } from "./reflect/names.js"; | ||
| import { reflect } from "./reflect/reflect.js"; | ||
| import { anyUnpack } from "./wkt/index.js"; | ||
| import { hasCustomJsonRepresentation, isWrapperDesc } from "./wkt/wrappers.js"; | ||
| import { durationSecondsMax, durationSecondsMin, timestampMsMax, timestampMsMin, } from "./wkt/json.js"; | ||
| import { base64Encode } from "./wire/index.js"; | ||
| import { createExtensionContainer, getExtension } from "./extensions.js"; | ||
| import { checkField, formatVal } from "./reflect/reflect-check.js"; | ||
| import { FieldError } from "./reflect/error.js"; | ||
| import { unsafeLocal } from "./reflect/unsafe.js"; | ||
| import { scalarZeroValue } from "./reflect/scalar.js"; | ||
| import { localMessageMapper } from "./reflect/message.js"; | ||
| // bootstrap-inject google.protobuf.FeatureSet.FieldPresence.LEGACY_REQUIRED: const $name = $number; | ||
@@ -41,3 +45,3 @@ const LEGACY_REQUIRED = 3; | ||
| export function toJson(schema, message, options) { | ||
| return reflectToJson(reflect(schema, message), makeWriteOptions(options)); | ||
| return compiledWriter(schema)(makeWriteOptions(options), message); | ||
| } | ||
@@ -66,119 +70,333 @@ /** | ||
| } | ||
| function reflectToJson(msg, opts) { | ||
| var _a; | ||
| const wktJson = tryWktToJson(msg, opts); | ||
| if (wktJson !== undefined) | ||
| return wktJson; | ||
| const json = {}; | ||
| for (const f of msg.sortedFields) { | ||
| if (!msg.isSet(f)) { | ||
| if (f.presence == LEGACY_REQUIRED) { | ||
| throw new Error(`cannot encode ${f} to JSON: required field not set`); | ||
| const compiledWriters = new WeakMap(); | ||
| /** | ||
| * Return the compiled encoder for a message, compiling it on first use. | ||
| */ | ||
| function compiledWriter(desc) { | ||
| let compiled = compiledWriters.get(desc); | ||
| if (compiled === undefined) { | ||
| compiled = compileMessage(desc); | ||
| } | ||
| return compiled; | ||
| } | ||
| function compileMessage(desc) { | ||
| const typeName = desc.typeName; | ||
| const writeWkt = compileWkt(desc); | ||
| if (writeWkt !== undefined) { | ||
| // The field reported in ForeignFieldError. All well-known types with a | ||
| // custom JSON representation have at least one field. | ||
| const foreignField = desc.fields[0]; | ||
| const compiledWriter = (opts, message) => { | ||
| if (message.$typeName !== typeName && foreignField !== undefined) { | ||
| throw new FieldError(foreignField, `cannot use ${foreignField} with message ${message.$typeName}`, "ForeignFieldError"); | ||
| } | ||
| if (!opts.alwaysEmitImplicit || f.presence !== IMPLICIT) { | ||
| // Fields with implicit presence omit zero values (e.g. empty string) by default | ||
| continue; | ||
| } | ||
| return writeWkt(opts, message); | ||
| }; | ||
| compiledWriters.set(desc, compiledWriter); | ||
| return compiledWriter; | ||
| } | ||
| const sortedFields = desc.fields.concat().sort((a, b) => a.number - b.number); | ||
| // The field reported in ForeignFieldError. | ||
| const foreignField = sortedFields[0]; | ||
| const fieldWriters = []; | ||
| const compiledWriter = (opts, message) => { | ||
| if (message.$typeName !== typeName && foreignField !== undefined) { | ||
| throw new FieldError(foreignField, `cannot use ${foreignField} with message ${message.$typeName}`, "ForeignFieldError"); | ||
| } | ||
| const jsonValue = fieldToJson(f, msg.get(f), opts); | ||
| if (jsonValue !== undefined) { | ||
| json[jsonName(f, opts)] = jsonValue; | ||
| const json = {}; | ||
| for (let i = 0; i < fieldWriters.length; i++) { | ||
| fieldWriters[i](opts, message, json); | ||
| } | ||
| if (opts.registry) { | ||
| writeExtensions(json, opts, opts.registry, message, desc); | ||
| } | ||
| return json; | ||
| }; | ||
| // Register before compiling fields, so that recursive message types | ||
| // resolve to this instance instead of compiling endlessly. | ||
| compiledWriters.set(desc, compiledWriter); | ||
| for (const field of sortedFields) { | ||
| fieldWriters.push(compileField(field)); | ||
| } | ||
| if (opts.registry) { | ||
| const tagSeen = new Set(); | ||
| for (const { no } of (_a = msg.getUnknown()) !== null && _a !== void 0 ? _a : []) { | ||
| // Same tag can appear multiple times, so we | ||
| // keep track and skip identical ones. | ||
| if (!tagSeen.has(no)) { | ||
| tagSeen.add(no); | ||
| const extension = opts.registry.getExtensionFor(msg.desc, no); | ||
| if (!extension) { | ||
| continue; | ||
| return compiledWriter; | ||
| } | ||
| /** | ||
| * Compile an encoder for a well-known type with a custom JSON representation, | ||
| * or return undefined for other messages. | ||
| */ | ||
| function compileWkt(desc) { | ||
| if (!desc.typeName.startsWith("google.protobuf.")) { | ||
| return undefined; | ||
| } | ||
| switch (desc.typeName) { | ||
| case "google.protobuf.Any": | ||
| return (opts, message) => anyToJson(message, opts); | ||
| case "google.protobuf.Timestamp": | ||
| return (opts, message) => timestampToJson(message); | ||
| case "google.protobuf.Duration": | ||
| return (opts, message) => durationToJson(message); | ||
| case "google.protobuf.FieldMask": | ||
| return (opts, message) => fieldMaskToJson(message); | ||
| case "google.protobuf.Struct": | ||
| return (opts, message) => structToJson(message); | ||
| case "google.protobuf.Value": | ||
| return (opts, message) => valueToJson(message); | ||
| case "google.protobuf.ListValue": | ||
| return (opts, message) => listValueToJson(message); | ||
| default: | ||
| if (isWrapperDesc(desc)) { | ||
| const valueField = desc.fields[0]; | ||
| const localName = valueField.localName; | ||
| const zero = scalarZeroValue(valueField.scalar, false); | ||
| const writeScalar = compileScalarValue(valueField); | ||
| return (opts, message) => { | ||
| const value = message[localName]; | ||
| return writeScalar(opts, value === undefined ? zero : value); | ||
| }; | ||
| } | ||
| return undefined; | ||
| } | ||
| } | ||
| function compileField(field) { | ||
| switch (field.fieldKind) { | ||
| case "scalar": | ||
| case "enum": | ||
| case "message": | ||
| return compileSingularField(field); | ||
| case "list": | ||
| case "map": { | ||
| const writeValue = field.fieldKind == "list" | ||
| ? compileListValue(field) | ||
| : compileMapValue(field); | ||
| const protoName = field.name; | ||
| const jsonKey = field.jsonName; | ||
| const localName = field.localName; | ||
| return (opts, message, json) => { | ||
| const value = writeValue(opts, message[localName]); | ||
| if (value !== undefined) { | ||
| json[opts.useProtoFieldName ? protoName : jsonKey] = value; | ||
| } | ||
| const value = getExtension(msg.message, extension); | ||
| const [container, field] = createExtensionContainer(extension, value); | ||
| const jsonValue = fieldToJson(field, container.get(field), opts); | ||
| if (jsonValue !== undefined) { | ||
| json[extension.jsonName] = jsonValue; | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| } | ||
| return json; | ||
| } | ||
| function fieldToJson(f, val, opts) { | ||
| switch (f.fieldKind) { | ||
| /** | ||
| * Compile an encoder for a singular field: the presence check, and the | ||
| * value encoder. | ||
| */ | ||
| function compileSingularField(field) { | ||
| const writeValue = compileSingularValue(field); | ||
| const protoName = field.name; | ||
| const jsonKey = field.jsonName; | ||
| const localName = field.localName; | ||
| if (field.oneof) { | ||
| const oneofLocalName = field.oneof.localName; | ||
| return (opts, message, json) => { | ||
| const oneof = message[oneofLocalName]; | ||
| if (oneof.case === localName) { | ||
| json[opts.useProtoFieldName ? protoName : jsonKey] = writeValue(opts, oneof.value); | ||
| } | ||
| }; | ||
| } | ||
| if (field.presence != IMPLICIT) { | ||
| const requiredError = field.presence == LEGACY_REQUIRED | ||
| ? `cannot encode ${field} to JSON: required field not set` | ||
| : undefined; | ||
| return (opts, message, json) => { | ||
| const value = message[localName]; | ||
| // Fields with explicit presence have properties on the prototype | ||
| // chain for default / zero values (except for proto3). | ||
| if (value !== undefined && | ||
| Object.prototype.hasOwnProperty.call(message, localName)) { | ||
| json[opts.useProtoFieldName ? protoName : jsonKey] = writeValue(opts, value); | ||
| } | ||
| else if (requiredError !== undefined) { | ||
| throw new Error(requiredError); | ||
| } | ||
| }; | ||
| } | ||
| // Implicit presence: the field is emitted when the value is not the zero | ||
| // value, or when alwaysEmitImplicit is enabled. The zero check is inlined | ||
| // per type, see isScalarZeroValue. | ||
| if (field.fieldKind == "enum") { | ||
| const zero = field.enum.values[0].number; | ||
| return (opts, message, json) => { | ||
| const value = message[localName]; | ||
| if (value !== zero || opts.alwaysEmitImplicit) { | ||
| json[opts.useProtoFieldName ? protoName : jsonKey] = writeValue(opts, value); | ||
| } | ||
| }; | ||
| } | ||
| switch (field.scalar) { | ||
| case ScalarType.BOOL: | ||
| return (opts, message, json) => { | ||
| const value = message[localName]; | ||
| if (value !== false || opts.alwaysEmitImplicit) { | ||
| json[opts.useProtoFieldName ? protoName : jsonKey] = writeValue(opts, value); | ||
| } | ||
| }; | ||
| case ScalarType.STRING: | ||
| return (opts, message, json) => { | ||
| const value = message[localName]; | ||
| if (value !== "" || opts.alwaysEmitImplicit) { | ||
| json[opts.useProtoFieldName ? protoName : jsonKey] = writeValue(opts, value); | ||
| } | ||
| }; | ||
| case ScalarType.BYTES: | ||
| return (opts, message, json) => { | ||
| const value = message[localName]; | ||
| if (!(value instanceof Uint8Array) || | ||
| value.byteLength > 0 || | ||
| opts.alwaysEmitImplicit) { | ||
| json[opts.useProtoFieldName ? protoName : jsonKey] = writeValue(opts, value); | ||
| } | ||
| }; | ||
| case ScalarType.DOUBLE: | ||
| case ScalarType.FLOAT: | ||
| return (opts, message, json) => { | ||
| const value = message[localName]; | ||
| // Object.is distinguishes -0 from 0. | ||
| if (!Object.is(value, 0) || opts.alwaysEmitImplicit) { | ||
| json[opts.useProtoFieldName ? protoName : jsonKey] = writeValue(opts, value); | ||
| } | ||
| }; | ||
| default: | ||
| return (opts, message, json) => { | ||
| const value = message[localName]; | ||
| // Loose comparison matches 0n, 0 and "0". | ||
| if (value != 0 || opts.alwaysEmitImplicit) { | ||
| json[opts.useProtoFieldName ? protoName : jsonKey] = writeValue(opts, value); | ||
| } | ||
| }; | ||
| } | ||
| } | ||
| /** | ||
| * Compile an encoder for the value of a field of any kind. Used for | ||
| * extension values. | ||
| */ | ||
| function compileFieldValue(field) { | ||
| switch (field.fieldKind) { | ||
| case "scalar": | ||
| return scalarToJson(f, val); | ||
| case "enum": | ||
| case "message": | ||
| return reflectToJson(val, opts); | ||
| case "enum": | ||
| return enumToJsonInternal(f.enum, val, opts.enumAsInteger); | ||
| return compileSingularValue(field); | ||
| case "list": | ||
| return listToJson(val, opts); | ||
| return compileListValue(field); | ||
| case "map": | ||
| return mapToJson(val, opts); | ||
| return compileMapValue(field); | ||
| } | ||
| } | ||
| function mapToJson(map, opts) { | ||
| const f = map.field(); | ||
| const jsonObj = {}; | ||
| switch (f.mapKind) { | ||
| /** | ||
| * Compile an encoder for the value of a singular field. | ||
| */ | ||
| function compileSingularValue(field) { | ||
| switch (field.fieldKind) { | ||
| case "scalar": | ||
| for (const [entryKey, entryValue] of map) { | ||
| jsonObj[entryKey] = scalarToJson(f, entryValue); | ||
| } | ||
| break; | ||
| return compileScalarValue(field); | ||
| case "enum": | ||
| return compileEnumValue(field); | ||
| case "message": | ||
| for (const [entryKey, entryValue] of map) { | ||
| jsonObj[entryKey] = reflectToJson(entryValue, opts); | ||
| } | ||
| break; | ||
| return compileMessageValue(field); | ||
| } | ||
| } | ||
| /** | ||
| * Compile an encoder for the value of a message field. | ||
| */ | ||
| function compileMessageValue(field) { | ||
| const { toMessage } = localMessageMapper(field); | ||
| const writeMessage = compiledWriter(field.message); | ||
| return (opts, value) => writeMessage(opts, toMessage(value)); | ||
| } | ||
| /** | ||
| * Compile an encoder for a list field value. Returns undefined for an empty | ||
| * list, unless alwaysEmitImplicit is enabled. | ||
| */ | ||
| function compileListValue(field) { | ||
| const writeItem = compileListItemValue(field); | ||
| return (opts, value) => { | ||
| const items = value; | ||
| if (items.length == 0 && !opts.alwaysEmitImplicit) { | ||
| return undefined; | ||
| } | ||
| const jsonArray = []; | ||
| for (let i = 0; i < items.length; i++) { | ||
| jsonArray.push(writeItem(opts, items[i])); | ||
| } | ||
| return jsonArray; | ||
| }; | ||
| } | ||
| function compileListItemValue(field) { | ||
| switch (field.listKind) { | ||
| case "scalar": | ||
| return compileScalarValue(field); | ||
| case "enum": | ||
| for (const [entryKey, entryValue] of map) { | ||
| jsonObj[entryKey] = enumToJsonInternal(f.enum, entryValue, opts.enumAsInteger); | ||
| } | ||
| break; | ||
| return compileEnumValue(field); | ||
| case "message": | ||
| return compileMessageValue(field); | ||
| } | ||
| return opts.alwaysEmitImplicit || map.size > 0 ? jsonObj : undefined; | ||
| } | ||
| function listToJson(list, opts) { | ||
| const f = list.field(); | ||
| const jsonArr = []; | ||
| switch (f.listKind) { | ||
| /** | ||
| * Compile an encoder for a map field value. Returns undefined for an empty | ||
| * map, unless alwaysEmitImplicit is enabled. Map keys are stored as object | ||
| * keys and are used as JSON keys as-is. | ||
| */ | ||
| function compileMapValue(field) { | ||
| const writeMapValue = compileMapEntryValue(field); | ||
| return (opts, value) => { | ||
| const record = value; | ||
| const keys = Object.keys(record); | ||
| if (keys.length == 0 && !opts.alwaysEmitImplicit) { | ||
| return undefined; | ||
| } | ||
| const jsonObject = {}; | ||
| for (let i = 0; i < keys.length; i++) { | ||
| const key = keys[i]; | ||
| jsonObject[key] = writeMapValue(opts, record[key]); | ||
| } | ||
| return jsonObject; | ||
| }; | ||
| } | ||
| function compileMapEntryValue(field) { | ||
| switch (field.mapKind) { | ||
| case "scalar": | ||
| for (const item of list) { | ||
| jsonArr.push(scalarToJson(f, item)); | ||
| } | ||
| break; | ||
| return compileScalarValue(field); | ||
| case "enum": | ||
| for (const item of list) { | ||
| jsonArr.push(enumToJsonInternal(f.enum, item, opts.enumAsInteger)); | ||
| } | ||
| break; | ||
| return compileEnumValue(field); | ||
| case "message": | ||
| for (const item of list) { | ||
| jsonArr.push(reflectToJson(item, opts)); | ||
| } | ||
| break; | ||
| return compileMessageValue(field); | ||
| } | ||
| return opts.alwaysEmitImplicit || jsonArr.length > 0 ? jsonArr : undefined; | ||
| } | ||
| function enumToJsonInternal(desc, value, enumAsInteger) { | ||
| var _a; | ||
| if (typeof value != "number") { | ||
| throw new Error(`cannot encode ${desc} to JSON: expected number, got ${formatVal(value)}`); | ||
| } | ||
| /** | ||
| * Compile an encoder for an enum value. | ||
| */ | ||
| function compileEnumValue(field) { | ||
| const desc = field.enum; | ||
| if (desc.typeName == "google.protobuf.NullValue") { | ||
| return null; | ||
| return (opts, value) => { | ||
| if (typeof value != "number") { | ||
| throw errorEnumValue(desc, value); | ||
| } | ||
| return null; | ||
| }; | ||
| } | ||
| if (enumAsInteger) { | ||
| return value; | ||
| } | ||
| const val = desc.value[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 | ||
| return (opts, value) => { | ||
| var _a, _b; | ||
| if (typeof value != "number") { | ||
| throw errorEnumValue(desc, value); | ||
| } | ||
| if (opts.enumAsInteger) { | ||
| return value; | ||
| } | ||
| // If we don't know the enum value, just return the number. | ||
| return (_b = (_a = desc.value[value]) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : value; | ||
| }; | ||
| } | ||
| function scalarToJson(field, value) { | ||
| var _a, _b, _c, _d, _e, _f; | ||
| function errorEnumValue(desc, value) { | ||
| return new Error(`cannot encode ${desc} to JSON: expected number, got ${formatVal(value)}`); | ||
| } | ||
| /** | ||
| * Compile an encoder for a scalar value. Errors report the original field | ||
| * descriptor, which may be a list or map field for items of those fields. | ||
| */ | ||
| function compileScalarValue(field) { | ||
| switch (field.scalar) { | ||
@@ -191,32 +409,40 @@ // int32, fixed32, uint32: JSON value will be a decimal number. Either numbers or strings are accepted. | ||
| case ScalarType.UINT32: | ||
| if (typeof value != "number") { | ||
| throw new Error(`cannot encode ${field} to JSON: ${(_a = checkField(field, value)) === null || _a === void 0 ? void 0 : _a.message}`); | ||
| } | ||
| return value; | ||
| return (opts, value) => { | ||
| if (typeof value != "number") { | ||
| throw errorScalarValue(field, value); | ||
| } | ||
| 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: | ||
| case ScalarType.DOUBLE: // eslint-disable-line no-fallthrough | ||
| if (typeof value != "number") { | ||
| throw new Error(`cannot encode ${field} to JSON: ${(_b = checkField(field, value)) === null || _b === void 0 ? void 0 : _b.message}`); | ||
| } | ||
| if (Number.isNaN(value)) | ||
| return "NaN"; | ||
| if (value === Number.POSITIVE_INFINITY) | ||
| return "Infinity"; | ||
| if (value === Number.NEGATIVE_INFINITY) | ||
| return "-Infinity"; | ||
| return value; | ||
| case ScalarType.DOUBLE: | ||
| return (opts, value) => { | ||
| if (typeof value != "number") { | ||
| throw errorScalarValue(field, value); | ||
| } | ||
| 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: | ||
| if (typeof value != "string") { | ||
| throw new Error(`cannot encode ${field} to JSON: ${(_c = checkField(field, value)) === null || _c === void 0 ? void 0 : _c.message}`); | ||
| } | ||
| return value; | ||
| return (opts, value) => { | ||
| if (typeof value != "string") { | ||
| throw errorScalarValue(field, value); | ||
| } | ||
| return value; | ||
| }; | ||
| // bool: | ||
| case ScalarType.BOOL: | ||
| if (typeof value != "boolean") { | ||
| throw new Error(`cannot encode ${field} to JSON: ${(_d = checkField(field, value)) === null || _d === void 0 ? void 0 : _d.message}`); | ||
| } | ||
| return value; | ||
| return (opts, value) => { | ||
| if (typeof value != "boolean") { | ||
| throw errorScalarValue(field, value); | ||
| } | ||
| return value; | ||
| }; | ||
| // JSON value will be a decimal string. Either numbers or strings are accepted. | ||
@@ -228,46 +454,52 @@ case ScalarType.UINT64: | ||
| case ScalarType.SINT64: | ||
| if (typeof value == "bigint" || | ||
| typeof value == "string" || | ||
| (typeof value == "number" && Number.isInteger(value))) { | ||
| return value.toString(); | ||
| } | ||
| throw new Error(`cannot encode ${field} to JSON: ${(_e = checkField(field, value)) === null || _e === void 0 ? void 0 : _e.message}`); | ||
| return (opts, value) => { | ||
| if (typeof value == "bigint" || | ||
| typeof value == "string" || | ||
| (typeof value == "number" && Number.isInteger(value))) { | ||
| return value.toString(); | ||
| } | ||
| throw errorScalarValue(field, value); | ||
| }; | ||
| // 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 (value instanceof Uint8Array) { | ||
| return base64Encode(value); | ||
| } | ||
| throw new Error(`cannot encode ${field} to JSON: ${(_f = checkField(field, value)) === null || _f === void 0 ? void 0 : _f.message}`); | ||
| return (opts, value) => { | ||
| if (value instanceof Uint8Array) { | ||
| return base64Encode(value); | ||
| } | ||
| throw errorScalarValue(field, value); | ||
| }; | ||
| } | ||
| } | ||
| function jsonName(f, opts) { | ||
| return opts.useProtoFieldName ? f.name : f.jsonName; | ||
| function errorScalarValue(field, value) { | ||
| var _a; | ||
| return new Error(`cannot encode ${field} to JSON: ${(_a = checkField(field, value)) === null || _a === void 0 ? void 0 : _a.message}`); | ||
| } | ||
| // returns a json value if wkt, otherwise returns undefined. | ||
| function tryWktToJson(msg, opts) { | ||
| if (!msg.desc.typeName.startsWith("google.protobuf.")) { | ||
| return undefined; | ||
| /** | ||
| * Write extensions for unknown fields that are found in the registry. | ||
| */ | ||
| function writeExtensions(json, opts, registry, message, desc) { | ||
| const unknown = message.$unknown; | ||
| if (unknown === undefined) { | ||
| return; | ||
| } | ||
| switch (msg.desc.typeName) { | ||
| case "google.protobuf.Any": | ||
| return anyToJson(msg.message, opts); | ||
| case "google.protobuf.Timestamp": | ||
| return timestampToJson(msg.message); | ||
| case "google.protobuf.Duration": | ||
| return durationToJson(msg.message); | ||
| case "google.protobuf.FieldMask": | ||
| return fieldMaskToJson(msg.message); | ||
| case "google.protobuf.Struct": | ||
| return structToJson(msg.message); | ||
| case "google.protobuf.Value": | ||
| return valueToJson(msg.message); | ||
| case "google.protobuf.ListValue": | ||
| return listValueToJson(msg.message); | ||
| default: | ||
| if (isWrapperDesc(msg.desc)) { | ||
| const valueField = msg.desc.fields[0]; | ||
| return scalarToJson(valueField, msg.get(valueField)); | ||
| const tagSeen = new Set(); | ||
| for (let i = 0; i < unknown.length; i++) { | ||
| const { no } = unknown[i]; | ||
| // Same tag can appear multiple times, so we | ||
| // keep track and skip identical ones. | ||
| if (!tagSeen.has(no)) { | ||
| tagSeen.add(no); | ||
| const extension = registry.getExtensionFor(desc, no); | ||
| if (!extension) { | ||
| continue; | ||
| } | ||
| return undefined; | ||
| const value = getExtension(message, extension); | ||
| const [container, field] = createExtensionContainer(extension, value); | ||
| const local = container[unsafeLocal]; | ||
| const jsonValue = compileFieldValue(field)(opts, local[field.localName]); | ||
| if (jsonValue !== undefined) { | ||
| json[extension.jsonName] = jsonValue; | ||
| } | ||
| } | ||
| } | ||
@@ -291,6 +523,7 @@ } | ||
| } | ||
| const reflected = reflect(desc, message); | ||
| const json = hasCustomJsonRepresentation(desc) | ||
| ? { value: tryWktToJson(reflected, opts) } | ||
| : reflectToJson(reflected, opts); | ||
| ? { | ||
| value: compiledWriter(desc)(opts, message), | ||
| } | ||
| : compiledWriter(desc)(opts, message); | ||
| json["@type"] = val.typeUrl; | ||
@@ -302,3 +535,3 @@ return json; | ||
| const nanos = val.nanos; | ||
| if (seconds > 315576000000 || seconds < -315576000000) { | ||
| if (seconds > durationSecondsMax || seconds < durationSecondsMin) { | ||
| throw new Error(`cannot encode message ${val.$typeName} to JSON: value out of range`); | ||
@@ -338,4 +571,6 @@ } | ||
| const json = {}; | ||
| for (const [k, v] of Object.entries(val.fields)) { | ||
| json[k] = valueToJson(v); | ||
| const keys = Object.keys(val.fields); | ||
| for (let i = 0; i < keys.length; i++) { | ||
| const key = keys[i]; | ||
| json[key] = valueToJson(val.fields[key]); | ||
| } | ||
@@ -370,4 +605,3 @@ return json; | ||
| const ms = Number(val.seconds) * 1000; | ||
| if (ms < Date.parse("0001-01-01T00:00:00Z") || | ||
| ms > Date.parse("9999-12-31T23:59:59Z")) { | ||
| if (ms < timestampMsMin || ms > timestampMsMax) { | ||
| throw new Error(`cannot encode message ${val.$typeName} to JSON: must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive`); | ||
@@ -374,0 +608,0 @@ } |
@@ -13,2 +13,3 @@ /** | ||
| export declare function base64Decode(base64Str: string): Uint8Array<ArrayBuffer>; | ||
| type Base64Encoding = "std" | "std_raw" | "url"; | ||
| /** | ||
@@ -24,2 +25,3 @@ * Encode a byte array to a base64 string. | ||
| */ | ||
| export declare function base64Encode(bytes: Uint8Array, encoding?: "std" | "std_raw" | "url"): string; | ||
| export declare function base64Encode(bytes: Uint8Array, encoding?: Base64Encoding): string; | ||
| export {}; |
@@ -14,2 +14,4 @@ // Copyright 2021-2026 Buf Technologies, Inc. | ||
| // limitations under the License. | ||
| // Native Uint8Array.prototype.setFromBase64, if the runtime provides it. | ||
| const nativeSetFromBase64 = Uint8Array.prototype.setFromBase64; | ||
| /** | ||
@@ -27,10 +29,31 @@ * Decodes a base64 string to a byte array. | ||
| export function base64Decode(base64Str) { | ||
| const len = base64Str.length; | ||
| // Decoded size, assuming a well-formed string: three bytes per group of | ||
| // four characters, minus one byte for each padding character. | ||
| let size = len - ((len + 3) >> 2); | ||
| if ((len & 3) == 0 && base64Str[len - 1] == "=") { | ||
| size -= base64Str[len - 2] == "=" ? 2 : 1; | ||
| } | ||
| const bytes = new Uint8Array(size); | ||
| let written = -1; | ||
| if (nativeSetFromBase64) { | ||
| try { | ||
| const result = nativeSetFromBase64.call(bytes, base64Str); | ||
| if (result.read == len) { | ||
| written = result.written; | ||
| } | ||
| } | ||
| catch (_a) { | ||
| // The native decoder rejects base64url and inner padding, which we accept. | ||
| } | ||
| } | ||
| if (written < 0) { | ||
| written = setFromBase64(bytes, base64Str); | ||
| } | ||
| return written == size ? bytes : bytes.subarray(0, written); | ||
| } | ||
| /** Writes into `bytes` from index 0 and returns the number of bytes written. */ | ||
| function setFromBase64(bytes, base64Str) { | ||
| const table = getDecodeTable(); | ||
| // estimate byte size, not accounting for inner padding and whitespace | ||
| let es = (base64Str.length * 3) / 4; | ||
| if (base64Str[base64Str.length - 2] == "=") | ||
| es -= 2; | ||
| else if (base64Str[base64Str.length - 1] == "=") | ||
| es -= 1; | ||
| let bytes = new Uint8Array(es), bytePos = 0, // position in byte array | ||
| let bytePos = 0, // position in byte array | ||
| groupPos = 0, // position in base64 group | ||
@@ -78,4 +101,10 @@ b, // current byte | ||
| throw Error("invalid base64 string"); | ||
| return bytes.subarray(0, bytePos); | ||
| return bytePos; | ||
| } | ||
| const nativeToBase64 = Uint8Array.prototype.toBase64; | ||
| const toBase64OptionsMap = { | ||
| std: { alphabet: "base64", omitPadding: false }, | ||
| std_raw: { alphabet: "base64", omitPadding: true }, | ||
| url: { alphabet: "base64url", omitPadding: true }, | ||
| }; | ||
| /** | ||
@@ -92,2 +121,5 @@ * Encode a byte array to a base64 string. | ||
| export function base64Encode(bytes, encoding = "std") { | ||
| if (nativeToBase64) { | ||
| return nativeToBase64.call(bytes, toBase64OptionsMap[encoding]); | ||
| } | ||
| const table = getEncodeTable(encoding); | ||
@@ -94,0 +126,0 @@ const pad = encoding == "std"; |
@@ -63,26 +63,34 @@ /** | ||
| export declare class BinaryWriter { | ||
| private readonly encodeUtf8; | ||
| /** | ||
| * We cannot allocate a buffer for the entire output | ||
| * because we don't know its size. | ||
| * | ||
| * So we collect smaller chunks of known size and | ||
| * concat them later. | ||
| * | ||
| * Use `raw()` to push data to this array. It will flush | ||
| * `buf` first. | ||
| * Growable byte buffer. We allocate a reasonably sized | ||
| * initial buffer and double its capacity when needed. | ||
| */ | ||
| private chunks; | ||
| private buffer; | ||
| /** | ||
| * A growing buffer for byte values. If you don't know | ||
| * the size of the data you are writing, push to this | ||
| * array. | ||
| * Cached DataView for fixed-width writes. Read it via `view()`, which | ||
| * rebuilds it if `buffer` has since grown. | ||
| */ | ||
| protected buf: number[]; | ||
| private viewCache; | ||
| /** | ||
| * Previous fork states. | ||
| * Current write position in the buffer. | ||
| */ | ||
| private stack; | ||
| private pos; | ||
| /** | ||
| * Previous fork positions (the write position at the time | ||
| * `fork()` was called). | ||
| */ | ||
| private stackPos; | ||
| /** | ||
| * UTF-8 codec used by `string()`. Uses the text encoding's `encodeUtf8Into`, | ||
| * or emulates it if a custom `encodeUtf8` was passed to the constructor. | ||
| */ | ||
| private readonly encodeUtf8Into; | ||
| constructor(encodeUtf8?: (text: string) => Uint8Array); | ||
| private ensureCapacity; | ||
| /** | ||
| * The DataView over `buffer`, rebuilt only if the buffer has grown since it | ||
| * was last used. | ||
| */ | ||
| private view; | ||
| /** | ||
| * Return all bytes written and reset this writer. | ||
@@ -175,2 +183,10 @@ */ | ||
| uint64(value: string | number | bigint): this; | ||
| /** | ||
| * Write a 64-bit varint directly into the buffer. Accepts the value as | ||
| * split low/high 32-bit words. | ||
| * | ||
| * Ported from varint64write() to avoid the intermediate number[] buffer. | ||
| * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/writer.js#L344 | ||
| */ | ||
| private writeVarint64; | ||
| } | ||
@@ -187,3 +203,3 @@ export declare class BinaryReader { | ||
| readonly len: number; | ||
| protected readonly buf: Uint8Array; | ||
| private readonly buf; | ||
| private readonly view; | ||
@@ -205,7 +221,9 @@ constructor(buf: Uint8Array, decodeUtf8?: (bytes: Uint8Array, strict?: boolean) => string); | ||
| skip(wireType: WireType, fieldNo?: number, recursionLimit?: number): Uint8Array; | ||
| protected varint64: () => [number, number]; | ||
| private varint64Lo; | ||
| private varint64Hi; | ||
| private varint64; | ||
| /** | ||
| * Throws error if position in byte array is out of range. | ||
| */ | ||
| protected assertBounds(): void; | ||
| private assertBounds; | ||
| /** | ||
@@ -212,0 +230,0 @@ * Read a `uint32` field, an unsigned 32 bit varint. |
@@ -14,5 +14,5 @@ // Copyright 2021-2026 Buf Technologies, Inc. | ||
| // limitations under the License. | ||
| import { varint32read, varint32write, varint64read, varint64write, } from "./varint.js"; | ||
| import { varint32read, varint64read } from "./varint.js"; | ||
| import { protoInt64 } from "../proto-int64.js"; | ||
| import { getTextEncoding } from "./text-encoding.js"; | ||
| import { emulateEncodeInto, getTextEncoding } from "./text-encoding.js"; | ||
| /** | ||
@@ -81,30 +81,51 @@ * Protobuf binary format wire types. | ||
| export class BinaryWriter { | ||
| constructor(encodeUtf8 = getTextEncoding().encodeUtf8) { | ||
| this.encodeUtf8 = encodeUtf8; | ||
| constructor(encodeUtf8) { | ||
| /** | ||
| * Previous fork states. | ||
| * Previous fork positions (the write position at the time | ||
| * `fork()` was called). | ||
| */ | ||
| this.stack = []; | ||
| this.chunks = []; | ||
| this.buf = []; | ||
| this.stackPos = []; | ||
| this.encodeUtf8Into = encodeUtf8 | ||
| ? emulateEncodeInto(encodeUtf8) | ||
| : getTextEncoding().encodeUtf8Into; | ||
| this.buffer = EMPTY_BUFFER; | ||
| this.viewCache = EMPTY_VIEW; | ||
| this.pos = 0; | ||
| } | ||
| ensureCapacity(size) { | ||
| const required = this.pos + size; | ||
| if (required > this.buffer.length) { | ||
| let newLen = this.buffer.length || INITIAL_SIZE; | ||
| while (newLen < required) | ||
| newLen *= 2; | ||
| const newBuf = new Uint8Array(newLen); | ||
| if (this.pos > 0) | ||
| newBuf.set(this.buffer); | ||
| this.buffer = newBuf; | ||
| } | ||
| } | ||
| /** | ||
| * The DataView over `buffer`, rebuilt only if the buffer has grown since it | ||
| * was last used. | ||
| */ | ||
| view() { | ||
| const bytes = this.buffer; | ||
| const view = this.viewCache; | ||
| // Since ensureCapacity() only ever replaces the buffer with a strictly larger one, | ||
| // equal lengths mean the view is still current. This is faster than comparing | ||
| // buffers directly. | ||
| if (view.byteLength === bytes.byteLength) | ||
| return view; | ||
| const newView = new DataView(bytes.buffer); | ||
| this.viewCache = newView; | ||
| return newView; | ||
| } | ||
| /** | ||
| * Return all bytes written and reset this writer. | ||
| */ | ||
| finish() { | ||
| if (this.buf.length) { | ||
| this.chunks.push(new Uint8Array(this.buf)); // flush the buffer | ||
| this.buf = []; | ||
| } | ||
| let len = 0; | ||
| for (let i = 0; i < this.chunks.length; i++) | ||
| len += this.chunks[i].length; | ||
| let bytes = new Uint8Array(len); | ||
| let offset = 0; | ||
| for (let i = 0; i < this.chunks.length; i++) { | ||
| bytes.set(this.chunks[i], offset); | ||
| offset += this.chunks[i].length; | ||
| } | ||
| this.chunks = []; | ||
| return bytes; | ||
| const result = this.buffer.slice(0, this.pos); | ||
| this.pos = 0; | ||
| this.stackPos = []; | ||
| return result; | ||
| } | ||
@@ -118,5 +139,7 @@ /** | ||
| fork() { | ||
| this.stack.push({ chunks: this.chunks, buf: this.buf }); | ||
| this.chunks = []; | ||
| this.buf = []; | ||
| this.stackPos.push(this.pos); | ||
| // Reserve room for the length prefix. Payloads under 128 bytes, fairly | ||
| // common, will need no copy in join(). | ||
| this.ensureCapacity(DEFAULT_LEN_PREFIX_SIZE); | ||
| this.buffer[this.pos++] = 0; | ||
| return this; | ||
@@ -129,13 +152,20 @@ } | ||
| join() { | ||
| // get chunk of fork | ||
| let chunk = this.finish(); | ||
| // restore previous state | ||
| let prev = this.stack.pop(); | ||
| if (!prev) | ||
| const forkPos = this.stackPos.pop(); | ||
| if (forkPos === undefined) | ||
| throw new Error("invalid state, fork stack empty"); | ||
| this.chunks = prev.chunks; | ||
| this.buf = prev.buf; | ||
| // write length of chunk as varint | ||
| this.uint32(chunk.byteLength); | ||
| return this.raw(chunk); | ||
| // fork() presumed the payload would fit the prefix it reserved. If it | ||
| // doesn't, we need to shift the bytes we just wrote. | ||
| const len = this.pos - forkPos - DEFAULT_LEN_PREFIX_SIZE; | ||
| const lenPrefixSize = varint32Size(len); | ||
| if (lenPrefixSize > DEFAULT_LEN_PREFIX_SIZE) { | ||
| // Widening pushes the payload past the end of the buffer, so grow first: | ||
| // copyWithin clamps to the buffer instead of throwing, so a short buffer | ||
| // would silently drop the tail of the payload. | ||
| this.ensureCapacity(lenPrefixSize - DEFAULT_LEN_PREFIX_SIZE); | ||
| this.buffer.copyWithin(forkPos + lenPrefixSize, forkPos + DEFAULT_LEN_PREFIX_SIZE, this.pos); | ||
| } | ||
| this.pos = forkPos; | ||
| this.uint32(len); | ||
| this.pos += len; | ||
| return this; | ||
| } | ||
@@ -156,7 +186,5 @@ /** | ||
| raw(chunk) { | ||
| if (this.buf.length) { | ||
| this.chunks.push(new Uint8Array(this.buf)); | ||
| this.buf = []; | ||
| } | ||
| this.chunks.push(chunk); | ||
| this.ensureCapacity(chunk.length); | ||
| this.buffer.set(chunk, this.pos); | ||
| this.pos += chunk.length; | ||
| return this; | ||
@@ -169,8 +197,14 @@ } | ||
| assertUInt32(value); | ||
| // write value as varint 32, inlined for speed | ||
| // uint32 varints are at most 5 bytes; reserve once and avoid per-byte | ||
| // capacity checks. | ||
| this.ensureCapacity(5); | ||
| if (value < 0x80) { | ||
| this.buffer[this.pos++] = value; | ||
| return this; | ||
| } | ||
| while (value > 0x7f) { | ||
| this.buf.push((value & 0x7f) | 0x80); | ||
| value = value >>> 7; | ||
| this.buffer[this.pos++] = (value & 0x7f) | 0x80; | ||
| value >>>= 7; | ||
| } | ||
| this.buf.push(value); | ||
| this.buffer[this.pos++] = value; | ||
| return this; | ||
@@ -183,3 +217,12 @@ } | ||
| assertInt32(value); | ||
| varint32write(value, this.buf); | ||
| if (value >= 0) { | ||
| return this.uint32(value); | ||
| } | ||
| // Negative: sign-extend to 64 bits, encodes to 10 bytes. | ||
| this.ensureCapacity(10); | ||
| for (let i = 0; i < 9; i++) { | ||
| this.buffer[this.pos++] = (value & 0x7f) | 0x80; | ||
| value >>= 7; | ||
| } | ||
| this.buffer[this.pos++] = 1; | ||
| return this; | ||
@@ -191,3 +234,4 @@ } | ||
| bool(value) { | ||
| this.buf.push(value ? 1 : 0); | ||
| this.ensureCapacity(1); | ||
| this.buffer[this.pos++] = value ? 1 : 0; | ||
| return this; | ||
@@ -199,3 +243,3 @@ } | ||
| bytes(value) { | ||
| this.uint32(value.byteLength); // write length of chunk as varint | ||
| this.uint32(value.byteLength); | ||
| return this.raw(value); | ||
@@ -207,5 +251,45 @@ } | ||
| string(value) { | ||
| let chunk = this.encodeUtf8(value); | ||
| this.uint32(chunk.byteLength); // write length of chunk as varint | ||
| return this.raw(chunk); | ||
| // TextEncoder.encode() coerces its argument to string, but encodeInto() | ||
| // rejects non-strings. | ||
| if (typeof value !== "string") { | ||
| value = String(value); | ||
| } | ||
| const len = value.length; | ||
| // Fast path for ASCII. | ||
| if (len <= ASCII_MAX_LENGTH) { | ||
| this.ensureCapacity(len + 1); | ||
| const ascii = this.buffer; | ||
| let pos = this.pos; | ||
| ascii[pos++] = len; | ||
| let i = 0; | ||
| for (; i < len; i++) { | ||
| const code = value.charCodeAt(i); | ||
| if (code > 0x7f) | ||
| break; | ||
| ascii[pos++] = code; | ||
| } | ||
| if (i == len) { | ||
| this.pos = pos; | ||
| return this; | ||
| } | ||
| } | ||
| // encodeUtf8Into needs the full-length buffer upfront. The length prefix | ||
| // can be upto 5 bytes, and a UTF-16 code unit takes at most 3 UTF-8 bytes. | ||
| this.ensureCapacity(len * 3 + 5); | ||
| // The length prefix goes first, but the byte length is only known after | ||
| // encoding. We guess the final varint size here (assuming most text is | ||
| // ASCII) and then encode. | ||
| const lenPrefixSizeGuess = varint32Size(len); | ||
| const buf = this.buffer; | ||
| const start = this.pos; | ||
| const { written } = this.encodeUtf8Into(value, buf.subarray(start + lenPrefixSizeGuess)); | ||
| // If our guess was incorrect, we need to shift the bytes we just wrote. | ||
| const lenPrefixSize = varint32Size(written); | ||
| if (lenPrefixSize != lenPrefixSizeGuess) { | ||
| buf.copyWithin(start + lenPrefixSize, start + lenPrefixSizeGuess, start + lenPrefixSizeGuess + written); | ||
| } | ||
| // Write the lenPrefix and advance the pos. | ||
| this.uint32(written); | ||
| this.pos += written; | ||
| return this; | ||
| } | ||
@@ -217,5 +301,6 @@ /** | ||
| assertFloat32(value); | ||
| let chunk = new Uint8Array(4); | ||
| new DataView(chunk.buffer).setFloat32(0, value, true); | ||
| return this.raw(chunk); | ||
| this.ensureCapacity(4); | ||
| this.view().setFloat32(this.pos, value, true); | ||
| this.pos += 4; | ||
| return this; | ||
| } | ||
@@ -226,5 +311,6 @@ /** | ||
| double(value) { | ||
| let chunk = new Uint8Array(8); | ||
| new DataView(chunk.buffer).setFloat64(0, value, true); | ||
| return this.raw(chunk); | ||
| this.ensureCapacity(8); | ||
| this.view().setFloat64(this.pos, value, true); | ||
| this.pos += 8; | ||
| return this; | ||
| } | ||
@@ -236,5 +322,6 @@ /** | ||
| assertUInt32(value); | ||
| let chunk = new Uint8Array(4); | ||
| new DataView(chunk.buffer).setUint32(0, value, true); | ||
| return this.raw(chunk); | ||
| this.ensureCapacity(4); | ||
| this.view().setUint32(this.pos, value, true); | ||
| this.pos += 4; | ||
| return this; | ||
| } | ||
@@ -246,5 +333,6 @@ /** | ||
| assertInt32(value); | ||
| let chunk = new Uint8Array(4); | ||
| new DataView(chunk.buffer).setInt32(0, value, true); | ||
| return this.raw(chunk); | ||
| this.ensureCapacity(4); | ||
| this.view().setInt32(this.pos, value, true); | ||
| this.pos += 4; | ||
| return this; | ||
| } | ||
@@ -256,6 +344,4 @@ /** | ||
| assertInt32(value); | ||
| // zigzag encode | ||
| value = ((value << 1) ^ (value >> 31)) >>> 0; | ||
| varint32write(value, this.buf); | ||
| return this; | ||
| // zigzag encode then emit as uint32 varint | ||
| return this.uint32(((value << 1) ^ (value >> 31)) >>> 0); | ||
| } | ||
@@ -266,6 +352,9 @@ /** | ||
| sfixed64(value) { | ||
| let chunk = new Uint8Array(8), view = new DataView(chunk.buffer), tc = protoInt64.enc(value); | ||
| view.setInt32(0, tc.lo, true); | ||
| view.setInt32(4, tc.hi, true); | ||
| return this.raw(chunk); | ||
| const tc = protoInt64.enc(value); | ||
| this.ensureCapacity(8); | ||
| const view = this.view(); | ||
| view.setInt32(this.pos, tc.lo, true); | ||
| view.setInt32(this.pos + 4, tc.hi, true); | ||
| this.pos += 8; | ||
| return this; | ||
| } | ||
@@ -276,6 +365,9 @@ /** | ||
| fixed64(value) { | ||
| let chunk = new Uint8Array(8), view = new DataView(chunk.buffer), tc = protoInt64.uEnc(value); | ||
| view.setInt32(0, tc.lo, true); | ||
| view.setInt32(4, tc.hi, true); | ||
| return this.raw(chunk); | ||
| const tc = protoInt64.uEnc(value); | ||
| this.ensureCapacity(8); | ||
| const view = this.view(); | ||
| view.setInt32(this.pos, tc.lo, true); | ||
| view.setInt32(this.pos + 4, tc.hi, true); | ||
| this.pos += 8; | ||
| return this; | ||
| } | ||
@@ -286,5 +378,4 @@ /** | ||
| int64(value) { | ||
| let tc = protoInt64.enc(value); | ||
| varint64write(tc.lo, tc.hi, this.buf); | ||
| return this; | ||
| const tc = protoInt64.enc(value); | ||
| return this.writeVarint64(tc.lo, tc.hi); | ||
| } | ||
@@ -298,4 +389,3 @@ /** | ||
| sign = tc.hi >> 31, lo = (tc.lo << 1) ^ sign, hi = ((tc.hi << 1) | (tc.lo >>> 31)) ^ sign; | ||
| varint64write(lo, hi, this.buf); | ||
| return this; | ||
| return this.writeVarint64(lo, hi); | ||
| } | ||
@@ -307,9 +397,91 @@ /** | ||
| const tc = protoInt64.uEnc(value); | ||
| varint64write(tc.lo, tc.hi, this.buf); | ||
| return this.writeVarint64(tc.lo, tc.hi); | ||
| } | ||
| /** | ||
| * Write a 64-bit varint directly into the buffer. Accepts the value as | ||
| * split low/high 32-bit words. | ||
| * | ||
| * Ported from varint64write() to avoid the intermediate number[] buffer. | ||
| * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/writer.js#L344 | ||
| */ | ||
| writeVarint64(lo, hi) { | ||
| // Worst case: 10 bytes. | ||
| this.ensureCapacity(10); | ||
| const buf = this.buffer; | ||
| let pos = this.pos; | ||
| for (let i = 0; i < 28; i = i + 7) { | ||
| const shift = lo >>> i; | ||
| const hasNext = !(shift >>> 7 == 0 && hi == 0); | ||
| buf[pos++] = (hasNext ? shift | 0x80 : shift) & 0xff; | ||
| if (!hasNext) { | ||
| this.pos = pos; | ||
| return this; | ||
| } | ||
| } | ||
| const splitBits = ((lo >>> 28) & 0x0f) | ((hi & 0x07) << 4); | ||
| const hasMoreBits = !(hi >> 3 == 0); | ||
| buf[pos++] = (hasMoreBits ? splitBits | 0x80 : splitBits) & 0xff; | ||
| if (!hasMoreBits) { | ||
| this.pos = pos; | ||
| return this; | ||
| } | ||
| for (let i = 3; i < 31; i = i + 7) { | ||
| const shift = hi >>> i; | ||
| const hasNext = !(shift >>> 7 == 0); | ||
| buf[pos++] = (hasNext ? shift | 0x80 : shift) & 0xff; | ||
| if (!hasNext) { | ||
| this.pos = pos; | ||
| return this; | ||
| } | ||
| } | ||
| buf[pos++] = (hi >>> 31) & 0x01; | ||
| this.pos = pos; | ||
| return this; | ||
| } | ||
| } | ||
| /** | ||
| * Capacity of the buffer allocated by the first write.. | ||
| */ | ||
| const INITIAL_SIZE = 128; | ||
| /** | ||
| * Bytes `fork()` reserves for the length prefix, betting that the payload will | ||
| * be under 128 bytes. `join()` fills them in, and widens them if the bet was | ||
| * wrong. | ||
| */ | ||
| const DEFAULT_LEN_PREFIX_SIZE = 1; | ||
| /** | ||
| * Shared empty buffer used as the initial value before the first write. | ||
| * Avoids allocating and zeroing `INITIAL_SIZE` bytes per BinaryWriter when a | ||
| * writer is only used for a tiny message (or not used at all). | ||
| */ | ||
| const EMPTY_BUFFER = new Uint8Array(0); | ||
| /** | ||
| * Shared empty view, paired with `EMPTY_BUFFER`. Never written to: any | ||
| * fixed-width write first grows the buffer, which replaces this view. | ||
| */ | ||
| const EMPTY_VIEW = new DataView(EMPTY_BUFFER.buffer); | ||
| /** | ||
| * Longest string on the ASCII fast paths. Must stay below 0x80, so | ||
| * that the writer's length prefix always fits a single varint byte. | ||
| */ | ||
| const ASCII_MAX_LENGTH = 32; | ||
| /** | ||
| * Number of bytes needed to encode `value` as an unsigned 32-bit varint. | ||
| */ | ||
| function varint32Size(value) { | ||
| if (value < 0x80) | ||
| return 1; | ||
| if (value < 0x4000) | ||
| return 2; | ||
| if (value < 0x200000) | ||
| return 3; | ||
| if (value < 0x10000000) | ||
| return 4; | ||
| return 5; | ||
| } | ||
| export class BinaryReader { | ||
| constructor(buf, decodeUtf8 = getTextEncoding().decodeUtf8) { | ||
| this.decodeUtf8 = decodeUtf8; | ||
| this.varint64Lo = 0; | ||
| this.varint64Hi = 0; | ||
| this.varint64 = varint64read; // dirty cast for `this` | ||
@@ -415,3 +587,4 @@ /** | ||
| int64() { | ||
| return protoInt64.dec(...this.varint64()); | ||
| this.varint64(); | ||
| return protoInt64.dec(this.varint64Lo, this.varint64Hi); | ||
| } | ||
@@ -422,3 +595,4 @@ /** | ||
| uint64() { | ||
| return protoInt64.uDec(...this.varint64()); | ||
| this.varint64(); | ||
| return protoInt64.uDec(this.varint64Lo, this.varint64Hi); | ||
| } | ||
@@ -429,3 +603,5 @@ /** | ||
| sint64() { | ||
| let [lo, hi] = this.varint64(); | ||
| this.varint64(); | ||
| let lo = this.varint64Lo; | ||
| let hi = this.varint64Hi; | ||
| // decode zig zag | ||
@@ -441,4 +617,10 @@ let s = -(lo & 1); | ||
| bool() { | ||
| let [lo, hi] = this.varint64(); | ||
| return lo !== 0 || hi !== 0; | ||
| // Fast path: most bools are 0x0 or 0x1. | ||
| const b = this.buf[this.pos]; | ||
| if (b < 0x80) { | ||
| this.pos++; | ||
| return b !== 0; | ||
| } | ||
| this.varint64(); | ||
| return this.varint64Lo !== 0 || this.varint64Hi !== 0; | ||
| } | ||
@@ -499,3 +681,17 @@ /** | ||
| string(strict) { | ||
| return this.decodeUtf8(this.bytes(), strict); | ||
| const bytes = this.bytes(); | ||
| const len = bytes.length; | ||
| // Fast path for ASCII. | ||
| if (len <= ASCII_MAX_LENGTH) { | ||
| const codes = new Array(len); | ||
| for (let i = 0; i < len; i++) { | ||
| const byte = bytes[i]; | ||
| if (byte > 0x7f) { | ||
| return this.decodeUtf8(bytes, strict); | ||
| } | ||
| codes[i] = byte; | ||
| } | ||
| return String.fromCharCode.apply(String, codes); | ||
| } | ||
| return this.decodeUtf8(bytes, strict); | ||
| } | ||
@@ -502,0 +698,0 @@ } |
| export * from "./binary-encoding.js"; | ||
| export * from "./base64-encoding.js"; | ||
| export * from "./text-encoding.js"; | ||
| export { getTextEncoding, configureTextEncoding } from "./text-encoding.js"; | ||
| export * from "./text-format.js"; | ||
| export * from "./size-delimited.js"; |
@@ -16,4 +16,4 @@ // Copyright 2021-2026 Buf Technologies, Inc. | ||
| export * from "./base64-encoding.js"; | ||
| export * from "./text-encoding.js"; | ||
| export { getTextEncoding, configureTextEncoding } from "./text-encoding.js"; | ||
| export * from "./text-format.js"; | ||
| export * from "./size-delimited.js"; |
@@ -11,2 +11,8 @@ interface TextEncoding { | ||
| /** | ||
| * Encode UTF-8 text to a Uint8Array. The destination must be large enough. | ||
| */ | ||
| encodeUtf8Into: (text: string, dest: Uint8Array) => { | ||
| written: number; | ||
| }; | ||
| /** | ||
| * Decode UTF-8 text from binary. If `strict` is true, throw on invalid byte | ||
@@ -18,2 +24,3 @@ * sequences instead of silently substituting U+FFFD. Implementations that | ||
| } | ||
| type TextEncodingConfig = Omit<TextEncoding, "encodeUtf8Into"> & Partial<Pick<TextEncoding, "encodeUtf8Into">>; | ||
| /** | ||
@@ -25,7 +32,17 @@ * Protobuf-ES requires the Text Encoding API to convert UTF-8 from and to | ||
| * | ||
| * Providing `encodeUtf8Into` is optional for backwards compatibility. If it | ||
| * is omitted, we emulate it with a wrapper that calls `encodeUtf8`. | ||
| * | ||
| * Note that the Text Encoding API does not provide a way to validate UTF-8. | ||
| * Our implementation falls back to use encodeURIComponent(). | ||
| * Our implementation uses String.prototype.isWellFormed, and falls back | ||
| * to use encodeURIComponent(). | ||
| */ | ||
| export declare function configureTextEncoding(textEncoding: TextEncoding): void; | ||
| export declare function configureTextEncoding(textEncoding: TextEncodingConfig): void; | ||
| export declare function getTextEncoding(): TextEncoding; | ||
| /** | ||
| * Simplistic polyfill for encodeUtf8Into. | ||
| * | ||
| * @private | ||
| */ | ||
| export declare function emulateEncodeInto(encodeUtf8: (str: string) => Uint8Array): TextEncoding["encodeUtf8Into"]; | ||
| export {}; |
@@ -21,25 +21,33 @@ // Copyright 2021-2026 Buf Technologies, Inc. | ||
| * | ||
| * Providing `encodeUtf8Into` is optional for backwards compatibility. If it | ||
| * is omitted, we emulate it with a wrapper that calls `encodeUtf8`. | ||
| * | ||
| * Note that the Text Encoding API does not provide a way to validate UTF-8. | ||
| * Our implementation falls back to use encodeURIComponent(). | ||
| * Our implementation uses String.prototype.isWellFormed, and falls back | ||
| * to use encodeURIComponent(). | ||
| */ | ||
| export function configureTextEncoding(textEncoding) { | ||
| globalThis[symbol] = textEncoding; | ||
| var _a; | ||
| globalThis[symbol] = Object.assign(Object.assign({}, textEncoding), { encodeUtf8Into: (_a = textEncoding.encodeUtf8Into) !== null && _a !== void 0 ? _a : emulateEncodeInto(textEncoding.encodeUtf8.bind(textEncoding)) }); | ||
| } | ||
| export function getTextEncoding() { | ||
| if (globalThis[symbol] == undefined) { | ||
| const te = new globalThis.TextEncoder(); | ||
| const td = new globalThis.TextDecoder(); | ||
| let tdStrict; | ||
| globalThis[symbol] = { | ||
| const globals = globalThis; | ||
| if (!globals[symbol]) { | ||
| const textEncoder = new globals.TextEncoder(); | ||
| const textDecoder = new globals.TextDecoder(); | ||
| let textDecoderStrict; | ||
| const config = { | ||
| encodeUtf8(text) { | ||
| return te.encode(text); | ||
| return textEncoder.encode(text); | ||
| }, | ||
| decodeUtf8(bytes, strict) { | ||
| if (strict) { | ||
| if (tdStrict === undefined) { | ||
| tdStrict = new globalThis.TextDecoder("utf-8", { fatal: true }); | ||
| if (!textDecoderStrict) { | ||
| textDecoderStrict = new globals.TextDecoder("utf-8", { | ||
| fatal: true, | ||
| }); | ||
| } | ||
| return tdStrict.decode(bytes); | ||
| return textDecoderStrict.decode(bytes); | ||
| } | ||
| return td.decode(bytes); | ||
| return textDecoder.decode(bytes); | ||
| }, | ||
@@ -56,4 +64,29 @@ checkUtf8(text) { | ||
| }; | ||
| // If encodeInto is available, use it. Otherwise, configureTextEncoding | ||
| // fills in a slower fallback that uses encodeUtf8. | ||
| if (textEncoder.encodeInto) { | ||
| config.encodeUtf8Into = textEncoder.encodeInto.bind(textEncoder); | ||
| } | ||
| // Native String.prototype.isWellFormed, if the runtime provides it. | ||
| const nativeStringIsWellFormed = String.prototype.isWellFormed; | ||
| if (nativeStringIsWellFormed) { | ||
| config.checkUtf8 = (text) => { | ||
| return nativeStringIsWellFormed.call(text); | ||
| }; | ||
| } | ||
| configureTextEncoding(config); | ||
| } | ||
| return globalThis[symbol]; | ||
| return globals[symbol]; | ||
| } | ||
| /** | ||
| * Simplistic polyfill for encodeUtf8Into. | ||
| * | ||
| * @private | ||
| */ | ||
| export function emulateEncodeInto(encodeUtf8) { | ||
| return (text, dest) => { | ||
| const bytes = encodeUtf8(text); | ||
| dest.set(bytes); | ||
| return { written: bytes.byteLength }; | ||
| }; | ||
| } |
| /** | ||
| * Read a 64 bit varint as two JS numbers. | ||
| * | ||
| * Returns tuple: | ||
| * [0]: low bits | ||
| * [1]: high bits | ||
| * Stores the low and high words on the reader. | ||
| * | ||
@@ -12,3 +10,3 @@ * Copyright 2008 Google Inc. All rights reserved. | ||
| */ | ||
| export declare function varint64read<T extends ReaderLike>(this: T): [number, number]; | ||
| export declare function varint64read<T extends ReaderLike>(this: T): void; | ||
| /** | ||
@@ -69,4 +67,6 @@ * Write a 64 bit varint, given as two JS numbers, to the given bytes array. | ||
| len: number; | ||
| varint64Lo: number; | ||
| varint64Hi: number; | ||
| assertBounds(): void; | ||
| }; | ||
| export {}; |
+35
-23
@@ -36,5 +36,3 @@ // Copyright 2008 Google Inc. All rights reserved. | ||
| * | ||
| * Returns tuple: | ||
| * [0]: low bits | ||
| * [1]: high bits | ||
| * Stores the low and high words on the reader. | ||
| * | ||
@@ -46,27 +44,38 @@ * Copyright 2008 Google Inc. All rights reserved. | ||
| export function varint64read() { | ||
| let lowBits = 0; | ||
| let highBits = 0; | ||
| const buf = this.buf; | ||
| let pos = this.pos; | ||
| let lo = 0; | ||
| let hi = 0; | ||
| for (let shift = 0; shift < 28; shift += 7) { | ||
| let b = this.buf[this.pos++]; | ||
| lowBits |= (b & 0x7f) << shift; | ||
| const b = buf[pos++]; | ||
| lo |= (b & 0x7f) << shift; | ||
| if ((b & 0x80) == 0) { | ||
| this.pos = pos; | ||
| this.assertBounds(); | ||
| return [lowBits, highBits]; | ||
| this.varint64Lo = lo; | ||
| this.varint64Hi = hi; | ||
| return; | ||
| } | ||
| } | ||
| let middleByte = this.buf[this.pos++]; | ||
| const middleByte = buf[pos++]; | ||
| // last four bits of the first 32 bit number | ||
| lowBits |= (middleByte & 0x0f) << 28; | ||
| lo |= (middleByte & 0x0f) << 28; | ||
| // 3 upper bits are part of the next 32 bit number | ||
| highBits = (middleByte & 0x70) >> 4; | ||
| hi = (middleByte & 0x70) >> 4; | ||
| if ((middleByte & 0x80) == 0) { | ||
| this.pos = pos; | ||
| this.assertBounds(); | ||
| return [lowBits, highBits]; | ||
| this.varint64Lo = lo; | ||
| this.varint64Hi = hi; | ||
| return; | ||
| } | ||
| for (let shift = 3; shift <= 31; shift += 7) { | ||
| let b = this.buf[this.pos++]; | ||
| highBits |= (b & 0x7f) << shift; | ||
| const b = buf[pos++]; | ||
| hi |= (b & 0x7f) << shift; | ||
| if ((b & 0x80) == 0) { | ||
| this.pos = pos; | ||
| this.assertBounds(); | ||
| return [lowBits, highBits]; | ||
| this.varint64Lo = lo; | ||
| this.varint64Hi = hi; | ||
| return; | ||
| } | ||
@@ -259,2 +268,6 @@ } | ||
| export function varint32write(value, bytes) { | ||
| if (value >>> 0 < 0x80) { | ||
| bytes.push(value); | ||
| return; | ||
| } | ||
| if (value >= 0) { | ||
@@ -283,10 +296,10 @@ // write value as varint 32 | ||
| let b = this.buf[this.pos++]; | ||
| let result = b & 0x7f; | ||
| if ((b & 0x80) == 0) { | ||
| if ((b & 0x80) === 0) { | ||
| this.assertBounds(); | ||
| return result; | ||
| return b; | ||
| } | ||
| let result = b & 0x7f; | ||
| b = this.buf[this.pos++]; | ||
| result |= (b & 0x7f) << 7; | ||
| if ((b & 0x80) == 0) { | ||
| if ((b & 0x80) === 0) { | ||
| this.assertBounds(); | ||
@@ -297,3 +310,3 @@ return result; | ||
| result |= (b & 0x7f) << 14; | ||
| if ((b & 0x80) == 0) { | ||
| if ((b & 0x80) === 0) { | ||
| this.assertBounds(); | ||
@@ -304,3 +317,3 @@ return result; | ||
| result |= (b & 0x7f) << 21; | ||
| if ((b & 0x80) == 0) { | ||
| if ((b & 0x80) === 0) { | ||
| this.assertBounds(); | ||
@@ -314,7 +327,6 @@ return result; | ||
| b = this.buf[this.pos++]; | ||
| if ((b & 0x80) != 0) | ||
| if ((b & 0x80) !== 0) | ||
| throw new Error("invalid varint"); | ||
| this.assertBounds(); | ||
| // Result can have 32 bits, convert it to unsigned | ||
| return result >>> 0; | ||
| } |
+12
-12
@@ -48,15 +48,15 @@ // Copyright 2021-2026 Buf Technologies, Inc. | ||
| } | ||
| const wrapperTypeNames = /*@__PURE__*/ new Set([ | ||
| "google.protobuf.DoubleValue", | ||
| "google.protobuf.FloatValue", | ||
| "google.protobuf.Int64Value", | ||
| "google.protobuf.UInt64Value", | ||
| "google.protobuf.Int32Value", | ||
| "google.protobuf.UInt32Value", | ||
| "google.protobuf.BoolValue", | ||
| "google.protobuf.StringValue", | ||
| "google.protobuf.BytesValue", | ||
| ]); | ||
| function isWrapperTypeName(name) { | ||
| return (name.startsWith("google.protobuf.") && | ||
| [ | ||
| "DoubleValue", | ||
| "FloatValue", | ||
| "Int64Value", | ||
| "UInt64Value", | ||
| "Int32Value", | ||
| "UInt32Value", | ||
| "BoolValue", | ||
| "StringValue", | ||
| "BytesValue", | ||
| ].includes(name.substring(16))); | ||
| return wrapperTypeNames.has(name); | ||
| } |
+2
-2
| { | ||
| "name": "@bufbuild/protobuf", | ||
| "version": "2.13.0", | ||
| "version": "2.14.0", | ||
| "license": "(Apache-2.0 AND BSD-3-Clause)", | ||
| "description": "Protocol Buffers for ECMAScript. The only JavaScript Protobuf library that is fully-compliant with Protobuf conformance tests.", | ||
| "description": "Protocol Buffers for ECMAScript. Fully compliant with the Protobuf conformance tests.", | ||
| "keywords": [ | ||
@@ -7,0 +7,0 @@ "protobuf", |
+10
-13
@@ -11,17 +11,14 @@ # @bufbuild/protobuf | ||
| **Protobuf-ES** is a solid, modern alternative to existing Protobuf implementations for the JavaScript ecosystem. It's | ||
| the first project in this space to provide a comprehensive plugin framework and decouple the base types from RPC | ||
| functionality. | ||
| **Protobuf-ES** is a solid, modern alternative to existing Protobuf implementations for the JavaScript ecosystem. It | ||
| provides a comprehensive plugin framework and decouples the base types from RPC functionality. | ||
| Some additional features that set it apart from the others: | ||
| Some additional features: | ||
| - ECMAScript module support | ||
| - First-class TypeScript support | ||
| - Generation of idiomatic JavaScript and TypeScript code | ||
| - Generation of [much smaller bundles](https://github.com/bufbuild/protobuf-es/tree/main/packages/bundle-size/) | ||
| - Implementation of all proto3 features, including the [canonical JSON format](https://protobuf.dev/programming-guides/proto3/#json) | ||
| - Implementation of all proto2 features, including extensions and the text format | ||
| - Usage of standard JavaScript APIs instead of the [Closure Library](http://googlecode.blogspot.com/2009/11/introducing-closure-tools.html) | ||
| - Compatibility is covered by the Protocol Buffers [conformance tests](https://github.com/bufbuild/protobuf-es/tree/main/packages/protobuf-conformance/) | ||
| - Descriptor and reflection support | ||
| - Generates pure TypeScript | ||
| - Plain message objects, no getters/setters | ||
| - Reflection, registries, and custom options | ||
| - 100% conformant against the official Protobuf test suite | ||
| - Standard plugin-based generation, works with the Buf CLI as well as `protoc` | ||
| - Write your own code generators with [@bufbuild/protoplugin](https://www.npmjs.com/package/@bufbuild/protoplugin) | ||
| - Pairs with [@connectrpc/connect](https://www.npmjs.com/package/@connectrpc/connect) for RPC and [@bufbuild/protovalidate](https://www.npmjs.com/package/@bufbuild/protovalidate) for validation | ||
@@ -28,0 +25,0 @@ ## Installation |
1917778
6.85%344
2.38%49854
6.61%42
-6.67%