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
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);
|
|
}
|
|
}
|