You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

906 lines
32 KiB

// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { ScalarType, } from "./descriptors.js";
import { parseTextFormatEnumValue, parseTextFormatScalarValue, } from "./wire/text-format.js";
import { nestedTypes } from "./reflect/nested-types.js";
import { unsafeIsSetExplicit } from "./reflect/unsafe.js";
import { protoCamelCase, safeObjectProperty } from "./reflect/names.js";
/**
* Create a registry from the given inputs.
*
* An input can be:
* - Any message, enum, service, or extension descriptor, which adds just the
* descriptor for this type.
* - A file descriptor, which adds all typed defined in this file.
* - A registry, which adds all types from the registry.
*
* For duplicate descriptors (same type name), the one given last wins.
*/
export function createRegistry(...input) {
return initBaseRegistry(input);
}
/**
* Create a registry that allows adding and removing descriptors.
*/
export function createMutableRegistry(...input) {
const reg = initBaseRegistry(input);
return Object.assign(Object.assign({}, reg), { remove(desc) {
var _a;
if (desc.kind == "extension") {
(_a = reg.extendees.get(desc.extendee.typeName)) === null || _a === void 0 ? void 0 : _a.delete(desc.number);
}
reg.types.delete(desc.typeName);
} });
}
export function createFileRegistry(...args) {
const registry = createBaseRegistry();
if (!args.length) {
return registry;
}
if ("$typeName" in args[0] &&
args[0].$typeName == "google.protobuf.FileDescriptorSet") {
for (const file of args[0].file) {
addFile(file, registry);
}
return registry;
}
if ("$typeName" in args[0]) {
const input = args[0];
const resolve = args[1];
const seen = new Set();
// eslint-disable-next-line no-inner-declarations
function recurseDeps(file) {
const deps = [];
for (const protoFileName of file.dependency) {
if (registry.getFile(protoFileName) != undefined) {
continue;
}
if (seen.has(protoFileName)) {
continue;
}
const dep = resolve(protoFileName);
if (!dep) {
throw new Error(`Unable to resolve ${protoFileName}, imported by ${file.name}`);
}
if ("kind" in dep) {
registry.addFile(dep, false, true);
}
else {
seen.add(dep.name);
deps.push(dep);
}
}
return deps.concat(...deps.map(recurseDeps));
}
for (const file of [input, ...recurseDeps(input)].reverse()) {
addFile(file, registry);
}
}
else {
for (const fileReg of args) {
for (const file of fileReg.files) {
registry.addFile(file);
}
}
}
return registry;
}
/**
* @private
*/
function createBaseRegistry() {
const types = new Map();
const extendees = new Map();
const files = new Map();
return {
kind: "registry",
types,
extendees,
[Symbol.iterator]() {
return types.values();
},
get files() {
return files.values();
},
addFile(file, skipTypes, withDeps) {
files.set(file.proto.name, file);
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (!skipTypes) {
for (const type of nestedTypes(file)) {
this.add(type);
}
}
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (withDeps) {
for (const f of file.dependencies) {
this.addFile(f, skipTypes, withDeps);
}
}
},
add(desc) {
if (desc.kind == "extension") {
let numberToExt = extendees.get(desc.extendee.typeName);
if (!numberToExt) {
extendees.set(desc.extendee.typeName, (numberToExt = new Map()));
}
numberToExt.set(desc.number, desc);
}
types.set(desc.typeName, desc);
},
get(typeName) {
return types.get(typeName);
},
getFile(fileName) {
return files.get(fileName);
},
getMessage(typeName) {
const t = types.get(typeName);
return (t === null || t === void 0 ? void 0 : t.kind) == "message" ? t : undefined;
},
getEnum(typeName) {
const t = types.get(typeName);
return (t === null || t === void 0 ? void 0 : t.kind) == "enum" ? t : undefined;
},
getExtension(typeName) {
const t = types.get(typeName);
return (t === null || t === void 0 ? void 0 : t.kind) == "extension" ? t : undefined;
},
getExtensionFor(extendee, no) {
var _a;
return (_a = extendees.get(extendee.typeName)) === null || _a === void 0 ? void 0 : _a.get(no);
},
getService(typeName) {
const t = types.get(typeName);
return (t === null || t === void 0 ? void 0 : t.kind) == "service" ? t : undefined;
},
};
}
/**
* @private
*/
function initBaseRegistry(inputs) {
const registry = createBaseRegistry();
for (const input of inputs) {
switch (input.kind) {
case "registry":
for (const n of input) {
registry.add(n);
}
break;
case "file":
registry.addFile(input);
break;
default:
registry.add(input);
break;
}
}
return registry;
}
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO2: const $name: Edition.$localName = $number;
const EDITION_PROTO2 = 998;
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO3: const $name: Edition.$localName = $number;
const EDITION_PROTO3 = 999;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_STRING: const $name: FieldDescriptorProto_Type.$localName = $number;
const TYPE_STRING = 9;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_GROUP: const $name: FieldDescriptorProto_Type.$localName = $number;
const TYPE_GROUP = 10;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_MESSAGE: const $name: FieldDescriptorProto_Type.$localName = $number;
const TYPE_MESSAGE = 11;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_BYTES: const $name: FieldDescriptorProto_Type.$localName = $number;
const TYPE_BYTES = 12;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_ENUM: const $name: FieldDescriptorProto_Type.$localName = $number;
const TYPE_ENUM = 14;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Label.LABEL_REPEATED: const $name: FieldDescriptorProto_Label.$localName = $number;
const LABEL_REPEATED = 3;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Label.LABEL_REQUIRED: const $name: FieldDescriptorProto_Label.$localName = $number;
const LABEL_REQUIRED = 2;
// bootstrap-inject google.protobuf.FieldOptions.JSType.JS_STRING: const $name: FieldOptions_JSType.$localName = $number;
const JS_STRING = 1;
// bootstrap-inject google.protobuf.MethodOptions.IdempotencyLevel.IDEMPOTENCY_UNKNOWN: const $name: MethodOptions_IdempotencyLevel.$localName = $number;
const IDEMPOTENCY_UNKNOWN = 0;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.EXPLICIT: const $name: FeatureSet_FieldPresence.$localName = $number;
const EXPLICIT = 1;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name: FeatureSet_FieldPresence.$localName = $number;
const IMPLICIT = 2;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.LEGACY_REQUIRED: const $name: FeatureSet_FieldPresence.$localName = $number;
const LEGACY_REQUIRED = 3;
// bootstrap-inject google.protobuf.FeatureSet.RepeatedFieldEncoding.PACKED: const $name: FeatureSet_RepeatedFieldEncoding.$localName = $number;
const PACKED = 1;
// bootstrap-inject google.protobuf.FeatureSet.MessageEncoding.DELIMITED: const $name: FeatureSet_MessageEncoding.$localName = $number;
const DELIMITED = 2;
// bootstrap-inject google.protobuf.FeatureSet.EnumType.OPEN: const $name: FeatureSet_EnumType.$localName = $number;
const OPEN = 1;
// prettier-ignore
// bootstrap-inject defaults: EDITION_PROTO2 to EDITION_2023: export const minimumEdition: SupportedEdition = $minimumEdition, maximumEdition: SupportedEdition = $maximumEdition;
// generated from protoc v28.0
export const minimumEdition = 998, maximumEdition = 1000;
const featureDefaults = {
// EDITION_PROTO2
998: {
fieldPresence: 1, // EXPLICIT,
enumType: 2, // CLOSED,
repeatedFieldEncoding: 2, // EXPANDED,
utf8Validation: 3, // NONE,
messageEncoding: 1, // LENGTH_PREFIXED,
jsonFormat: 2, // LEGACY_BEST_EFFORT,
},
// EDITION_PROTO3
999: {
fieldPresence: 2, // IMPLICIT,
enumType: 1, // OPEN,
repeatedFieldEncoding: 1, // PACKED,
utf8Validation: 2, // VERIFY,
messageEncoding: 1, // LENGTH_PREFIXED,
jsonFormat: 1, // ALLOW,
},
// EDITION_2023
1000: {
fieldPresence: 1, // EXPLICIT,
enumType: 1, // OPEN,
repeatedFieldEncoding: 1, // PACKED,
utf8Validation: 2, // VERIFY,
messageEncoding: 1, // LENGTH_PREFIXED,
jsonFormat: 1, // ALLOW,
},
};
/**
* Create a descriptor for a file, add it to the registry.
*/
function addFile(proto, reg) {
var _a, _b;
const file = {
kind: "file",
proto,
deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false,
edition: getFileEdition(proto),
name: proto.name.replace(/\.proto$/, ""),
dependencies: findFileDependencies(proto, reg),
enums: [],
messages: [],
extensions: [],
services: [],
toString() {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- we asserted above
return `file ${proto.name}`;
},
};
const mapEntriesStore = new Map();
const mapEntries = {
get(typeName) {
return mapEntriesStore.get(typeName);
},
add(desc) {
var _a;
assert(((_a = desc.proto.options) === null || _a === void 0 ? void 0 : _a.mapEntry) === true);
mapEntriesStore.set(desc.typeName, desc);
},
};
for (const enumProto of proto.enumType) {
addEnum(enumProto, file, undefined, reg);
}
for (const messageProto of proto.messageType) {
addMessage(messageProto, file, undefined, reg, mapEntries);
}
for (const serviceProto of proto.service) {
addService(serviceProto, file, reg);
}
addExtensions(file, reg);
for (const mapEntry of mapEntriesStore.values()) {
// to create a map field, we need access to the map entry's fields
addFields(mapEntry, reg, mapEntries);
}
for (const message of file.messages) {
addFields(message, reg, mapEntries);
addExtensions(message, reg);
}
reg.addFile(file, true);
}
/**
* Create descriptors for extensions, and add them to the message / file,
* and to our cart.
* Recurses into nested types.
*/
function addExtensions(desc, reg) {
switch (desc.kind) {
case "file":
for (const proto of desc.proto.extension) {
const ext = newField(proto, desc, reg);
desc.extensions.push(ext);
reg.add(ext);
}
break;
case "message":
for (const proto of desc.proto.extension) {
const ext = newField(proto, desc, reg);
desc.nestedExtensions.push(ext);
reg.add(ext);
}
for (const message of desc.nestedMessages) {
addExtensions(message, reg);
}
break;
}
}
/**
* Create descriptors for fields and oneof groups, and add them to the message.
* Recurses into nested types.
*/
function addFields(message, reg, mapEntries) {
const allOneofs = message.proto.oneofDecl.map((proto) => newOneof(proto, message));
const oneofsSeen = new Set();
for (const proto of message.proto.field) {
const oneof = findOneof(proto, allOneofs);
const field = newField(proto, message, reg, oneof, mapEntries);
message.fields.push(field);
message.field[field.localName] = field;
if (oneof === undefined) {
message.members.push(field);
}
else {
oneof.fields.push(field);
if (!oneofsSeen.has(oneof)) {
oneofsSeen.add(oneof);
message.members.push(oneof);
}
}
}
for (const oneof of allOneofs.filter((o) => oneofsSeen.has(o))) {
message.oneofs.push(oneof);
}
for (const child of message.nestedMessages) {
addFields(child, reg, mapEntries);
}
}
/**
* Create a descriptor for an enumeration, and add it our cart and to the
* parent type, if any.
*/
function addEnum(proto, file, parent, reg) {
var _a, _b, _c;
const sharedPrefix = findEnumSharedPrefix(proto.name, proto.value);
const desc = {
kind: "enum",
proto,
deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false,
file,
parent,
open: true,
name: proto.name,
typeName: makeTypeName(proto, parent, file),
value: {},
values: [],
sharedPrefix,
toString() {
return `enum ${this.typeName}`;
},
};
desc.open = isEnumOpen(desc);
reg.add(desc);
proto.value.forEach((proto) => {
var _a, _b;
const name = proto.name;
desc.values.push((desc.value[proto.number] = {
kind: "enum_value",
proto,
deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false,
parent: desc,
name,
localName: safeObjectProperty(sharedPrefix == undefined
? name
: name.substring(sharedPrefix.length)),
number: proto.number,
toString() {
return `enum value ${desc.typeName}.${name}`;
},
}));
});
((_c = parent === null || parent === void 0 ? void 0 : parent.nestedEnums) !== null && _c !== void 0 ? _c : file.enums).push(desc);
}
/**
* Create a descriptor for a message, including nested types, and add it to our
* cart. Note that this does not create descriptors fields.
*/
function addMessage(proto, file, parent, reg, mapEntries) {
var _a, _b, _c, _d;
const desc = {
kind: "message",
proto,
deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false,
file,
parent,
name: proto.name,
typeName: makeTypeName(proto, parent, file),
fields: [],
field: {},
oneofs: [],
members: [],
nestedEnums: [],
nestedMessages: [],
nestedExtensions: [],
toString() {
return `message ${this.typeName}`;
},
};
if (((_c = proto.options) === null || _c === void 0 ? void 0 : _c.mapEntry) === true) {
mapEntries.add(desc);
}
else {
((_d = parent === null || parent === void 0 ? void 0 : parent.nestedMessages) !== null && _d !== void 0 ? _d : file.messages).push(desc);
reg.add(desc);
}
for (const enumProto of proto.enumType) {
addEnum(enumProto, file, desc, reg);
}
for (const messageProto of proto.nestedType) {
addMessage(messageProto, file, desc, reg, mapEntries);
}
}
/**
* Create a descriptor for a service, including methods, and add it to our
* cart.
*/
function addService(proto, file, reg) {
var _a, _b;
const desc = {
kind: "service",
proto,
deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false,
file,
name: proto.name,
typeName: makeTypeName(proto, undefined, file),
methods: [],
method: {},
toString() {
return `service ${this.typeName}`;
},
};
file.services.push(desc);
reg.add(desc);
for (const methodProto of proto.method) {
const method = newMethod(methodProto, desc, reg);
desc.methods.push(method);
desc.method[method.localName] = method;
}
}
/**
* Create a descriptor for a method.
*/
function newMethod(proto, parent, reg) {
var _a, _b, _c, _d;
let methodKind;
if (proto.clientStreaming && proto.serverStreaming) {
methodKind = "bidi_streaming";
}
else if (proto.clientStreaming) {
methodKind = "client_streaming";
}
else if (proto.serverStreaming) {
methodKind = "server_streaming";
}
else {
methodKind = "unary";
}
const input = reg.getMessage(trimLeadingDot(proto.inputType));
const output = reg.getMessage(trimLeadingDot(proto.outputType));
assert(input, `invalid MethodDescriptorProto: input_type ${proto.inputType} not found`);
assert(output, `invalid MethodDescriptorProto: output_type ${proto.inputType} not found`);
const name = proto.name;
return {
kind: "rpc",
proto,
deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false,
parent,
name,
localName: safeObjectProperty(name.length
? safeObjectProperty(name[0].toLowerCase() + name.substring(1))
: name),
methodKind,
input,
output,
idempotency: (_d = (_c = proto.options) === null || _c === void 0 ? void 0 : _c.idempotencyLevel) !== null && _d !== void 0 ? _d : IDEMPOTENCY_UNKNOWN,
toString() {
return `rpc ${parent.typeName}.${name}`;
},
};
}
/**
* Create a descriptor for a oneof group.
*/
function newOneof(proto, parent) {
return {
kind: "oneof",
proto,
deprecated: false,
parent,
fields: [],
name: proto.name,
localName: safeObjectProperty(protoCamelCase(proto.name)),
toString() {
return `oneof ${parent.typeName}.${this.name}`;
},
};
}
function newField(proto, parentOrFile, reg, oneof, mapEntries) {
var _a, _b, _c;
const isExtension = mapEntries === undefined;
const field = {
kind: "field",
proto,
deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false,
name: proto.name,
number: proto.number,
scalar: undefined,
message: undefined,
enum: undefined,
presence: getFieldPresence(proto, oneof, isExtension, parentOrFile),
listKind: undefined,
mapKind: undefined,
mapKey: undefined,
delimitedEncoding: undefined,
packed: undefined,
longAsString: false,
getDefaultValue: undefined,
};
if (isExtension) {
// extension field
const file = parentOrFile.kind == "file" ? parentOrFile : parentOrFile.file;
const parent = parentOrFile.kind == "file" ? undefined : parentOrFile;
const typeName = makeTypeName(proto, parent, file);
field.kind = "extension";
field.file = file;
field.parent = parent;
field.oneof = undefined;
field.typeName = typeName;
field.jsonName = `[${typeName}]`; // option json_name is not allowed on extension fields
field.toString = () => `extension ${typeName}`;
const extendee = reg.getMessage(trimLeadingDot(proto.extendee));
assert(extendee, `invalid FieldDescriptorProto: extendee ${proto.extendee} not found`);
field.extendee = extendee;
}
else {
// regular field
const parent = parentOrFile;
assert(parent.kind == "message");
field.parent = parent;
field.oneof = oneof;
field.localName = oneof
? protoCamelCase(proto.name)
: safeObjectProperty(protoCamelCase(proto.name));
field.jsonName = proto.jsonName;
field.toString = () => `field ${parent.typeName}.${proto.name}`;
}
const label = proto.label;
const type = proto.type;
const jstype = (_c = proto.options) === null || _c === void 0 ? void 0 : _c.jstype;
if (label === LABEL_REPEATED) {
// list or map field
const mapEntry = type == TYPE_MESSAGE
? mapEntries === null || mapEntries === void 0 ? void 0 : mapEntries.get(trimLeadingDot(proto.typeName))
: undefined;
if (mapEntry) {
// map field
field.fieldKind = "map";
const { key, value } = findMapEntryFields(mapEntry);
field.mapKey = key.scalar;
field.mapKind = value.fieldKind;
field.message = value.message;
field.delimitedEncoding = false; // map fields are always LENGTH_PREFIXED
field.enum = value.enum;
field.scalar = value.scalar;
return field;
}
// list field
field.fieldKind = "list";
switch (type) {
case TYPE_MESSAGE:
case TYPE_GROUP:
field.listKind = "message";
field.message = reg.getMessage(trimLeadingDot(proto.typeName));
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
assert(field.message);
field.delimitedEncoding = isDelimitedEncoding(proto, parentOrFile);
break;
case TYPE_ENUM:
field.listKind = "enum";
field.enum = reg.getEnum(trimLeadingDot(proto.typeName));
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
assert(field.enum);
break;
default:
field.listKind = "scalar";
field.scalar = type;
field.longAsString = jstype == JS_STRING;
break;
}
field.packed = isPackedField(proto, parentOrFile);
return field;
}
// singular
switch (type) {
case TYPE_MESSAGE:
case TYPE_GROUP:
field.fieldKind = "message";
field.message = reg.getMessage(trimLeadingDot(proto.typeName));
assert(
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
field.message, `invalid FieldDescriptorProto: type_name ${proto.typeName} not found`);
field.delimitedEncoding = isDelimitedEncoding(proto, parentOrFile);
field.getDefaultValue = () => undefined;
break;
case TYPE_ENUM: {
const enumeration = reg.getEnum(trimLeadingDot(proto.typeName));
assert(enumeration !== undefined, `invalid FieldDescriptorProto: type_name ${proto.typeName} not found`);
field.fieldKind = "enum";
field.enum = reg.getEnum(trimLeadingDot(proto.typeName));
field.getDefaultValue = () => {
return unsafeIsSetExplicit(proto, "defaultValue")
? parseTextFormatEnumValue(enumeration, proto.defaultValue)
: undefined;
};
break;
}
default: {
field.fieldKind = "scalar";
field.scalar = type;
field.longAsString = jstype == JS_STRING;
field.getDefaultValue = () => {
return unsafeIsSetExplicit(proto, "defaultValue")
? parseTextFormatScalarValue(type, proto.defaultValue)
: undefined;
};
break;
}
}
return field;
}
/**
* Parse the "syntax" and "edition" fields, returning one of the supported
* editions.
*/
function getFileEdition(proto) {
switch (proto.syntax) {
case "":
case "proto2":
return EDITION_PROTO2;
case "proto3":
return EDITION_PROTO3;
case "editions":
if (proto.edition in featureDefaults) {
return proto.edition;
}
throw new Error(`${proto.name}: unsupported edition`);
default:
throw new Error(`${proto.name}: unsupported syntax "${proto.syntax}"`);
}
}
/**
* Resolve dependencies of FileDescriptorProto to DescFile.
*/
function findFileDependencies(proto, reg) {
return proto.dependency.map((wantName) => {
const dep = reg.getFile(wantName);
if (!dep) {
throw new Error(`Cannot find ${wantName}, imported by ${proto.name}`);
}
return dep;
});
}
/**
* Finds a prefix shared by enum values, for example `my_enum_` for
* `enum MyEnum {MY_ENUM_A=0; MY_ENUM_B=1;}`.
*/
function findEnumSharedPrefix(enumName, values) {
const prefix = camelToSnakeCase(enumName) + "_";
for (const value of values) {
if (!value.name.toLowerCase().startsWith(prefix)) {
return undefined;
}
const shortName = value.name.substring(prefix.length);
if (shortName.length == 0) {
return undefined;
}
if (/^\d/.test(shortName)) {
// identifiers must not start with numbers
return undefined;
}
}
return prefix;
}
/**
* Converts lowerCamelCase or UpperCamelCase into lower_snake_case.
* This is used to find shared prefixes in an enum.
*/
function camelToSnakeCase(camel) {
return (camel.substring(0, 1) + camel.substring(1).replace(/[A-Z]/g, (c) => "_" + c)).toLowerCase();
}
/**
* Create a fully qualified name for a protobuf type or extension field.
*
* The fully qualified name for messages, enumerations, and services is
* constructed by concatenating the package name (if present), parent
* message names (for nested types), and the type name. We omit the leading
* dot added by protobuf compilers. Examples:
* - mypackage.MyMessage
* - mypackage.MyMessage.NestedMessage
*
* The fully qualified name for extension fields is constructed by
* concatenating the package name (if present), parent message names (for
* extensions declared within a message), and the field name. Examples:
* - mypackage.extfield
* - mypackage.MyMessage.extfield
*/
function makeTypeName(proto, parent, file) {
let typeName;
if (parent) {
typeName = `${parent.typeName}.${proto.name}`;
}
else if (file.proto.package.length > 0) {
typeName = `${file.proto.package}.${proto.name}`;
}
else {
typeName = `${proto.name}`;
}
return typeName;
}
/**
* Remove the leading dot from a fully qualified type name.
*/
function trimLeadingDot(typeName) {
return typeName.startsWith(".") ? typeName.substring(1) : typeName;
}
/**
* Did the user put the field in a oneof group?
* Synthetic oneofs for proto3 optionals are ignored.
*/
function findOneof(proto, allOneofs) {
if (!unsafeIsSetExplicit(proto, "oneofIndex")) {
return undefined;
}
if (proto.proto3Optional) {
return undefined;
}
const oneof = allOneofs[proto.oneofIndex];
assert(
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
oneof, `invalid FieldDescriptorProto: oneof #${proto.oneofIndex} for field #${proto.number} not found`);
return oneof;
}
/**
* Presence of the field.
* See https://protobuf.dev/programming-guides/field_presence/
*/
function getFieldPresence(proto, oneof, isExtension, parent) {
if (proto.label == LABEL_REQUIRED) {
// proto2 required is LEGACY_REQUIRED
return LEGACY_REQUIRED;
}
if (proto.label == LABEL_REPEATED) {
// repeated fields (including maps) do not track presence
return IMPLICIT;
}
if (!!oneof || proto.proto3Optional) {
// oneof is always explicit
return EXPLICIT;
}
if (proto.type == TYPE_MESSAGE) {
// singular message field cannot be implicit
return EXPLICIT;
}
if (isExtension) {
// extensions always track presence
return EXPLICIT;
}
return resolveFeature("fieldPresence", { proto, parent });
}
/**
* Pack this repeated field?
*/
function isPackedField(proto, parent) {
if (proto.label != LABEL_REPEATED) {
return false;
}
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
switch (proto.type) {
case TYPE_STRING:
case TYPE_BYTES:
case TYPE_GROUP:
case TYPE_MESSAGE:
// length-delimited types cannot be packed
return false;
}
const o = proto.options;
if (o && unsafeIsSetExplicit(o, "packed")) {
// prefer the field option over edition features
return o.packed;
}
return (PACKED ==
resolveFeature("repeatedFieldEncoding", {
proto,
parent,
}));
}
/**
* Find the key and value fields of a synthetic map entry message.
*/
function findMapEntryFields(mapEntry) {
const key = mapEntry.fields.find((f) => f.number === 1);
const value = mapEntry.fields.find((f) => f.number === 2);
assert(key &&
key.fieldKind == "scalar" &&
key.scalar != ScalarType.BYTES &&
key.scalar != ScalarType.FLOAT &&
key.scalar != ScalarType.DOUBLE &&
value &&
value.fieldKind != "list" &&
value.fieldKind != "map");
return { key, value };
}
/**
* Enumerations can be open or closed.
* See https://protobuf.dev/programming-guides/enum/
*/
function isEnumOpen(desc) {
var _a;
return (OPEN ==
resolveFeature("enumType", {
proto: desc.proto,
parent: (_a = desc.parent) !== null && _a !== void 0 ? _a : desc.file,
}));
}
/**
* Encode the message delimited (a.k.a. proto2 group encoding), or
* length-prefixed?
*/
function isDelimitedEncoding(proto, parent) {
if (proto.type == TYPE_GROUP) {
return true;
}
return (DELIMITED ==
resolveFeature("messageEncoding", {
proto,
parent,
}));
}
function resolveFeature(name, ref) {
var _a, _b;
const featureSet = (_a = ref.proto.options) === null || _a === void 0 ? void 0 : _a.features;
if (featureSet) {
const val = featureSet[name];
if (val != 0) {
return val;
}
}
if ("kind" in ref) {
if (ref.kind == "message") {
return resolveFeature(name, (_b = ref.parent) !== null && _b !== void 0 ? _b : ref.file);
}
const editionDefaults = featureDefaults[ref.edition];
if (!editionDefaults) {
throw new Error(`feature default for edition ${ref.edition} not found`);
}
return editionDefaults[name];
}
return resolveFeature(name, ref.parent);
}
/**
* Assert that condition is truthy or throw error (with message)
*/
function assert(condition, msg) {
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions -- we want the implicit conversion to boolean
if (!condition) {
throw new Error(msg);
}
}