From 1fafc437a4a9a9dfe2b29998cba8e97fd48a6633 Mon Sep 17 00:00:00 2001 From: Mike Kruskal <62662355+mkruskal-google@users.noreply.github.com> Date: Fri, 23 May 2025 18:56:59 -0700 Subject: [PATCH 1/6] Fix error message on parse failure --- src/root.js | 6 +++--- tests/comp_parse-uncommon.js | 10 ++++++++++ tests/data/invalid-lookup.proto | 11 +++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 tests/data/invalid-lookup.proto diff --git a/src/root.js b/src/root.js index 7f0927996..99625d453 100644 --- a/src/root.js +++ b/src/root.js @@ -110,9 +110,6 @@ Root.prototype.load = function load(filename, options, callback) { // Finishes loading by calling the callback (exactly once) function finish(err, root) { - if (root) { - root.resolveAll(); - } /* istanbul ignore if */ if (!callback) { return; @@ -120,6 +117,9 @@ Root.prototype.load = function load(filename, options, callback) { if (sync) { throw err; } + if (root) { + root.resolveAll(); + } var cb = callback; callback = null; cb(err, root); diff --git a/tests/comp_parse-uncommon.js b/tests/comp_parse-uncommon.js index 07ce36339..8a37926ac 100644 --- a/tests/comp_parse-uncommon.js +++ b/tests/comp_parse-uncommon.js @@ -34,3 +34,13 @@ function traverseTypes(current, fn) { traverseTypes(nested, fn); }); } + +tape.test("invalid lookup", async function(test) { + try { + await protobuf.load("tests/data/invalid-lookup.proto"); + test.fail("should have thrown"); + } catch(err) { + test.match(err.message, /illegal token 'required'/, "failed to parse"); + } +}); + diff --git a/tests/data/invalid-lookup.proto b/tests/data/invalid-lookup.proto new file mode 100644 index 000000000..88702660b --- /dev/null +++ b/tests/data/invalid-lookup.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package test; + +service MyService { + rpc MyMethod (InvalidMessage) returns (stream InvalidMessage) {}; +} + +message InvalidMessage { + required bad_field = 1; +} \ No newline at end of file From 94fd0e054cc8e4f68125df6fd97035cc09c637fc Mon Sep 17 00:00:00 2001 From: Mike Kruskal <62662355+mkruskal-google@users.noreply.github.com> Date: Tue, 27 May 2025 12:45:58 -0700 Subject: [PATCH 2/6] Fix root-less type caching bug --- src/namespace.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/namespace.js b/src/namespace.js index 305e700b6..111a9ea7a 100644 --- a/src/namespace.js +++ b/src/namespace.js @@ -408,7 +408,7 @@ Namespace.prototype.lookup = function lookup(path, filterTypes, parentAlreadyChe return this.root.lookup(path.slice(1), filterTypes); // Early bailout for objects with matching absolute paths - var found = this.root._fullyQualifiedObjects["." + flatPath]; + var found = this.root._fullyQualifiedObjects && this.root._fullyQualifiedObjects["." + flatPath]; if (found && (!filterTypes || filterTypes.indexOf(found.constructor) > -1)) { return found; } From 6053235f4a176d3017ee9aa494932f2d10a68b88 Mon Sep 17 00:00:00 2001 From: Mike Kruskal <62662355+mkruskal-google@users.noreply.github.com> Date: Tue, 27 May 2025 12:46:26 -0700 Subject: [PATCH 3/6] Add basic support for editions descriptors + test coverage --- ext/descriptor/index.js | 243 +++++-- google/protobuf/descriptor.json | 569 +++++++++++++++- google/protobuf/descriptor.proto | 235 ++++++- src/namespace.js | 2 + src/root.js | 1 - tests/comp_import_extend.js | 204 +++++- tests/data/google/protobuf/descriptor.proto | 679 ++++++++++++++++---- 7 files changed, 1709 insertions(+), 224 deletions(-) diff --git a/ext/descriptor/index.js b/ext/descriptor/index.js index 6aafd2ab8..1ea3ec800 100644 --- a/ext/descriptor/index.js +++ b/ext/descriptor/index.js @@ -35,6 +35,25 @@ var Namespace = $protobuf.Namespace, * @property {IFileOptions} [options] Options * @property {*} [sourceCodeInfo] Not supported * @property {string} [syntax="proto2"] Syntax + * @property {IEdition} [edition] Edition + */ + +/** + * Values of the Edition enum. + * @typedef IEdition + * @type {number} + * @property {number} EDITION_UNKNOWN=0 + * @property {number} EDITION_LEGACY=900 + * @property {number} EDITION_PROTO2=998 + * @property {number} EDITION_PROTO3=999 + * @property {number} EDITION_2023=1000 + * @property {number} EDITION_2024=1001 + * @property {number} EDITION_1_TEST_ONLY=1 + * @property {number} EDITION_2_TEST_ONLY=2 + * @property {number} EDITION_99997_TEST_ONLY=99997 + * @property {number} EDITION_99998_TEST_ONLY=99998 + * @property {number} EDITION_99998_TEST_ONLY=99999 + * @property {number} EDITION_MAX=2147483647 */ /** @@ -85,20 +104,21 @@ Root.fromDescriptor = function fromDescriptor(descriptor) { filePackage = root; if ((fileDescriptor = descriptor.file[j])["package"] && fileDescriptor["package"].length) filePackage = root.define(fileDescriptor["package"]); + var edition = editionFromDescriptor(fileDescriptor); if (fileDescriptor.name && fileDescriptor.name.length) root.files.push(filePackage.filename = fileDescriptor.name); if (fileDescriptor.messageType) for (i = 0; i < fileDescriptor.messageType.length; ++i) - filePackage.add(Type.fromDescriptor(fileDescriptor.messageType[i], fileDescriptor.syntax)); + filePackage.add(Type.fromDescriptor(fileDescriptor.messageType[i], edition)); if (fileDescriptor.enumType) for (i = 0; i < fileDescriptor.enumType.length; ++i) - filePackage.add(Enum.fromDescriptor(fileDescriptor.enumType[i])); + filePackage.add(Enum.fromDescriptor(fileDescriptor.enumType[i], edition)); if (fileDescriptor.extension) for (i = 0; i < fileDescriptor.extension.length; ++i) - filePackage.add(Field.fromDescriptor(fileDescriptor.extension[i])); + filePackage.add(Field.fromDescriptor(fileDescriptor.extension[i], edition)); if (fileDescriptor.service) for (i = 0; i < fileDescriptor.service.length; ++i) - filePackage.add(Service.fromDescriptor(fileDescriptor.service[i])); + filePackage.add(Service.fromDescriptor(fileDescriptor.service[i], edition)); var opts = fromDescriptorOptions(fileDescriptor.options, exports.FileOptions); if (opts) { var ks = Object.keys(opts); @@ -108,42 +128,41 @@ Root.fromDescriptor = function fromDescriptor(descriptor) { } } - return root; + return root.resolveAll(); }; /** * Converts a root to a descriptor set. * @returns {Message} Descriptor - * @param {string} [syntax="proto2"] Syntax + * @param {string} [edition="proto2"] The syntax or edition to use */ -Root.prototype.toDescriptor = function toDescriptor(syntax) { +Root.prototype.toDescriptor = function toDescriptor(edition) { var set = exports.FileDescriptorSet.create(); - Root_toDescriptorRecursive(this, set.file, syntax); + Root_toDescriptorRecursive(this, set.file, edition); return set; }; // Traverses a namespace and assembles the descriptor set -function Root_toDescriptorRecursive(ns, files, syntax) { +function Root_toDescriptorRecursive(ns, files, edition) { // Create a new file var file = exports.FileDescriptorProto.create({ name: ns.filename || (ns.fullName.substring(1).replace(/\./g, "_") || "root") + ".proto" }); - if (syntax) - file.syntax = syntax; + editionToDescriptor(edition, file); if (!(ns instanceof Root)) file["package"] = ns.fullName.substring(1); // Add nested types for (var i = 0, nested; i < ns.nestedArray.length; ++i) if ((nested = ns._nestedArray[i]) instanceof Type) - file.messageType.push(nested.toDescriptor(syntax)); + file.messageType.push(nested.toDescriptor(edition)); else if (nested instanceof Enum) file.enumType.push(nested.toDescriptor()); else if (nested instanceof Field) - file.extension.push(nested.toDescriptor(syntax)); + file.extension.push(nested.toDescriptor(edition)); else if (nested instanceof Service) file.service.push(nested.toDescriptor()); else if (nested instanceof /* plain */ Namespace) - Root_toDescriptorRecursive(nested, files, syntax); // requires new file + Root_toDescriptorRecursive(nested, files, edition); // requires new file // Keep package-level options file.options = toDescriptorOptions(ns.options, exports.FileOptions); @@ -194,12 +213,15 @@ var unnamedMessageIndex = 0; /** * Creates a type from a descriptor. + * + * Warning: this is not safe to use with editions protos, since it discards relevant file context. + * * @param {IDescriptorProto|Reader|Uint8Array} descriptor Descriptor - * @param {string} [syntax="proto2"] Syntax + * @param {string} [edition="proto2"] The syntax or edition to use + * @param {boolean} [nested=false] Whether or not this is a nested object * @returns {Type} Type instance */ -Type.fromDescriptor = function fromDescriptor(descriptor, syntax) { - +Type.fromDescriptor = function fromDescriptor(descriptor, edition, nested) { // Decode the descriptor message if specified as a buffer: if (typeof descriptor.length === "number") descriptor = exports.DescriptorProto.decode(descriptor); @@ -208,28 +230,31 @@ Type.fromDescriptor = function fromDescriptor(descriptor, syntax) { var type = new Type(descriptor.name.length ? descriptor.name : "Type" + unnamedMessageIndex++, fromDescriptorOptions(descriptor.options, exports.MessageOptions)), i; + if (!nested) + type._edition = edition; + /* Oneofs */ if (descriptor.oneofDecl) for (i = 0; i < descriptor.oneofDecl.length; ++i) type.add(OneOf.fromDescriptor(descriptor.oneofDecl[i])); /* Fields */ if (descriptor.field) for (i = 0; i < descriptor.field.length; ++i) { - var field = Field.fromDescriptor(descriptor.field[i], syntax); + var field = Field.fromDescriptor(descriptor.field[i], edition, true); type.add(field); if (descriptor.field[i].hasOwnProperty("oneofIndex")) // eslint-disable-line no-prototype-builtins type.oneofsArray[descriptor.field[i].oneofIndex].add(field); } /* Extension fields */ if (descriptor.extension) for (i = 0; i < descriptor.extension.length; ++i) - type.add(Field.fromDescriptor(descriptor.extension[i], syntax)); + type.add(Field.fromDescriptor(descriptor.extension[i], edition, true)); /* Nested types */ if (descriptor.nestedType) for (i = 0; i < descriptor.nestedType.length; ++i) { - type.add(Type.fromDescriptor(descriptor.nestedType[i], syntax)); + type.add(Type.fromDescriptor(descriptor.nestedType[i], edition, true)); if (descriptor.nestedType[i].options && descriptor.nestedType[i].options.mapEntry) type.setOption("map_entry", true); } /* Nested enums */ if (descriptor.enumType) for (i = 0; i < descriptor.enumType.length; ++i) - type.add(Enum.fromDescriptor(descriptor.enumType[i])); + type.add(Enum.fromDescriptor(descriptor.enumType[i], edition, true)); /* Extension ranges */ if (descriptor.extensionRange && descriptor.extensionRange.length) { type.extensions = []; for (i = 0; i < descriptor.extensionRange.length; ++i) @@ -251,18 +276,18 @@ Type.fromDescriptor = function fromDescriptor(descriptor, syntax) { /** * Converts a type to a descriptor. * @returns {Message} Descriptor - * @param {string} [syntax="proto2"] Syntax + * @param {string} [edition="proto2"] The syntax or edition to use */ -Type.prototype.toDescriptor = function toDescriptor(syntax) { +Type.prototype.toDescriptor = function toDescriptor(edition) { var descriptor = exports.DescriptorProto.create({ name: this.name }), i; /* Fields */ for (i = 0; i < this.fieldsArray.length; ++i) { var fieldDescriptor; - descriptor.field.push(fieldDescriptor = this._fieldsArray[i].toDescriptor(syntax)); + descriptor.field.push(fieldDescriptor = this._fieldsArray[i].toDescriptor(edition)); if (this._fieldsArray[i] instanceof MapField) { // map fields are repeated FieldNameEntry - var keyType = toDescriptorType(this._fieldsArray[i].keyType, this._fieldsArray[i].resolvedKeyType), - valueType = toDescriptorType(this._fieldsArray[i].type, this._fieldsArray[i].resolvedType), + var keyType = toDescriptorType(this._fieldsArray[i].keyType, this._fieldsArray[i].resolvedKeyType, false), + valueType = toDescriptorType(this._fieldsArray[i].type, this._fieldsArray[i].resolvedType, false), valueTypeName = valueType === /* type */ 11 || valueType === /* enum */ 14 ? this._fieldsArray[i].resolvedType && shortname(this.parent, this._fieldsArray[i].resolvedType) || this._fieldsArray[i].type : undefined; @@ -280,9 +305,9 @@ Type.prototype.toDescriptor = function toDescriptor(syntax) { descriptor.oneofDecl.push(this._oneofsArray[i].toDescriptor()); /* Nested... */ for (i = 0; i < this.nestedArray.length; ++i) { /* Extension fields */ if (this._nestedArray[i] instanceof Field) - descriptor.field.push(this._nestedArray[i].toDescriptor(syntax)); + descriptor.field.push(this._nestedArray[i].toDescriptor(edition)); /* Types */ else if (this._nestedArray[i] instanceof Type) - descriptor.nestedType.push(this._nestedArray[i].toDescriptor(syntax)); + descriptor.nestedType.push(this._nestedArray[i].toDescriptor(edition)); /* Enums */ else if (this._nestedArray[i] instanceof Enum) descriptor.enumType.push(this._nestedArray[i].toDescriptor()); // plain nested namespaces become packages instead in Root#toDescriptor @@ -373,11 +398,15 @@ var numberRe = /^(?![eE])[0-9]*(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?$/; /** * Creates a field from a descriptor. + * + * Warning: this is not safe to use with editions protos, since it discards relevant file context. + * * @param {IFieldDescriptorProto|Reader|Uint8Array} descriptor Descriptor - * @param {string} [syntax="proto2"] Syntax + * @param {string} [edition="proto2"] The syntax or edition to use + * @param {boolean} [nested=false] Whether or not this is a top-level object * @returns {Field} Field instance */ -Field.fromDescriptor = function fromDescriptor(descriptor, syntax) { +Field.fromDescriptor = function fromDescriptor(descriptor, edition, nested) { // Decode the descriptor message if specified as a buffer: if (typeof descriptor.length === "number") @@ -415,7 +444,12 @@ Field.fromDescriptor = function fromDescriptor(descriptor, syntax) { extendee ); + if (!nested) + field._edition = edition; + field.options = fromDescriptorOptions(descriptor.options, exports.FieldOptions); + if (descriptor.proto3_optional) + field.options.proto3_optional = true; if (descriptor.defaultValue && descriptor.defaultValue.length) { var defaultValue = descriptor.defaultValue; @@ -436,11 +470,11 @@ Field.fromDescriptor = function fromDescriptor(descriptor, syntax) { } if (packableDescriptorType(descriptor.type)) { - if (syntax === "proto3") { // defaults to packed=true (internal preset is packed=true) + if (edition === "proto3") { // defaults to packed=true (internal preset is packed=true) if (descriptor.options && !descriptor.options.packed) field.setOption("packed", false); - } else if (!(descriptor.options && descriptor.options.packed)) // defaults to packed=false - field.setOption("packed", false); + } else if ((!edition || edition === "proto2") && descriptor.options && descriptor.options.packed) // defaults to packed=false + field.setOption("packed", true); } return field; @@ -449,9 +483,9 @@ Field.fromDescriptor = function fromDescriptor(descriptor, syntax) { /** * Converts a field to a descriptor. * @returns {Message} Descriptor - * @param {string} [syntax="proto2"] Syntax + * @param {string} [edition="proto2"] The syntax or edition to use */ -Field.prototype.toDescriptor = function toDescriptor(syntax) { +Field.prototype.toDescriptor = function toDescriptor(edition) { var descriptor = exports.FieldDescriptorProto.create({ name: this.name, number: this.id }); if (this.map) { @@ -463,7 +497,7 @@ Field.prototype.toDescriptor = function toDescriptor(syntax) { } else { // Rewire field type - switch (descriptor.type = toDescriptorType(this.type, this.resolve().resolvedType)) { + switch (descriptor.type = toDescriptorType(this.type, this.resolve().resolvedType, this.delimited)) { case 10: // group case 11: // type case 14: // enum @@ -472,12 +506,13 @@ Field.prototype.toDescriptor = function toDescriptor(syntax) { } // Rewire field rule - switch (this.rule) { - case "repeated": descriptor.label = 3; break; - case "required": descriptor.label = 2; break; - default: descriptor.label = 1; break; + if (this.rule === "repeated") { + descriptor.label = 3; + } else if (this.required && edition === "proto2") { + descriptor.label = 2; + } else { + descriptor.label = 1; } - } // Handle extension field @@ -492,12 +527,14 @@ Field.prototype.toDescriptor = function toDescriptor(syntax) { descriptor.options = toDescriptorOptions(this.options, exports.FieldOptions); if (this.options["default"] != null) descriptor.defaultValue = String(this.options["default"]); + if (this.options.proto3_optional) + descriptor.proto3_optional = true; } - if (syntax === "proto3") { // defaults to packed=true + if (edition === "proto3") { // defaults to packed=true if (!this.packed) (descriptor.options || (descriptor.options = exports.FieldOptions.create())).packed = false; - } else if (this.packed) // defaults to packed=false + } else if ((!edition || edition === "proto2") && this.packed) // defaults to packed=false (descriptor.options || (descriptor.options = exports.FieldOptions.create())).packed = true; return descriptor; @@ -532,10 +569,15 @@ var unnamedEnumIndex = 0; /** * Creates an enum from a descriptor. + * + * Warning: this is not safe to use with editions protos, since it discards relevant file context. + * * @param {IEnumDescriptorProto|Reader|Uint8Array} descriptor Descriptor + * @param {string} [edition="proto2"] The syntax or edition to use + * @param {boolean} [nested=false] Whether or not this is a top-level object * @returns {Enum} Enum instance */ -Enum.fromDescriptor = function fromDescriptor(descriptor) { +Enum.fromDescriptor = function fromDescriptor(descriptor, edition, nested) { // Decode the descriptor message if specified as a buffer: if (typeof descriptor.length === "number") @@ -550,11 +592,16 @@ Enum.fromDescriptor = function fromDescriptor(descriptor) { values[name && name.length ? name : "NAME" + value] = value; } - return new Enum( + var enm = new Enum( descriptor.name && descriptor.name.length ? descriptor.name : "Enum" + unnamedEnumIndex++, values, fromDescriptorOptions(descriptor.options, exports.EnumOptions) ); + + if (!nested) + enm._edition = edition; + + return enm; }; /** @@ -588,6 +635,9 @@ var unnamedOneofIndex = 0; /** * Creates a oneof from a descriptor. + * + * Warning: this is not safe to use with editions protos, since it discards relevant file context. + * * @param {IOneofDescriptorProto|Reader|Uint8Array} descriptor Descriptor * @returns {OneOf} OneOf instance */ @@ -635,16 +685,23 @@ var unnamedServiceIndex = 0; /** * Creates a service from a descriptor. + * + * Warning: this is not safe to use with editions protos, since it discards relevant file context. + * * @param {IServiceDescriptorProto|Reader|Uint8Array} descriptor Descriptor + * @param {string} [edition="proto2"] The syntax or edition to use + * @param {boolean} [nested=false] Whether or not this is a top-level object * @returns {Service} Service instance */ -Service.fromDescriptor = function fromDescriptor(descriptor) { +Service.fromDescriptor = function fromDescriptor(descriptor, edition, nested) { // Decode the descriptor message if specified as a buffer: if (typeof descriptor.length === "number") descriptor = exports.ServiceDescriptorProto.decode(descriptor); var service = new Service(descriptor.name && descriptor.name.length ? descriptor.name : "Service" + unnamedServiceIndex++, fromDescriptorOptions(descriptor.options, exports.ServiceOptions)); + if (!nested) + service._edition = edition; if (descriptor.method) for (var i = 0; i < descriptor.method.length; ++i) service.add(Method.fromDescriptor(descriptor.method[i])); @@ -685,6 +742,9 @@ Service.prototype.toDescriptor = function toDescriptor() { /** * Properties of a MethodOptions message. + * + * Warning: this is not safe to use with editions protos, since it discards relevant file context. + * * @interface IMethodOptions * @property {boolean} [deprecated] */ @@ -777,7 +837,7 @@ function packableDescriptorType(type) { } // Converts a protobuf.js basic type to a descriptor type -function toDescriptorType(type, resolvedType) { +function toDescriptorType(type, resolvedType, delimited) { switch (type) { // 0 is reserved for errors case "double": return 1; @@ -799,41 +859,55 @@ function toDescriptorType(type, resolvedType) { if (resolvedType instanceof Enum) return 14; if (resolvedType instanceof Type) - return resolvedType.group ? 10 : 11; + return delimited ? 10 : 11; throw Error("illegal type: " + type); } +function fixDescriptorOptionsRecursive(obj, type) { + var val = {}; + for (var i = 0, field, key; i < type.fieldsArray.length; ++i) { + if ((key = (field = type._fieldsArray[i]).name) === "uninterpretedOption") continue; + if (!Object.prototype.hasOwnProperty.call(obj, key)) continue; + + var newKey = underScore(key); + if (field.resolvedType instanceof Type) { + val[newKey] = fixDescriptorOptionsRecursive(obj[key], field.resolvedType); + } else if(field.resolvedType instanceof Enum) { + val[newKey] = field.resolvedType.valuesById[obj[key]]; + } else { + val[newKey] = obj[key]; + } + } + return val; +} + // Converts descriptor options to an options object function fromDescriptorOptions(options, type) { if (!options) return undefined; - var out = []; - for (var i = 0, field, key, val; i < type.fieldsArray.length; ++i) - if ((key = (field = type._fieldsArray[i]).name) !== "uninterpretedOption") - if (options.hasOwnProperty(key)) { // eslint-disable-line no-prototype-builtins - val = options[key]; - if (field.resolvedType instanceof Enum && typeof val === "number" && field.resolvedType.valuesById[val] !== undefined) - val = field.resolvedType.valuesById[val]; - out.push(underScore(key), val); - } - return out.length ? $protobuf.util.toObject(out) : undefined; + return fixDescriptorOptionsRecursive(type.toObject(options), type); +} + +function camelCaseRecursive(obj) { + var val = {}; + var keys = Object.keys(obj); + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + var newKey = $protobuf.util.camelCase(key); + if (typeof obj[key] !== "object") { + val[newKey] = obj[key]; + } else { + val[newKey] = camelCaseRecursive(obj[key]); + } + } + return val; } // Converts an options object to descriptor options function toDescriptorOptions(options, type) { if (!options) return undefined; - var out = []; - for (var i = 0, ks = Object.keys(options), key, val; i < ks.length; ++i) { - val = options[key = ks[i]]; - if (key === "default") - continue; - var field = type.fields[key]; - if (!field && !(field = type.fields[key = $protobuf.util.camelCase(key)])) - continue; - out.push(key, val); - } - return out.length ? type.fromObject($protobuf.util.toObject(out)) : undefined; + return type.fromObject(camelCaseRecursive(options)); } // Calculates the shortest relative path from `from` to `to`. @@ -862,6 +936,37 @@ function underScore(str) { .replace(/([A-Z])(?=[a-z]|$)/g, function($0, $1) { return "_" + $1.toLowerCase(); }); } +function editionFromDescriptor(fileDescriptor) { + if (fileDescriptor.syntax === "editions") { + switch(fileDescriptor.edition) { + case exports.Edition.EDITION_2023: + return "2023"; + default: + throw new Error("Unsupported edition " + fileDescriptor.edition); + } + } + if (fileDescriptor.syntax === "proto3") { + return "proto3"; + } + return "proto2"; +} + +function editionToDescriptor(edition, fileDescriptor) { + if (!edition) return; + if (edition === "proto2" || edition === "proto3") { + fileDescriptor.syntax = edition; + } else { + fileDescriptor.syntax = "editions"; + switch(edition) { + case "2023": + fileDescriptor.edition = exports.Edition.EDITION_2023; + break; + default: + throw new Error("Unsupported edition " + edition); + } + } +} + // --- exports --- /** diff --git a/google/protobuf/descriptor.json b/google/protobuf/descriptor.json index f6c5c1123..20bcd7add 100644 --- a/google/protobuf/descriptor.json +++ b/google/protobuf/descriptor.json @@ -3,17 +3,51 @@ "google": { "nested": { "protobuf": { + "options": { + "go_package": "google.golang.org/protobuf/types/descriptorpb", + "java_package": "com.google.protobuf", + "java_outer_classname": "DescriptorProtos", + "csharp_namespace": "Google.Protobuf.Reflection", + "objc_class_prefix": "GPB", + "cc_enable_arenas": true, + "optimize_for": "SPEED" + }, "nested": { "FileDescriptorSet": { + "edition": "proto2", "fields": { "file": { "rule": "repeated", "type": "FileDescriptorProto", "id": 1 } + }, + "extensions": [ + [ + 536000000, + 536000000 + ] + ] + }, + "Edition": { + "edition": "proto2", + "values": { + "EDITION_UNKNOWN": 0, + "EDITION_LEGACY": 900, + "EDITION_PROTO2": 998, + "EDITION_PROTO3": 999, + "EDITION_2023": 1000, + "EDITION_2024": 1001, + "EDITION_1_TEST_ONLY": 1, + "EDITION_2_TEST_ONLY": 2, + "EDITION_99997_TEST_ONLY": 99997, + "EDITION_99998_TEST_ONLY": 99998, + "EDITION_99999_TEST_ONLY": 99999, + "EDITION_MAX": 2147483647 } }, "FileDescriptorProto": { + "edition": "proto2", "fields": { "name": { "type": "string", @@ -31,18 +65,12 @@ "publicDependency": { "rule": "repeated", "type": "int32", - "id": 10, - "options": { - "packed": false - } + "id": 10 }, "weakDependency": { "rule": "repeated", "type": "int32", - "id": 11, - "options": { - "packed": false - } + "id": 11 }, "messageType": { "rule": "repeated", @@ -75,10 +103,15 @@ "syntax": { "type": "string", "id": 12 + }, + "edition": { + "type": "Edition", + "id": 14 } } }, "DescriptorProto": { + "edition": "proto2", "fields": { "name": { "type": "string", @@ -139,6 +172,10 @@ "end": { "type": "int32", "id": 2 + }, + "options": { + "type": "ExtensionRangeOptions", + "id": 3 } } }, @@ -156,7 +193,82 @@ } } }, + "ExtensionRangeOptions": { + "edition": "proto2", + "fields": { + "uninterpretedOption": { + "rule": "repeated", + "type": "UninterpretedOption", + "id": 999 + }, + "declaration": { + "rule": "repeated", + "type": "Declaration", + "id": 2, + "options": { + "retention": "RETENTION_SOURCE" + } + }, + "features": { + "type": "FeatureSet", + "id": 50 + }, + "verification": { + "type": "VerificationState", + "id": 3, + "options": { + "default": "UNVERIFIED", + "retention": "RETENTION_SOURCE" + } + } + }, + "extensions": [ + [ + 1000, + 536870911 + ] + ], + "nested": { + "Declaration": { + "fields": { + "number": { + "type": "int32", + "id": 1 + }, + "fullName": { + "type": "string", + "id": 2 + }, + "type": { + "type": "string", + "id": 3 + }, + "reserved": { + "type": "bool", + "id": 5 + }, + "repeated": { + "type": "bool", + "id": 6 + } + }, + "reserved": [ + [ + 4, + 4 + ] + ] + }, + "VerificationState": { + "values": { + "DECLARATION": 0, + "UNVERIFIED": 1 + } + } + } + }, "FieldDescriptorProto": { + "edition": "proto2", "fields": { "name": { "type": "string", @@ -197,6 +309,10 @@ "options": { "type": "FieldOptions", "id": 8 + }, + "proto3Optional": { + "type": "bool", + "id": 17 } }, "nested": { @@ -225,13 +341,14 @@ "Label": { "values": { "LABEL_OPTIONAL": 1, - "LABEL_REQUIRED": 2, - "LABEL_REPEATED": 3 + "LABEL_REPEATED": 3, + "LABEL_REQUIRED": 2 } } } }, "OneofDescriptorProto": { + "edition": "proto2", "fields": { "name": { "type": "string", @@ -244,6 +361,7 @@ } }, "EnumDescriptorProto": { + "edition": "proto2", "fields": { "name": { "type": "string", @@ -257,10 +375,35 @@ "options": { "type": "EnumOptions", "id": 3 + }, + "reservedRange": { + "rule": "repeated", + "type": "EnumReservedRange", + "id": 4 + }, + "reservedName": { + "rule": "repeated", + "type": "string", + "id": 5 + } + }, + "nested": { + "EnumReservedRange": { + "fields": { + "start": { + "type": "int32", + "id": 1 + }, + "end": { + "type": "int32", + "id": 2 + } + } } } }, "EnumValueDescriptorProto": { + "edition": "proto2", "fields": { "name": { "type": "string", @@ -277,6 +420,7 @@ } }, "ServiceDescriptorProto": { + "edition": "proto2", "fields": { "name": { "type": "string", @@ -294,6 +438,7 @@ } }, "MethodDescriptorProto": { + "edition": "proto2", "fields": { "name": { "type": "string", @@ -322,6 +467,7 @@ } }, "FileOptions": { + "edition": "proto2", "fields": { "javaPackage": { "type": "string", @@ -375,7 +521,10 @@ }, "ccEnableArenas": { "type": "bool", - "id": 31 + "id": 31, + "options": { + "default": true + } }, "objcClassPrefix": { "type": "string", @@ -385,6 +534,30 @@ "type": "string", "id": 37 }, + "swiftPrefix": { + "type": "string", + "id": 39 + }, + "phpClassPrefix": { + "type": "string", + "id": 40 + }, + "phpNamespace": { + "type": "string", + "id": 41 + }, + "phpMetadataNamespace": { + "type": "string", + "id": 44 + }, + "rubyPackage": { + "type": "string", + "id": 45 + }, + "features": { + "type": "FeatureSet", + "id": 50 + }, "uninterpretedOption": { "rule": "repeated", "type": "UninterpretedOption", @@ -398,6 +571,11 @@ ] ], "reserved": [ + [ + 42, + 42 + ], + "php_generic_services", [ 38, 38 @@ -414,6 +592,7 @@ } }, "MessageOptions": { + "edition": "proto2", "fields": { "messageSetWireFormat": { "type": "bool", @@ -431,6 +610,17 @@ "type": "bool", "id": 7 }, + "deprecatedLegacyJsonFieldConflicts": { + "type": "bool", + "id": 11, + "options": { + "deprecated": true + } + }, + "features": { + "type": "FeatureSet", + "id": 12 + }, "uninterpretedOption": { "rule": "repeated", "type": "UninterpretedOption", @@ -444,13 +634,30 @@ ] ], "reserved": [ + [ + 4, + 4 + ], + [ + 5, + 5 + ], + [ + 6, + 6 + ], [ 8, 8 + ], + [ + 9, + 9 ] ] }, "FieldOptions": { + "edition": "proto2", "fields": { "ctype": { "type": "CType", @@ -474,6 +681,10 @@ "type": "bool", "id": 5 }, + "unverifiedLazy": { + "type": "bool", + "id": 15 + }, "deprecated": { "type": "bool", "id": 3 @@ -482,6 +693,32 @@ "type": "bool", "id": 10 }, + "debugRedact": { + "type": "bool", + "id": 16 + }, + "retention": { + "type": "OptionRetention", + "id": 17 + }, + "targets": { + "rule": "repeated", + "type": "OptionTargetType", + "id": 19 + }, + "editionDefaults": { + "rule": "repeated", + "type": "EditionDefault", + "id": 20 + }, + "features": { + "type": "FeatureSet", + "id": 21 + }, + "featureSupport": { + "type": "FeatureSupport", + "id": 22 + }, "uninterpretedOption": { "rule": "repeated", "type": "UninterpretedOption", @@ -498,6 +735,10 @@ [ 4, 4 + ], + [ + 18, + 18 ] ], "nested": { @@ -514,11 +755,69 @@ "JS_STRING": 1, "JS_NUMBER": 2 } + }, + "OptionRetention": { + "values": { + "RETENTION_UNKNOWN": 0, + "RETENTION_RUNTIME": 1, + "RETENTION_SOURCE": 2 + } + }, + "OptionTargetType": { + "values": { + "TARGET_TYPE_UNKNOWN": 0, + "TARGET_TYPE_FILE": 1, + "TARGET_TYPE_EXTENSION_RANGE": 2, + "TARGET_TYPE_MESSAGE": 3, + "TARGET_TYPE_FIELD": 4, + "TARGET_TYPE_ONEOF": 5, + "TARGET_TYPE_ENUM": 6, + "TARGET_TYPE_ENUM_ENTRY": 7, + "TARGET_TYPE_SERVICE": 8, + "TARGET_TYPE_METHOD": 9 + } + }, + "EditionDefault": { + "fields": { + "edition": { + "type": "Edition", + "id": 3 + }, + "value": { + "type": "string", + "id": 2 + } + } + }, + "FeatureSupport": { + "fields": { + "editionIntroduced": { + "type": "Edition", + "id": 1 + }, + "editionDeprecated": { + "type": "Edition", + "id": 2 + }, + "deprecationWarning": { + "type": "string", + "id": 3 + }, + "editionRemoved": { + "type": "Edition", + "id": 4 + } + } } } }, "OneofOptions": { + "edition": "proto2", "fields": { + "features": { + "type": "FeatureSet", + "id": 1 + }, "uninterpretedOption": { "rule": "repeated", "type": "UninterpretedOption", @@ -533,6 +832,7 @@ ] }, "EnumOptions": { + "edition": "proto2", "fields": { "allowAlias": { "type": "bool", @@ -542,6 +842,17 @@ "type": "bool", "id": 3 }, + "deprecatedLegacyJsonFieldConflicts": { + "type": "bool", + "id": 6, + "options": { + "deprecated": true + } + }, + "features": { + "type": "FeatureSet", + "id": 7 + }, "uninterpretedOption": { "rule": "repeated", "type": "UninterpretedOption", @@ -553,14 +864,33 @@ 1000, 536870911 ] + ], + "reserved": [ + [ + 5, + 5 + ] ] }, "EnumValueOptions": { + "edition": "proto2", "fields": { "deprecated": { "type": "bool", "id": 1 }, + "features": { + "type": "FeatureSet", + "id": 2 + }, + "debugRedact": { + "type": "bool", + "id": 3 + }, + "featureSupport": { + "type": "FieldOptions.FeatureSupport", + "id": 4 + }, "uninterpretedOption": { "rule": "repeated", "type": "UninterpretedOption", @@ -575,7 +905,12 @@ ] }, "ServiceOptions": { + "edition": "proto2", "fields": { + "features": { + "type": "FeatureSet", + "id": 34 + }, "deprecated": { "type": "bool", "id": 33 @@ -594,11 +929,23 @@ ] }, "MethodOptions": { + "edition": "proto2", "fields": { "deprecated": { "type": "bool", "id": 33 }, + "idempotencyLevel": { + "type": "IdempotencyLevel", + "id": 34, + "options": { + "default": "IDEMPOTENCY_UNKNOWN" + } + }, + "features": { + "type": "FeatureSet", + "id": 35 + }, "uninterpretedOption": { "rule": "repeated", "type": "UninterpretedOption", @@ -610,9 +957,19 @@ 1000, 536870911 ] - ] + ], + "nested": { + "IdempotencyLevel": { + "values": { + "IDEMPOTENCY_UNKNOWN": 0, + "NO_SIDE_EFFECTS": 1, + "IDEMPOTENT": 2 + } + } + } }, "UninterpretedOption": { + "edition": "proto2", "fields": { "name": { "rule": "repeated", @@ -661,7 +1018,160 @@ } } }, + "FeatureSet": { + "edition": "proto2", + "fields": { + "fieldPresence": { + "type": "FieldPresence", + "id": 1 + }, + "enumType": { + "type": "EnumType", + "id": 2 + }, + "repeatedFieldEncoding": { + "type": "RepeatedFieldEncoding", + "id": 3 + }, + "utf8Validation": { + "type": "Utf8Validation", + "id": 4 + }, + "messageEncoding": { + "type": "MessageEncoding", + "id": 5 + }, + "jsonFormat": { + "type": "JsonFormat", + "id": 6 + }, + "enforceNamingStyle": { + "type": "EnforceNamingStyle", + "id": 7 + } + }, + "extensions": [ + [ + 1000, + 9994 + ], + [ + 9995, + 9999 + ], + [ + 10000, + 10000 + ] + ], + "reserved": [ + [ + 999, + 999 + ] + ], + "nested": { + "FieldPresence": { + "values": { + "FIELD_PRESENCE_UNKNOWN": 0, + "EXPLICIT": 1, + "IMPLICIT": 2, + "LEGACY_REQUIRED": 3 + } + }, + "EnumType": { + "values": { + "ENUM_TYPE_UNKNOWN": 0, + "OPEN": 1, + "CLOSED": 2 + } + }, + "RepeatedFieldEncoding": { + "values": { + "REPEATED_FIELD_ENCODING_UNKNOWN": 0, + "PACKED": 1, + "EXPANDED": 2 + } + }, + "Utf8Validation": { + "values": { + "UTF8_VALIDATION_UNKNOWN": 0, + "VERIFY": 2, + "NONE": 3 + } + }, + "MessageEncoding": { + "values": { + "MESSAGE_ENCODING_UNKNOWN": 0, + "LENGTH_PREFIXED": 1, + "DELIMITED": 2 + } + }, + "JsonFormat": { + "values": { + "JSON_FORMAT_UNKNOWN": 0, + "ALLOW": 1, + "LEGACY_BEST_EFFORT": 2 + } + }, + "EnforceNamingStyle": { + "values": { + "ENFORCE_NAMING_STYLE_UNKNOWN": 0, + "STYLE2024": 1, + "STYLE_LEGACY": 2 + } + } + } + }, + "FeatureSetDefaults": { + "edition": "proto2", + "fields": { + "defaults": { + "rule": "repeated", + "type": "FeatureSetEditionDefault", + "id": 1 + }, + "minimumEdition": { + "type": "Edition", + "id": 4 + }, + "maximumEdition": { + "type": "Edition", + "id": 5 + } + }, + "nested": { + "FeatureSetEditionDefault": { + "fields": { + "edition": { + "type": "Edition", + "id": 3 + }, + "overridableFeatures": { + "type": "FeatureSet", + "id": 4 + }, + "fixedFeatures": { + "type": "FeatureSet", + "id": 5 + } + }, + "reserved": [ + [ + 1, + 1 + ], + [ + 2, + 2 + ], + "features" + ] + } + } + }, "SourceCodeInfo": { + "edition": "proto2", "fields": { "location": { "rule": "repeated", @@ -669,18 +1179,30 @@ "id": 1 } }, + "extensions": [ + [ + 536000000, + 536000000 + ] + ], "nested": { "Location": { "fields": { "path": { "rule": "repeated", "type": "int32", - "id": 1 + "id": 1, + "options": { + "packed": true + } }, "span": { "rule": "repeated", "type": "int32", - "id": 2 + "id": 2, + "options": { + "packed": true + } }, "leadingComments": { "type": "string", @@ -700,6 +1222,7 @@ } }, "GeneratedCodeInfo": { + "edition": "proto2", "fields": { "annotation": { "rule": "repeated", @@ -713,7 +1236,10 @@ "path": { "rule": "repeated", "type": "int32", - "id": 1 + "id": 1, + "options": { + "packed": true + } }, "sourceFile": { "type": "string", @@ -726,6 +1252,19 @@ "end": { "type": "int32", "id": 4 + }, + "semantic": { + "type": "Semantic", + "id": 5 + } + }, + "nested": { + "Semantic": { + "values": { + "NONE": 0, + "SET": 1, + "ALIAS": 2 + } } } } diff --git a/google/protobuf/descriptor.proto b/google/protobuf/descriptor.proto index 327949262..5d4e5ce8f 100644 --- a/google/protobuf/descriptor.proto +++ b/google/protobuf/descriptor.proto @@ -2,9 +2,35 @@ syntax = "proto2"; package google.protobuf; +option go_package = "google.golang.org/protobuf/types/descriptorpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DescriptorProtos"; +option csharp_namespace = "Google.Protobuf.Reflection"; +option objc_class_prefix = "GPB"; +option cc_enable_arenas = true; +option optimize_for = "SPEED"; + message FileDescriptorSet { repeated FileDescriptorProto file = 1; + + extensions 536000000; +} + +enum Edition { + + EDITION_UNKNOWN = 0; + EDITION_LEGACY = 900; + EDITION_PROTO2 = 998; + EDITION_PROTO3 = 999; + EDITION_2023 = 1000; + EDITION_2024 = 1001; + EDITION_1_TEST_ONLY = 1; + EDITION_2_TEST_ONLY = 2; + EDITION_99997_TEST_ONLY = 99997; + EDITION_99998_TEST_ONLY = 99998; + EDITION_99999_TEST_ONLY = 99999; + EDITION_MAX = 2147483647; } message FileDescriptorProto { @@ -21,6 +47,7 @@ message FileDescriptorProto { optional FileOptions options = 8; optional SourceCodeInfo source_code_info = 9; optional string syntax = 12; + optional Edition edition = 14; } message DescriptorProto { @@ -40,6 +67,7 @@ message DescriptorProto { optional int32 start = 1; optional int32 end = 2; + optional ExtensionRangeOptions options = 3; } message ReservedRange { @@ -49,6 +77,33 @@ message DescriptorProto { } } +message ExtensionRangeOptions { + + repeated UninterpretedOption uninterpreted_option = 999; + repeated Declaration declaration = 2 [retention="RETENTION_SOURCE"]; + optional FeatureSet features = 50; + optional VerificationState verification = 3 [default=UNVERIFIED, retention="RETENTION_SOURCE"]; + + message Declaration { + + optional int32 number = 1; + optional string full_name = 2; + optional string type = 3; + optional bool reserved = 5; + optional bool repeated = 6; + + reserved 4; + } + + enum VerificationState { + + DECLARATION = 0; + UNVERIFIED = 1; + } + + extensions 1000 to max; +} + message FieldDescriptorProto { optional string name = 1; @@ -61,6 +116,7 @@ message FieldDescriptorProto { optional int32 oneof_index = 9; optional string json_name = 10; optional FieldOptions options = 8; + optional bool proto3_optional = 17; enum Type { @@ -87,8 +143,8 @@ message FieldDescriptorProto { enum Label { LABEL_OPTIONAL = 1; - LABEL_REQUIRED = 2; LABEL_REPEATED = 3; + LABEL_REQUIRED = 2; } } @@ -103,6 +159,14 @@ message EnumDescriptorProto { optional string name = 1; repeated EnumValueDescriptorProto value = 2; optional EnumOptions options = 3; + repeated EnumReservedRange reserved_range = 4; + repeated string reserved_name = 5; + + message EnumReservedRange { + + optional int32 start = 1; + optional int32 end = 2; + } } message EnumValueDescriptorProto { @@ -142,9 +206,15 @@ message FileOptions { optional bool java_generic_services = 17; optional bool py_generic_services = 18; optional bool deprecated = 23; - optional bool cc_enable_arenas = 31; + optional bool cc_enable_arenas = 31 [default=true]; optional string objc_class_prefix = 36; optional string csharp_namespace = 37; + optional string swift_prefix = 39; + optional string php_class_prefix = 40; + optional string php_namespace = 41; + optional string php_metadata_namespace = 44; + optional string ruby_package = 45; + optional FeatureSet features = 50; repeated UninterpretedOption uninterpreted_option = 999; enum OptimizeMode { @@ -156,7 +226,7 @@ message FileOptions { extensions 1000 to max; - reserved 38; + reserved 42, "php_generic_services", 38; } message MessageOptions { @@ -165,11 +235,13 @@ message MessageOptions { optional bool no_standard_descriptor_accessor = 2; optional bool deprecated = 3; optional bool map_entry = 7; + optional bool deprecated_legacy_json_field_conflicts = 11 [deprecated=true]; + optional FeatureSet features = 12; repeated UninterpretedOption uninterpreted_option = 999; extensions 1000 to max; - reserved 8; + reserved 4, 5, 6, 8, 9; } message FieldOptions { @@ -178,8 +250,15 @@ message FieldOptions { optional bool packed = 2; optional JSType jstype = 6 [default=JS_NORMAL]; optional bool lazy = 5; + optional bool unverified_lazy = 15; optional bool deprecated = 3; optional bool weak = 10; + optional bool debug_redact = 16; + optional OptionRetention retention = 17; + repeated OptionTargetType targets = 19; + repeated EditionDefault edition_defaults = 20; + optional FeatureSet features = 21; + optional FeatureSupport feature_support = 22; repeated UninterpretedOption uninterpreted_option = 999; enum CType { @@ -196,13 +275,49 @@ message FieldOptions { JS_NUMBER = 2; } + enum OptionRetention { + + RETENTION_UNKNOWN = 0; + RETENTION_RUNTIME = 1; + RETENTION_SOURCE = 2; + } + + enum OptionTargetType { + + TARGET_TYPE_UNKNOWN = 0; + TARGET_TYPE_FILE = 1; + TARGET_TYPE_EXTENSION_RANGE = 2; + TARGET_TYPE_MESSAGE = 3; + TARGET_TYPE_FIELD = 4; + TARGET_TYPE_ONEOF = 5; + TARGET_TYPE_ENUM = 6; + TARGET_TYPE_ENUM_ENTRY = 7; + TARGET_TYPE_SERVICE = 8; + TARGET_TYPE_METHOD = 9; + } + + message EditionDefault { + + optional Edition edition = 3; + optional string value = 2; + } + + message FeatureSupport { + + optional Edition edition_introduced = 1; + optional Edition edition_deprecated = 2; + optional string deprecation_warning = 3; + optional Edition edition_removed = 4; + } + extensions 1000 to max; - reserved 4; + reserved 4, 18; } message OneofOptions { + optional FeatureSet features = 1; repeated UninterpretedOption uninterpreted_option = 999; extensions 1000 to max; @@ -212,14 +327,21 @@ message EnumOptions { optional bool allow_alias = 2; optional bool deprecated = 3; + optional bool deprecated_legacy_json_field_conflicts = 6 [deprecated=true]; + optional FeatureSet features = 7; repeated UninterpretedOption uninterpreted_option = 999; extensions 1000 to max; + + reserved 5; } message EnumValueOptions { optional bool deprecated = 1; + optional FeatureSet features = 2; + optional bool debug_redact = 3; + optional FieldOptions.FeatureSupport feature_support = 4; repeated UninterpretedOption uninterpreted_option = 999; extensions 1000 to max; @@ -227,6 +349,7 @@ message EnumValueOptions { message ServiceOptions { + optional FeatureSet features = 34; optional bool deprecated = 33; repeated UninterpretedOption uninterpreted_option = 999; @@ -236,8 +359,17 @@ message ServiceOptions { message MethodOptions { optional bool deprecated = 33; + optional IdempotencyLevel idempotency_level = 34 [default=IDEMPOTENCY_UNKNOWN]; + optional FeatureSet features = 35; repeated UninterpretedOption uninterpreted_option = 999; + enum IdempotencyLevel { + + IDEMPOTENCY_UNKNOWN = 0; + NO_SIDE_EFFECTS = 1; + IDEMPOTENT = 2; + } + extensions 1000 to max; } @@ -258,6 +390,87 @@ message UninterpretedOption { } } +message FeatureSet { + + optional FieldPresence field_presence = 1; + optional EnumType enum_type = 2; + optional RepeatedFieldEncoding repeated_field_encoding = 3; + optional Utf8Validation utf8_validation = 4; + optional MessageEncoding message_encoding = 5; + optional JsonFormat json_format = 6; + optional EnforceNamingStyle enforce_naming_style = 7; + + enum FieldPresence { + + FIELD_PRESENCE_UNKNOWN = 0; + EXPLICIT = 1; + IMPLICIT = 2; + LEGACY_REQUIRED = 3; + } + + enum EnumType { + + ENUM_TYPE_UNKNOWN = 0; + OPEN = 1; + CLOSED = 2; + } + + enum RepeatedFieldEncoding { + + REPEATED_FIELD_ENCODING_UNKNOWN = 0; + PACKED = 1; + EXPANDED = 2; + } + + enum Utf8Validation { + + UTF8_VALIDATION_UNKNOWN = 0; + VERIFY = 2; + NONE = 3; + } + + enum MessageEncoding { + + MESSAGE_ENCODING_UNKNOWN = 0; + LENGTH_PREFIXED = 1; + DELIMITED = 2; + } + + enum JsonFormat { + + JSON_FORMAT_UNKNOWN = 0; + ALLOW = 1; + LEGACY_BEST_EFFORT = 2; + } + + enum EnforceNamingStyle { + + ENFORCE_NAMING_STYLE_UNKNOWN = 0; + STYLE2024 = 1; + STYLE_LEGACY = 2; + } + + extensions 1000 to 9994, 9995 to 9999, 10000; + + reserved 999; +} + +message FeatureSetDefaults { + + repeated FeatureSetEditionDefault defaults = 1; + optional Edition minimum_edition = 4; + optional Edition maximum_edition = 5; + + message FeatureSetEditionDefault { + + optional Edition edition = 3; + optional FeatureSet overridable_features = 4; + optional FeatureSet fixed_features = 5; + + reserved 1, 2, "features"; + } +} + message SourceCodeInfo { repeated Location location = 1; @@ -270,6 +483,8 @@ message SourceCodeInfo { optional string trailing_comments = 4; repeated string leading_detached_comments = 6; } + + extensions 536000000; } message GeneratedCodeInfo { @@ -282,5 +497,13 @@ message GeneratedCodeInfo { optional string source_file = 2; optional int32 begin = 3; optional int32 end = 4; + optional Semantic semantic = 5; + + enum Semantic { + + NONE = 0; + SET = 1; + ALIAS = 2; + } } -} +} \ No newline at end of file diff --git a/src/namespace.js b/src/namespace.js index 111a9ea7a..3169280f5 100644 --- a/src/namespace.js +++ b/src/namespace.js @@ -352,6 +352,8 @@ Namespace.prototype.define = function define(path, json) { Namespace.prototype.resolveAll = function resolveAll() { if (!this._needsRecursiveResolve) return this; + this._resolveFeaturesRecursive(this._edition); + var nested = this.nestedArray, i = 0; this.resolve(); while (i < nested.length) diff --git a/src/root.js b/src/root.js index 99625d453..7e2ca6a8b 100644 --- a/src/root.js +++ b/src/root.js @@ -285,7 +285,6 @@ Root.prototype.resolveAll = function resolveAll() { throw Error("unresolvable extensions: " + this.deferred.map(function(field) { return "'extend " + field.extend + "' in " + field.parent.fullName; }).join(", ")); - this._resolveFeaturesRecursive(this._edition); return Namespace.prototype.resolveAll.call(this); }; diff --git a/tests/comp_import_extend.js b/tests/comp_import_extend.js index f69450369..574690415 100644 --- a/tests/comp_import_extend.js +++ b/tests/comp_import_extend.js @@ -3,16 +3,214 @@ Object.defineProperty(exports, "__esModule", { value: true }); var path = require("path"); var tape = require("tape"); var protobuf = require("../index"); + // to extend Root -require("../ext/descriptor"); -tape.test("extensions", function (test) { +var descriptor = require("../ext/descriptor"); + +tape.test("extensions - proto2 to proto3", function (test) { // load document with extended field imported multiple times var root = protobuf.loadSync(path.resolve(__dirname, "data/test.proto")); - root.resolveAll(); + // convert to Descriptor Set var decodedDescriptorSet = root.toDescriptor("proto3"); + // load back from descriptor set var root2 = protobuf.Root.fromDescriptor(decodedDescriptorSet); + + var Simple1 = root2.lookup("Simple1"); + test.notOk(Simple1.fields.aString.required, "required fields don't exist in proto3"); + test.notOk(Simple1.fields.aBoolean.hasPresence, "presence is not preserved"); + test.pass("should parse and resolve without errors"); test.end(); }); + +tape.test("extensions - proto2 roundtrip", function (test) { + // load document with extended field imported multiple times + var root = protobuf.parse(`syntax = "proto2"; + + message Message { + optional string explicit = 1; + required string required = 2; + repeated int32 packed = 3 [packed = true]; + repeated int32 unpacked = 4; + } + `).root.resolveAll(); + + // convert to Descriptor Set + var decodedDescriptorSet = root.toDescriptor("proto2"); + + // load back from descriptor set + var root2 = protobuf.Root.fromDescriptor(decodedDescriptorSet); + + test.same(root.toJSON(), root2.toJSON(), "JSON should roundtrip"); + + var Message = root2.lookup("Message"); + test.ok(Message.fields.required.required, "required field preserved"); + test.ok(Message.fields.explicit.hasPresence, "presence is preserved"); + test.ok(Message.fields.packed.packed, "packed is preserved"); + test.notOk(Message.fields.unpacked.packed, "expanded is preserved"); + + test.end(); +}); + +tape.test("extensions - proto3 roundtrip", function (test) { + var root = protobuf.parse(`syntax = "proto3"; + + message Message { + optional string explicit = 1; + string implicit = 2; + repeated int32 packed = 3; + repeated int32 unpacked = 4 [packed = false]; + } + `).root.resolveAll(); + + // convert to Descriptor Set + const decodedDescriptorSet = root.toDescriptor("proto3"); + + // load back from descriptor set + const root2 = protobuf.Root.fromDescriptor(decodedDescriptorSet); + + var Message = root2.lookup("Message"); + + test.same(root2.toJSON(), root.toJSON(), "JSON should roundtrip"); + + test.ok(Message.fields.explicit.hasPresence, "should have explicit presence"); + test.notOk(Message.fields.implicit.hasPresence, "should have implicit presence"); + test.ok(Message.fields.packed.packed, "packed is preserved"); + test.notOk(Message.fields.unpacked.packed, "expanded is preserved"); + + test.end(); +}); + +tape.test("extensions - edition 2023 file roundtrip", function (test) { + var json = { + nested: { Message: { + edition: "2023", + options: { "features": { "field_presence": "IMPLICIT" } }, + fields: { + explicit: { type: "string", id: 1, options: { "features": { "field_presence": "EXPLICIT" } } }, + implicit: { type: "string", id: 2 }, + required: { type: "string", id: 3, options: { "features": { "field_presence": "LEGACY_REQUIRED" }} }, + }, + nested: { Nested: { fields: { + explicit: { type: "string", id: 1, options: { "features": { "field_presence": "EXPLICIT" } } }, + implicit: { type: "string", id: 2 }, + } } } + } } + }; + var root = protobuf.Root.fromJSON(json); + + // convert to Descriptor Set + const decodedDescriptorSet = root.toDescriptor("2023"); + + // load back from descriptor set + const root2 = protobuf.Root.fromDescriptor(decodedDescriptorSet); + + var Type = root2.lookup("Message"); + var Nested = Type.nested.Nested; + + test.same(root2.toJSON(), json, "JSON should roundtrip"); + + test.ok(Type.fields.explicit.hasPresence, "should have explicit presence"); + test.notOk(Type.fields.implicit.hasPresence, "should have implicit presence"); + + test.ok(Nested.fields.explicit.hasPresence, "nested should have explicit presence"); + test.notOk(Nested.fields.implicit.hasPresence, "nested should have implicit presence"); + + test.end(); +}); + + +tape.test("extensions - proto2 root-less type", function (test) { + var Message = protobuf.Type.fromJSON("Message", { + "edition": "proto2", + "fields": { + "explicit": { + "type": "string", + "id": 1 + }, + "required": { + "rule": "required", + "type": "string", + "id": 2 + }, + "packed": { + "rule": "repeated", + "type": "int32", + "id": 3, + "options": { + "packed": true + } + }, + "unpacked": { + "rule": "repeated", + "type": "int32", + "id": 4 + }, + "nested": { + "type": "Nested", + "id": 5 + } + }, + "nested": { + "Nested": { + "fields": { + "a": { + "type": "int32", + "id": 1 + } + } + } + } + }).resolveAll(); + + // convert to Descriptor Set + const decodedDescriptorSet = Message.toDescriptor("proto2"); + + // load back from descriptor set + const Message2 = protobuf.Type.fromDescriptor(decodedDescriptorSet, "proto2").resolveAll(); + + test.same(Message2.toJSON(), Message.toJSON(), "JSON should roundtrip"); + + test.ok(Message2.fields.explicit.hasPresence, "should have explicit presence"); + test.ok(Message2.fields.required.required, "should have required presence"); + test.ok(Message2.fields.packed.packed, "should have packed encoding"); + test.notOk(Message2.fields.unpacked.packed, "should have expanded encoding"); + test.same(Message2.fields.nested.resolvedType, Message2.nested.Nested, "should have cross linkage"); + + test.end(); +}); + + +tape.test("extensions - unsupported edition", function (test) { + var json = { + nested: { Message: { + edition: "2023", + options: { "features": { "field_presence": "IMPLICIT" } }, + fields: { + explicit: { type: "string", id: 1, options: { "features": { "field_presence": "EXPLICIT" } } }, + implicit: { type: "string", id: 2 }, + }, + nested: { Nested: { fields: { + explicit: { type: "string", id: 1, options: { "features": { "field_presence": "EXPLICIT" } } }, + implicit: { type: "string", id: 2 }, + } } } + } } + }; + var root = protobuf.Root.fromJSON(json); + + // convert to Descriptor Set + test.throws(function() { + root.toDescriptor("2030") + }, /Unsupported edition 2030/, "unsupported edition output throws"); + + const decodedDescriptorSet = root.toDescriptor("2023"); + decodedDescriptorSet.file[0].edition = descriptor.Edition.EDITION_99997_TEST_ONLY + + test.throws(function() { + protobuf.Root.fromDescriptor(decodedDescriptorSet) + }, /Unsupported edition 99997/, "unsupported edition input throws"); + + test.end(); +}); diff --git a/tests/data/google/protobuf/descriptor.proto b/tests/data/google/protobuf/descriptor.proto index 561d9629e..c023fce8a 100644 --- a/tests/data/google/protobuf/descriptor.proto +++ b/tests/data/google/protobuf/descriptor.proto @@ -36,15 +36,16 @@ // A valid .proto file can be translated directly to a FileDescriptorProto // without any other information (e.g. without reading its imports). - syntax = "proto2"; package google.protobuf; -option go_package = "descriptor"; + +option go_package = "google.golang.org/protobuf/types/descriptorpb"; option java_package = "com.google.protobuf"; option java_outer_classname = "DescriptorProtos"; option csharp_namespace = "Google.Protobuf.Reflection"; option objc_class_prefix = "GPB"; +option cc_enable_arenas = true; // descriptor.proto must be optimized for speed because reflection-based // algorithms don't work during bootstrapping. @@ -54,12 +55,55 @@ option optimize_for = SPEED; // files it parses. message FileDescriptorSet { repeated FileDescriptorProto file = 1; + + // Extensions for tooling. + extensions 536000000 [declaration = { + number: 536000000 + type: ".buf.descriptor.v1.FileDescriptorSetExtension" + full_name: ".buf.descriptor.v1.buf_file_descriptor_set_extension" + }]; +} + +// The full set of known editions. +enum Edition { + // A placeholder for an unknown edition value. + EDITION_UNKNOWN = 0; + + // A placeholder edition for specifying default behaviors *before* a feature + // was first introduced. This is effectively an "infinite past". + EDITION_LEGACY = 900; + + // Legacy syntax "editions". These pre-date editions, but behave much like + // distinct editions. These can't be used to specify the edition of proto + // files, but feature definitions must supply proto2/proto3 defaults for + // backwards compatibility. + EDITION_PROTO2 = 998; + EDITION_PROTO3 = 999; + + // Editions that have been released. The specific values are arbitrary and + // should not be depended on, but they will always be time-ordered for easy + // comparison. + EDITION_2023 = 1000; + EDITION_2024 = 1001; + + // Placeholder editions for testing feature resolution. These should not be + // used or relied on outside of tests. + EDITION_1_TEST_ONLY = 1; + EDITION_2_TEST_ONLY = 2; + EDITION_99997_TEST_ONLY = 99997; + EDITION_99998_TEST_ONLY = 99998; + EDITION_99999_TEST_ONLY = 99999; + + // Placeholder for specifying unbounded edition support. This should only + // ever be used by plugins that can expect to never require any changes to + // support a new edition. + EDITION_MAX = 0x7FFFFFFF; } // Describes a complete .proto file. message FileDescriptorProto { - optional string name = 1; // file name, relative to root of source tree - optional string package = 2; // e.g. "foo", "foo.bar", etc. + optional string name = 1; // file name, relative to root of source tree + optional string package = 2; // e.g. "foo", "foo.bar", etc. // Names of files imported by this file. repeated string dependency = 3; @@ -84,8 +128,19 @@ message FileDescriptorProto { optional SourceCodeInfo source_code_info = 9; // The syntax of the proto file. - // The supported values are "proto2" and "proto3". + // The supported values are "proto2", "proto3", and "editions". + // + // If `edition` is present, this value must be "editions". + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. optional string syntax = 12; + + // The edition of the proto file. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional Edition edition = 14; } // Describes a message type. @@ -99,8 +154,8 @@ message DescriptorProto { repeated EnumDescriptorProto enum_type = 4; message ExtensionRange { - optional int32 start = 1; - optional int32 end = 2; + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. optional ExtensionRangeOptions options = 3; } @@ -114,8 +169,8 @@ message DescriptorProto { // fields or extension ranges in the same message. Reserved ranges may // not overlap. message ReservedRange { - optional int32 start = 1; // Inclusive. - optional int32 end = 2; // Exclusive. + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. } repeated ReservedRange reserved_range = 9; // Reserved field names, which may not be used by fields in the same message. @@ -123,7 +178,6 @@ message DescriptorProto { repeated string reserved_name = 10; } - message ExtensionRangeOptions { // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; @@ -158,6 +212,9 @@ message ExtensionRangeOptions { // used externally. repeated Declaration declaration = 2 [retention = RETENTION_SOURCE]; + // Any features defined in the specific edition. + optional FeatureSet features = 50; + // The verification state of the extension range. enum VerificationState { // All the extensions of the range must be declared. @@ -166,17 +223,12 @@ message ExtensionRangeOptions { } // The verification state of the range. - // TODO(b/278783756): flip the default to DECLARATION once all empty ranges + // TODO: flip the default to DECLARATION once all empty ranges // are marked as UNVERIFIED. optional VerificationState verification = 3 [default = UNVERIFIED, retention = RETENTION_SOURCE]; // Clients can define custom options in extensions of this message. See above. - // BEGIN GOOGLE-INTERNAL - // Extension numbers > 530,000,000 must be forward-declared in - // google3/third_party/protobuf/extdecl_extension_range_options.h. See - // go/extension-declaration-reserved-numbers for more information. - // END GOOGLE-INTERNAL extensions 1000 to max; } @@ -185,38 +237,46 @@ message FieldDescriptorProto { enum Type { // 0 is reserved for errors. // Order is weird for historical reasons. - TYPE_DOUBLE = 1; - TYPE_FLOAT = 2; + TYPE_DOUBLE = 1; + TYPE_FLOAT = 2; // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if // negative values are likely. - TYPE_INT64 = 3; - TYPE_UINT64 = 4; + TYPE_INT64 = 3; + TYPE_UINT64 = 4; // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if // negative values are likely. - TYPE_INT32 = 5; - TYPE_FIXED64 = 6; - TYPE_FIXED32 = 7; - TYPE_BOOL = 8; - TYPE_STRING = 9; - TYPE_GROUP = 10; // Tag-delimited aggregate. - TYPE_MESSAGE = 11; // Length-delimited aggregate. + TYPE_INT32 = 5; + TYPE_FIXED64 = 6; + TYPE_FIXED32 = 7; + TYPE_BOOL = 8; + TYPE_STRING = 9; + // Tag-delimited aggregate. + // Group type is deprecated and not supported after google.protobuf. However, Proto3 + // implementations should still be able to parse the group wire format and + // treat group fields as unknown fields. In Editions, the group wire format + // can be enabled via the `message_encoding` feature. + TYPE_GROUP = 10; + TYPE_MESSAGE = 11; // Length-delimited aggregate. // New in version 2. - TYPE_BYTES = 12; - TYPE_UINT32 = 13; - TYPE_ENUM = 14; - TYPE_SFIXED32 = 15; - TYPE_SFIXED64 = 16; - TYPE_SINT32 = 17; // Uses ZigZag encoding. - TYPE_SINT64 = 18; // Uses ZigZag encoding. - }; + TYPE_BYTES = 12; + TYPE_UINT32 = 13; + TYPE_ENUM = 14; + TYPE_SFIXED32 = 15; + TYPE_SFIXED64 = 16; + TYPE_SINT32 = 17; // Uses ZigZag encoding. + TYPE_SINT64 = 18; // Uses ZigZag encoding. + } enum Label { // 0 is reserved for errors - LABEL_OPTIONAL = 1; - LABEL_REQUIRED = 2; - LABEL_REPEATED = 3; - }; + LABEL_OPTIONAL = 1; + LABEL_REPEATED = 3; + // The required label is only allowed in google.protobuf. In proto3 and Editions + // it's explicitly prohibited. In Editions, the `field_presence` feature + // can be used to get this behavior. + LABEL_REQUIRED = 2; + } optional string name = 1; optional int32 number = 3; @@ -241,7 +301,6 @@ message FieldDescriptorProto { // For booleans, "true" or "false". // For strings, contains the default text contents (not escaped in any way). // For bytes, contains the C escaped value. All bytes >= 128 are escaped. - // TODO(kenton): Base-64 encode? optional string default_value = 7; // If set, gives the index of a oneof in the containing type's oneof_decl @@ -255,6 +314,29 @@ message FieldDescriptorProto { optional string json_name = 10; optional FieldOptions options = 8; + + // If true, this is a proto3 "optional". When a proto3 field is optional, it + // tracks presence regardless of field type. + // + // When proto3_optional is true, this field must belong to a oneof to signal + // to old proto3 clients that presence is tracked for this field. This oneof + // is known as a "synthetic" oneof, and this field must be its sole member + // (each proto3 optional field gets its own synthetic oneof). Synthetic oneofs + // exist in the descriptor only, and do not generate any API. Synthetic oneofs + // must be ordered after all "real" oneofs. + // + // For message fields, proto3_optional doesn't create any semantic change, + // since non-repeated message fields always track presence. However it still + // indicates the semantic detail of whether the user wrote "optional" or not. + // This can be useful for round-tripping the .proto file. For consistency we + // give message fields a synthetic oneof also, even though it is not required + // to track presence. This is especially important because the parser can't + // tell if a field is a message or an enum, so it must always create a + // synthetic oneof. + // + // Proto2 optional fields do not set this flag, because they already indicate + // optional with `LABEL_OPTIONAL`. + optional bool proto3_optional = 17; } // Describes a oneof. @@ -270,6 +352,26 @@ message EnumDescriptorProto { repeated EnumValueDescriptorProto value = 2; optional EnumOptions options = 3; + + // Range of reserved numeric values. Reserved values may not be used by + // entries in the same enum. Reserved ranges may not overlap. + // + // Note that this is distinct from DescriptorProto.ReservedRange in that it + // is inclusive such that it can appropriately represent the entire int32 + // domain. + message EnumReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Inclusive. + } + + // Range of reserved numeric values. Reserved numeric values may not be used + // by enum values in the same enum declaration. Reserved ranges may not + // overlap. + repeated EnumReservedRange reserved_range = 4; + + // Reserved enum value names, which may not be reused. A given name may only + // be reserved once. + repeated string reserved_name = 5; } // Describes a value within an enum. @@ -300,12 +402,11 @@ message MethodDescriptorProto { optional MethodOptions options = 4; // Identifies if client streams multiple client messages - optional bool client_streaming = 5 [default=false]; + optional bool client_streaming = 5 [default = false]; // Identifies if server streams multiple server messages - optional bool server_streaming = 6 [default=false]; + optional bool server_streaming = 6 [default = false]; } - // =================================================================== // Options @@ -338,7 +439,6 @@ message MethodDescriptorProto { // If this turns out to be popular, a web service will be set up // to automatically assign option numbers. - message FileOptions { // Sets the Java package where classes generated from this .proto will be @@ -347,42 +447,44 @@ message FileOptions { // domain names. optional string java_package = 1; - - // If set, all the classes from the .proto file are wrapped in a single - // outer class with the given name. This applies to both Proto1 - // (equivalent to the old "--one_java_file" option) and Proto2 (where - // a .proto always translates to a single class, but you may want to - // explicitly choose the class name). + // Controls the name of the wrapper Java class generated for the .proto file. + // That class will always contain the .proto file's getDescriptor() method as + // well as any top-level extensions defined in the .proto file. + // If java_multiple_files is disabled, then all the other classes from the + // .proto file will be nested inside the single wrapper outer class. optional string java_outer_classname = 8; - // If set true, then the Java code generator will generate a separate .java + // If enabled, then the Java code generator will generate a separate .java // file for each top-level message, enum, and service defined in the .proto - // file. Thus, these types will *not* be nested inside the outer class - // named by java_outer_classname. However, the outer class will still be + // file. Thus, these types will *not* be nested inside the wrapper class + // named by java_outer_classname. However, the wrapper class will still be // generated to contain the file's getDescriptor() method as well as any // top-level extensions defined in the file. - optional bool java_multiple_files = 10 [default=false]; + optional bool java_multiple_files = 10 [default = false]; // This option does nothing. optional bool java_generate_equals_and_hash = 20 [deprecated=true]; - // If set true, then the Java2 code generator will generate code that - // throws an exception whenever an attempt is made to assign a non-UTF-8 - // byte sequence to a string field. - // Message reflection will do the same. - // However, an extension field still accepts non-UTF-8 byte sequences. - // This option has no effect on when used with the lite runtime. - optional bool java_string_check_utf8 = 27 [default=false]; - + // A proto2 file can set this to true to opt in to UTF-8 checking for Java, + // which will throw an exception if invalid UTF-8 is parsed from the wire or + // assigned to a string field. + // + // TODO: clarify exactly what kinds of field types this option + // applies to, and update these docs accordingly. + // + // Proto3 files already perform these checks. Setting the option explicitly to + // false has no effect: it cannot be used to opt proto3 files out of UTF-8 + // checks. + optional bool java_string_check_utf8 = 27 [default = false]; // Generated classes can be optimized for speed or code size. enum OptimizeMode { - SPEED = 1; // Generate complete code for parsing, serialization, - // etc. - CODE_SIZE = 2; // Use ReflectionOps to implement these methods. - LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime. + SPEED = 1; // Generate complete code for parsing, serialization, + // etc. + CODE_SIZE = 2; // Use ReflectionOps to implement these methods. + LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime. } - optional OptimizeMode optimize_for = 9 [default=SPEED]; + optional OptimizeMode optimize_for = 9 [default = SPEED]; // Sets the Go package where structs generated from this .proto will be // placed. If omitted, the Go package will be derived from the following: @@ -391,8 +493,6 @@ message FileOptions { // - Otherwise, the basename of the .proto file, without extension. optional string go_package = 11; - - // Should generic services be generated in each language? "Generic" services // are not specific to any particular RPC system. They are generated by the // main code generators in each language (without additional plugins). @@ -403,20 +503,21 @@ message FileOptions { // that generate code specific to your particular RPC system. Therefore, // these default to false. Old code which depends on generic services should // explicitly set them to true. - optional bool cc_generic_services = 16 [default=false]; - optional bool java_generic_services = 17 [default=false]; - optional bool py_generic_services = 18 [default=false]; + optional bool cc_generic_services = 16 [default = false]; + optional bool java_generic_services = 17 [default = false]; + optional bool py_generic_services = 18 [default = false]; + reserved 42; // removed php_generic_services + reserved "php_generic_services"; // Is this file deprecated? // Depending on the target platform, this can emit Deprecated annotations // for everything in the file, or it will be completely ignored; in the very // least, this is a formalization for deprecating files. - optional bool deprecated = 23 [default=false]; + optional bool deprecated = 23 [default = false]; // Enables the use of arenas for the proto messages in this file. This applies // only to generated classes for C++. - optional bool cc_enable_arenas = 31 [default=false]; - + optional bool cc_enable_arenas = 31 [default = true]; // Sets the objective c class prefix which is prepended to all objective c // generated classes from this .proto. There is no default. @@ -425,10 +526,43 @@ message FileOptions { // Namespace for generated classes; defaults to the package. optional string csharp_namespace = 37; - // The parser stores options it doesn't recognize here. See above. + // By default Swift generators will take the proto package and CamelCase it + // replacing '.' with underscore and use that to prefix the types/symbols + // defined. When this options is provided, they will use this value instead + // to prefix the types/symbols defined. + optional string swift_prefix = 39; + + // Sets the php class prefix which is prepended to all php generated classes + // from this .proto. Default is empty. + optional string php_class_prefix = 40; + + // Use this option to change the namespace of php generated classes. Default + // is empty. When this option is empty, the package name will be used for + // determining the namespace. + optional string php_namespace = 41; + + // Use this option to change the namespace of php generated metadata classes. + // Default is empty. When this option is empty, the proto file name will be + // used for determining the namespace. + optional string php_metadata_namespace = 44; + + // Use this option to change the package of ruby generated classes. Default + // is empty. When this option is not set, the package name will be used for + // determining the ruby package. + optional string ruby_package = 45; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 50; + + // The parser stores options it doesn't recognize here. + // See the documentation for the "Options" section above. repeated UninterpretedOption uninterpreted_option = 999; - // Clients can define custom options in extensions of this message. See above. + // Clients can define custom options in extensions of this message. + // See the documentation for the "Options" section above. extensions 1000 to max; reserved 38; @@ -453,18 +587,20 @@ message MessageOptions { // // Because this is an option, the above two restrictions are not enforced by // the protocol compiler. - optional bool message_set_wire_format = 1 [default=false]; + optional bool message_set_wire_format = 1 [default = false]; // Disables the generation of the standard "descriptor()" accessor, which can // conflict with a field of the same name. This is meant to make migration // from proto1 easier; new code should avoid fields named "descriptor". - optional bool no_standard_descriptor_accessor = 2 [default=false]; + optional bool no_standard_descriptor_accessor = 2 [default = false]; // Is this message deprecated? // Depending on the target platform, this can emit Deprecated annotations // for the message, or it will be completely ignored; in the very least, // this is a formalization for deprecating messages. - optional bool deprecated = 3 [default=false]; + optional bool deprecated = 3 [default = false]; + + reserved 4, 5, 6; // Whether the message is an automatically generated map entry type for the // maps field. @@ -481,7 +617,7 @@ message MessageOptions { // // Implementations may choose not to generate the map_entry=true message, but // use a native map in the target language to hold the keys and values. - // The reflection APIs in such implementions still need to work as + // The reflection APIs in such implementations still need to work as // if the field is a repeated message field. // // NOTE: Do not set the option in .proto files. Always use the maps syntax @@ -490,7 +626,25 @@ message MessageOptions { optional bool map_entry = 7; reserved 8; // javalite_serializable + reserved 9; // javanano_as_lite + + // Enable the legacy handling of JSON field name conflicts. This lowercases + // and strips underscored from the fields before comparison in proto3 only. + // The new behavior takes `json_name` into account and applies to proto2 as + // well. + // + // This should only be used as a temporary measure against broken builds due + // to the change in behavior for JSON field name conflicts. + // + // TODO This is legacy behavior we plan to remove once downstream + // teams have had time to migrate. + optional bool deprecated_legacy_json_field_conflicts = 11 [deprecated = true]; + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 12; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; @@ -500,15 +654,24 @@ message MessageOptions { } message FieldOptions { + // NOTE: ctype is deprecated. Use `features.(pb.cpp).string_type` instead. // The ctype option instructs the C++ code generator to use a different // representation of the field than it normally would. See the specific - // options below. This option is not yet implemented in the open source - // release -- sorry, we'll try to include it in a future version! - optional CType ctype = 1 [default = STRING]; + // options below. This option is only implemented to support use of + // [ctype=CORD] and [ctype=STRING] (the default) on non-repeated fields of + // type "bytes" in the open source release. + // TODO: make ctype actually deprecated. + optional CType ctype = 1 [/*deprecated = true,*/ default = STRING]; enum CType { // Default mode. STRING = 0; + // The option [ctype=CORD] may be applied to a non-repeated field of type + // "bytes". It indicates that in C++, the data should be stored in a Cord + // instead of a string. For very large strings, this may reduce memory + // fragmentation. It may also allow better performance when parsing from a + // Cord, or when parsing with aliasing enabled, as the parsed Cord may then + // alias the original buffer. CORD = 1; STRING_PIECE = 2; @@ -517,18 +680,22 @@ message FieldOptions { // a more efficient representation on the wire. Rather than repeatedly // writing the tag and type for each element, the entire array is encoded as // a single length-delimited blob. In proto3, only explicit setting it to - // false will avoid using packed encoding. + // false will avoid using packed encoding. This option is prohibited in + // Editions, but the `repeated_field_encoding` feature can be used to control + // the behavior. optional bool packed = 2; // The jstype option determines the JavaScript type used for values of the // field. The option is permitted only for 64 bit integral and fixed types - // (int64, uint64, sint64, fixed64, sfixed64). By default these types are - // represented as JavaScript strings. This avoids loss of precision that can - // happen when a large value is converted to a floating point JavaScript - // numbers. Specifying JS_NUMBER for the jstype causes the generated - // JavaScript code to use the JavaScript "number" type instead of strings. - // This option is an enum to permit additional types to be added, - // e.g. goog.math.Integer. + // (int64, uint64, sint64, fixed64, sfixed64). A field with jstype JS_STRING + // is represented as JavaScript string, which avoids loss of precision that + // can happen when a large value is converted to a floating point JavaScript. + // Specifying JS_NUMBER for the jstype causes the generated JavaScript code to + // use the JavaScript "number" type. The behavior of the default option + // JS_NORMAL is implementation dependent. + // + // This option is an enum to permit additional types to be added, e.g. + // goog.math.Integer. optional JSType jstype = 6 [default = JS_NORMAL]; enum JSType { // Use the default type. @@ -558,28 +725,91 @@ message FieldOptions { // call from multiple threads concurrently, while non-const methods continue // to require exclusive access. // - // - // Note that implementations may choose not to check required fields within - // a lazy sub-message. That is, calling IsInitialized() on the outer message - // may return true even if the inner message has missing required fields. - // This is necessary because otherwise the inner message would have to be - // parsed in order to perform the check, defeating the purpose of lazy - // parsing. An implementation which chooses not to check required fields - // must be consistent about it. That is, for any particular sub-message, the - // implementation must either *always* check its required fields, or *never* - // check its required fields, regardless of whether or not the message has - // been parsed. - optional bool lazy = 5 [default=false]; + // Note that lazy message fields are still eagerly verified to check + // ill-formed wireformat or missing required fields. Calling IsInitialized() + // on the outer message would fail if the inner message has missing required + // fields. Failed verification would result in parsing failure (except when + // uninitialized messages are acceptable). + optional bool lazy = 5 [default = false]; + + // unverified_lazy does no correctness checks on the byte stream. This should + // only be used where lazy with verification is prohibitive for performance + // reasons. + optional bool unverified_lazy = 15 [default = false]; // Is this field deprecated? // Depending on the target platform, this can emit Deprecated annotations // for accessors, or it will be completely ignored; in the very least, this // is a formalization for deprecating fields. - optional bool deprecated = 3 [default=false]; + optional bool deprecated = 3 [default = false]; // For Google-internal migration only. Do not use. - optional bool weak = 10 [default=false]; + optional bool weak = 10 [default = false]; + + // Indicate that the field value should not be printed out when using debug + // formats, e.g. when the field contains sensitive credentials. + optional bool debug_redact = 16 [default = false]; + + // If set to RETENTION_SOURCE, the option will be omitted from the binary. + enum OptionRetention { + RETENTION_UNKNOWN = 0; + RETENTION_RUNTIME = 1; + RETENTION_SOURCE = 2; + } + + optional OptionRetention retention = 17; + + // This indicates the types of entities that the field may apply to when used + // as an option. If it is unset, then the field may be freely used as an + // option on any kind of entity. + enum OptionTargetType { + TARGET_TYPE_UNKNOWN = 0; + TARGET_TYPE_FILE = 1; + TARGET_TYPE_EXTENSION_RANGE = 2; + TARGET_TYPE_MESSAGE = 3; + TARGET_TYPE_FIELD = 4; + TARGET_TYPE_ONEOF = 5; + TARGET_TYPE_ENUM = 6; + TARGET_TYPE_ENUM_ENTRY = 7; + TARGET_TYPE_SERVICE = 8; + TARGET_TYPE_METHOD = 9; + } + repeated OptionTargetType targets = 19; + + message EditionDefault { + optional Edition edition = 3; + optional string value = 2; // Textproto value. + } + repeated EditionDefault edition_defaults = 20; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 21; + + // Information about the support window of a feature. + message FeatureSupport { + // The edition that this feature was first available in. In editions + // earlier than this one, the default assigned to EDITION_LEGACY will be + // used, and proto files will not be able to override it. + optional Edition edition_introduced = 1; + + // The edition this feature becomes deprecated in. Using this after this + // edition may trigger warnings. + optional Edition edition_deprecated = 2; + + // The deprecation warning text if this feature is used after the edition it + // was marked deprecated in. + optional string deprecation_warning = 3; + + // The edition this feature is no longer available in. In editions after + // this one, the last default assigned will be used, and proto files will + // not be able to override it. + optional Edition edition_removed = 4; + } + optional FeatureSupport feature_support = 22; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; @@ -587,10 +817,17 @@ message FieldOptions { // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; - reserved 4; // removed jtype + reserved 4; // removed jtype + reserved 18; // reserve target, target_obsolete_do_not_use } message OneofOptions { + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 1; + // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; @@ -608,8 +845,23 @@ message EnumOptions { // Depending on the target platform, this can emit Deprecated annotations // for the enum, or it will be completely ignored; in the very least, this // is a formalization for deprecating enums. - optional bool deprecated = 3 [default=false]; + optional bool deprecated = 3 [default = false]; + + reserved 5; // javanano_as_lite + // Enable the legacy handling of JSON field name conflicts. This lowercases + // and strips underscored from the fields before comparison in proto3 only. + // The new behavior takes `json_name` into account and applies to proto2 as + // well. + // TODO Remove this legacy behavior once downstream teams have + // had time to migrate. + optional bool deprecated_legacy_json_field_conflicts = 6 [deprecated = true]; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 7; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; @@ -623,7 +875,21 @@ message EnumValueOptions { // Depending on the target platform, this can emit Deprecated annotations // for the enum value, or it will be completely ignored; in the very least, // this is a formalization for deprecating enum values. - optional bool deprecated = 1 [default=false]; + optional bool deprecated = 1 [default = false]; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 2; + + // Indicate that fields annotated with this enum value should not be printed + // out when using debug formats, e.g. when the field contains sensitive + // credentials. + optional bool debug_redact = 3 [default = false]; + + // Information about the support window of a feature value. + optional FieldOptions.FeatureSupport feature_support = 4; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; @@ -634,6 +900,12 @@ message EnumValueOptions { message ServiceOptions { + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 34; + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC // framework. We apologize for hoarding these numbers to ourselves, but // we were already using them long before we decided to release Protocol @@ -643,7 +915,7 @@ message ServiceOptions { // Depending on the target platform, this can emit Deprecated annotations // for the service, or it will be completely ignored; in the very least, // this is a formalization for deprecating services. - optional bool deprecated = 33 [default=false]; + optional bool deprecated = 33 [default = false]; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; @@ -663,18 +935,24 @@ message MethodOptions { // Depending on the target platform, this can emit Deprecated annotations // for the method, or it will be completely ignored; in the very least, // this is a formalization for deprecating methods. - optional bool deprecated = 33 [default=false]; + optional bool deprecated = 33 [default = false]; // Is this method side-effect-free (or safe in HTTP parlance), or idempotent, // or neither? HTTP based RPC implementation may choose GET verb for safe // methods, and PUT verb for idempotent methods instead of the default POST. enum IdempotencyLevel { IDEMPOTENCY_UNKNOWN = 0; - NO_SIDE_EFFECTS = 1; // implies idempotent - IDEMPOTENT = 2; // idempotent, but may have side effects + NO_SIDE_EFFECTS = 1; // implies idempotent + IDEMPOTENT = 2; // idempotent, but may have side effects } - optional IdempotencyLevel idempotency_level = - 34 [default=IDEMPOTENCY_UNKNOWN]; + optional IdempotencyLevel idempotency_level = 34 + [default = IDEMPOTENCY_UNKNOWN]; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 35; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; @@ -683,7 +961,6 @@ message MethodOptions { extensions 1000 to max; } - // A message representing a option the parser does not recognize. This only // appears in options protos created by the compiler::Parser class. // DescriptorPool resolves these when building Descriptor objects. Therefore, @@ -694,8 +971,8 @@ message UninterpretedOption { // The name of the uninterpreted option. Each string represents a segment in // a dot-separated name. is_extension is true iff a segment represents an // extension (denoted with parentheses in options specs in .proto files). - // E.g.,{ ["foo", false], ["bar.baz", true], ["qux", false] } represents - // "foo.(bar.baz).qux". + // E.g.,{ ["foo", false], ["bar.baz", true], ["moo", false] } represents + // "foo.(bar.baz).moo". message NamePart { required string name_part = 1; required bool is_extension = 2; @@ -712,6 +989,129 @@ message UninterpretedOption { optional string aggregate_value = 8; } +// =================================================================== +// Features + +// TODO Enums in C++ gencode (and potentially other languages) are +// not well scoped. This means that each of the feature enums below can clash +// with each other. The short names we've chosen maximize call-site +// readability, but leave us very open to this scenario. A future feature will +// be designed and implemented to handle this, hopefully before we ever hit a +// conflict here. +message FeatureSet { + enum FieldPresence { + FIELD_PRESENCE_UNKNOWN = 0; + EXPLICIT = 1; + IMPLICIT = 2; + LEGACY_REQUIRED = 3; + } + optional FieldPresence field_presence = 1; + + enum EnumType { + ENUM_TYPE_UNKNOWN = 0; + OPEN = 1; + CLOSED = 2; + } + optional EnumType enum_type = 2; + + enum RepeatedFieldEncoding { + REPEATED_FIELD_ENCODING_UNKNOWN = 0; + PACKED = 1; + EXPANDED = 2; + } + optional RepeatedFieldEncoding repeated_field_encoding = 3; + + enum Utf8Validation { + UTF8_VALIDATION_UNKNOWN = 0; + VERIFY = 2; + NONE = 3; + reserved 1; + } + optional Utf8Validation utf8_validation = 4; + + enum MessageEncoding { + MESSAGE_ENCODING_UNKNOWN = 0; + LENGTH_PREFIXED = 1; + DELIMITED = 2; + } + optional MessageEncoding message_encoding = 5; + + enum JsonFormat { + JSON_FORMAT_UNKNOWN = 0; + ALLOW = 1; + LEGACY_BEST_EFFORT = 2; + } + optional JsonFormat json_format = 6; + + enum EnforceNamingStyle { + ENFORCE_NAMING_STYLE_UNKNOWN = 0; + STYLE2024 = 1; + STYLE_LEGACY = 2; + } + optional EnforceNamingStyle enforce_naming_style = 7; + + reserved 999; + + extensions 1000 to 9994 [ + declaration = { + number: 1000, + full_name: ".pb.cpp", + type: ".pb.CppFeatures" + }, + declaration = { + number: 1001, + full_name: ".pb.java", + type: ".pb.JavaFeatures" + }, + declaration = { number: 1002, full_name: ".pb.go", type: ".pb.GoFeatures" }, + declaration = { + number: 1003, + full_name: ".pb.python", + type: ".pb.PythonFeatures" + }, + declaration = { + number: 9990, + full_name: ".pb.proto1", + type: ".pb.Proto1Features" + } + ]; + + extensions 9995 to 9999; // For internal testing + extensions 10000; // for https://github.com/bufbuild/protobuf-es +} + +// A compiled specification for the defaults of a set of features. These +// messages are generated from FeatureSet extensions and can be used to seed +// feature resolution. The resolution with this object becomes a simple search +// for the closest matching edition, followed by proto merges. +message FeatureSetDefaults { + // A map from every known edition with a unique set of defaults to its + // defaults. Not all editions may be contained here. For a given edition, + // the defaults at the closest matching edition ordered at or before it should + // be used. This field must be in strict ascending order by edition. + message FeatureSetEditionDefault { + optional Edition edition = 3; + + // Defaults of features that can be overridden in this edition. + optional FeatureSet overridable_features = 4; + + // Defaults of features that can't be overridden in this edition. + optional FeatureSet fixed_features = 5; + + reserved 1, 2; + reserved "features"; + } + repeated FeatureSetEditionDefault defaults = 1; + + // The minimum supported edition (inclusive) when this was constructed. + // Editions before this will not have defaults. + optional Edition minimum_edition = 4; + + // The maximum known edition (inclusive) when this was constructed. Editions + // after this will not have reliable defaults. + optional Edition maximum_edition = 5; +} + // =================================================================== // Optional source code info @@ -755,7 +1155,7 @@ message SourceCodeInfo { // beginning of the "extend" block and is shared by all extensions within // the block. // - Just because a location's span is a subset of some other location's span - // does not mean that it is a descendent. For example, a "group" defines + // does not mean that it is a descendant. For example, a "group" defines // both a type and a field in a single declaration. Thus, the locations // corresponding to the type and field and their components will overlap. // - Code which tries to interpret locations should probably be designed to @@ -767,8 +1167,8 @@ message SourceCodeInfo { // location. // // Each element is a field number or an index. They form a path from - // the root FileDescriptorProto to the place where the definition. For - // example, this path: + // the root FileDescriptorProto to the place where the definition appears. + // For example, this path: // [ 4, 3, 2, 7, 1 ] // refers to: // file.message_type(3) // 4, 3 @@ -786,14 +1186,14 @@ message SourceCodeInfo { // [ 4, 3, 2, 7 ] // this path refers to the whole field declaration (from the beginning // of the label to the terminating semicolon). - repeated int32 path = 1 [packed=true]; + repeated int32 path = 1 [packed = true]; // Always has exactly three or four elements: start line, start column, // end line (optional, otherwise assumed same as start line), end column. // These are packed into a single field for efficiency. Note that line // and column numbers are zero-based -- typically you will want to add // 1 to each before displaying to a user. - repeated int32 span = 2 [packed=true]; + repeated int32 span = 2 [packed = true]; // If this SourceCodeInfo represents a complete declaration, these are any // comments appearing before and after the declaration which appear to be @@ -822,13 +1222,13 @@ message SourceCodeInfo { // // Comment attached to baz. // // Another line attached to baz. // - // // Comment attached to qux. + // // Comment attached to moo. // // - // // Another line attached to qux. - // optional double qux = 4; + // // Another line attached to moo. + // optional double moo = 4; // // // Detached comment for corge. This is not leading or trailing comments - // // to qux or corge because there are blank lines separating it from + // // to moo or corge because there are blank lines separating it from // // both. // // // Detached comment for corge paragraph 2. @@ -846,6 +1246,13 @@ message SourceCodeInfo { optional string trailing_comments = 4; repeated string leading_detached_comments = 6; } + + // Extensions for tooling. + extensions 536000000 [declaration = { + number: 536000000 + type: ".buf.descriptor.v1.SourceCodeInfoExtension" + full_name: ".buf.descriptor.v1.buf_source_code_info_extension" + }]; } // Describes the relationship between generated code and its original source @@ -858,7 +1265,7 @@ message GeneratedCodeInfo { message Annotation { // Identifies the element in the original source .proto file. This field // is formatted the same as SourceCodeInfo.Location.path. - repeated int32 path = 1 [packed=true]; + repeated int32 path = 1 [packed = true]; // Identifies the filesystem path to the original source .proto. optional string source_file = 2; @@ -868,8 +1275,20 @@ message GeneratedCodeInfo { optional int32 begin = 3; // Identifies the ending offset in bytes in the generated code that - // relates to the identified offset. The end offset should be one past + // relates to the identified object. The end offset should be one past // the last relevant byte (so the length of the text = end - begin). optional int32 end = 4; + + // Represents the identified object's effect on the element in the original + // .proto file. + enum Semantic { + // There is no effect or the effect is indescribable. + NONE = 0; + // The element is set or otherwise mutated. + SET = 1; + // An alias to the element is returned. + ALIAS = 2; + } + optional Semantic semantic = 5; } -} +} \ No newline at end of file From 11b29b5da55b26d0a48b68462dcce1ac57e727cf Mon Sep 17 00:00:00 2001 From: Mike Kruskal <62662355+mkruskal-google@users.noreply.github.com> Date: Tue, 27 May 2025 12:47:42 -0700 Subject: [PATCH 4/6] lint fixes --- ext/descriptor/index.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ext/descriptor/index.js b/ext/descriptor/index.js index 1ea3ec800..37cad97a9 100644 --- a/ext/descriptor/index.js +++ b/ext/descriptor/index.js @@ -213,9 +213,9 @@ var unnamedMessageIndex = 0; /** * Creates a type from a descriptor. - * + * * Warning: this is not safe to use with editions protos, since it discards relevant file context. - * + * * @param {IDescriptorProto|Reader|Uint8Array} descriptor Descriptor * @param {string} [edition="proto2"] The syntax or edition to use * @param {boolean} [nested=false] Whether or not this is a nested object @@ -400,7 +400,7 @@ var numberRe = /^(?![eE])[0-9]*(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?$/; * Creates a field from a descriptor. * * Warning: this is not safe to use with editions protos, since it discards relevant file context. - * + * * @param {IFieldDescriptorProto|Reader|Uint8Array} descriptor Descriptor * @param {string} [edition="proto2"] The syntax or edition to use * @param {boolean} [nested=false] Whether or not this is a top-level object @@ -571,7 +571,7 @@ var unnamedEnumIndex = 0; * Creates an enum from a descriptor. * * Warning: this is not safe to use with editions protos, since it discards relevant file context. - * + * * @param {IEnumDescriptorProto|Reader|Uint8Array} descriptor Descriptor * @param {string} [edition="proto2"] The syntax or edition to use * @param {boolean} [nested=false] Whether or not this is a top-level object @@ -637,7 +637,7 @@ var unnamedOneofIndex = 0; * Creates a oneof from a descriptor. * * Warning: this is not safe to use with editions protos, since it discards relevant file context. - * + * * @param {IOneofDescriptorProto|Reader|Uint8Array} descriptor Descriptor * @returns {OneOf} OneOf instance */ @@ -687,7 +687,7 @@ var unnamedServiceIndex = 0; * Creates a service from a descriptor. * * Warning: this is not safe to use with editions protos, since it discards relevant file context. - * + * * @param {IServiceDescriptorProto|Reader|Uint8Array} descriptor Descriptor * @param {string} [edition="proto2"] The syntax or edition to use * @param {boolean} [nested=false] Whether or not this is a top-level object @@ -744,7 +744,7 @@ Service.prototype.toDescriptor = function toDescriptor() { * Properties of a MethodOptions message. * * Warning: this is not safe to use with editions protos, since it discards relevant file context. - * + * * @interface IMethodOptions * @property {boolean} [deprecated] */ From a48bd5b799956daeb55b9ebd5d73a717d2ce61e8 Mon Sep 17 00:00:00 2001 From: Mike Kruskal <62662355+mkruskal-google@users.noreply.github.com> Date: Tue, 27 May 2025 15:02:54 -0700 Subject: [PATCH 5/6] update descriptor.proto to latest version --- google/protobuf/descriptor.json | 120 ++++++++++++++-- google/protobuf/descriptor.proto | 41 ++++-- tests/data/google/protobuf/descriptor.proto | 145 ++++++++++++++++++-- 3 files changed, 282 insertions(+), 24 deletions(-) diff --git a/google/protobuf/descriptor.json b/google/protobuf/descriptor.json index 20bcd7add..215e003d0 100644 --- a/google/protobuf/descriptor.json +++ b/google/protobuf/descriptor.json @@ -72,6 +72,11 @@ "type": "int32", "id": 11 }, + "optionDependency": { + "rule": "repeated", + "type": "string", + "id": 15 + }, "messageType": { "rule": "repeated", "type": "DescriptorProto", @@ -160,6 +165,10 @@ "rule": "repeated", "type": "string", "id": 10 + }, + "visibility": { + "type": "SymbolVisibility", + "id": 11 } }, "nested": { @@ -385,6 +394,10 @@ "rule": "repeated", "type": "string", "id": 5 + }, + "visibility": { + "type": "SymbolVisibility", + "id": 6 } }, "nested": { @@ -691,7 +704,10 @@ }, "weak": { "type": "bool", - "id": 10 + "id": 10, + "options": { + "deprecated": true + } }, "debugRedact": { "type": "bool", @@ -1023,31 +1039,91 @@ "fields": { "fieldPresence": { "type": "FieldPresence", - "id": 1 + "id": 1, + "options": { + "retention": "RETENTION_RUNTIME", + "targets": "TARGET_TYPE_FILE", + "feature_support.edition_introduced": "EDITION_2023", + "edition_defaults.edition": "EDITION_2023", + "edition_defaults.value": "EXPLICIT" + } }, "enumType": { "type": "EnumType", - "id": 2 + "id": 2, + "options": { + "retention": "RETENTION_RUNTIME", + "targets": "TARGET_TYPE_FILE", + "feature_support.edition_introduced": "EDITION_2023", + "edition_defaults.edition": "EDITION_PROTO3", + "edition_defaults.value": "OPEN" + } }, "repeatedFieldEncoding": { "type": "RepeatedFieldEncoding", - "id": 3 + "id": 3, + "options": { + "retention": "RETENTION_RUNTIME", + "targets": "TARGET_TYPE_FILE", + "feature_support.edition_introduced": "EDITION_2023", + "edition_defaults.edition": "EDITION_PROTO3", + "edition_defaults.value": "PACKED" + } }, "utf8Validation": { "type": "Utf8Validation", - "id": 4 + "id": 4, + "options": { + "retention": "RETENTION_RUNTIME", + "targets": "TARGET_TYPE_FILE", + "feature_support.edition_introduced": "EDITION_2023", + "edition_defaults.edition": "EDITION_PROTO3", + "edition_defaults.value": "VERIFY" + } }, "messageEncoding": { "type": "MessageEncoding", - "id": 5 + "id": 5, + "options": { + "retention": "RETENTION_RUNTIME", + "targets": "TARGET_TYPE_FILE", + "feature_support.edition_introduced": "EDITION_2023", + "edition_defaults.edition": "EDITION_LEGACY", + "edition_defaults.value": "LENGTH_PREFIXED" + } }, "jsonFormat": { "type": "JsonFormat", - "id": 6 + "id": 6, + "options": { + "retention": "RETENTION_RUNTIME", + "targets": "TARGET_TYPE_FILE", + "feature_support.edition_introduced": "EDITION_2023", + "edition_defaults.edition": "EDITION_PROTO3", + "edition_defaults.value": "ALLOW" + } }, "enforceNamingStyle": { "type": "EnforceNamingStyle", - "id": 7 + "id": 7, + "options": { + "retention": "RETENTION_SOURCE", + "targets": "TARGET_TYPE_METHOD", + "feature_support.edition_introduced": "EDITION_2024", + "edition_defaults.edition": "EDITION_2024", + "edition_defaults.value": "STYLE2024" + } + }, + "defaultSymbolVisibility": { + "type": "VisibilityFeature.DefaultSymbolVisibility", + "id": 8, + "options": { + "retention": "RETENTION_SOURCE", + "targets": "TARGET_TYPE_FILE", + "feature_support.edition_introduced": "EDITION_2024", + "edition_defaults.edition": "EDITION_2024", + "edition_defaults.value": "EXPORT_TOP_LEVEL" + } } }, "extensions": [ @@ -1120,6 +1196,26 @@ "STYLE2024": 1, "STYLE_LEGACY": 2 } + }, + "VisibilityFeature": { + "fields": {}, + "reserved": [ + [ + 1, + 536870911 + ] + ], + "nested": { + "DefaultSymbolVisibility": { + "values": { + "DEFAULT_SYMBOL_VISIBILITY_UNKNOWN": 0, + "EXPORT_ALL": 1, + "EXPORT_TOP_LEVEL": 2, + "LOCAL_ALL": 3, + "STRICT": 4 + } + } + } } } }, @@ -1269,6 +1365,14 @@ } } } + }, + "SymbolVisibility": { + "edition": "proto2", + "values": { + "VISIBILITY_UNSET": 0, + "VISIBILITY_LOCAL": 1, + "VISIBILITY_EXPORT": 2 + } } } } diff --git a/google/protobuf/descriptor.proto b/google/protobuf/descriptor.proto index 5d4e5ce8f..675ea796e 100644 --- a/google/protobuf/descriptor.proto +++ b/google/protobuf/descriptor.proto @@ -40,6 +40,7 @@ message FileDescriptorProto { repeated string dependency = 3; repeated int32 public_dependency = 10; repeated int32 weak_dependency = 11; + repeated string option_dependency = 15; repeated DescriptorProto message_type = 4; repeated EnumDescriptorProto enum_type = 5; repeated ServiceDescriptorProto service = 6; @@ -62,6 +63,7 @@ message DescriptorProto { optional MessageOptions options = 7; repeated ReservedRange reserved_range = 9; repeated string reserved_name = 10; + optional SymbolVisibility visibility = 11; message ExtensionRange { @@ -161,6 +163,7 @@ message EnumDescriptorProto { optional EnumOptions options = 3; repeated EnumReservedRange reserved_range = 4; repeated string reserved_name = 5; + optional SymbolVisibility visibility = 6; message EnumReservedRange { @@ -252,7 +255,7 @@ message FieldOptions { optional bool lazy = 5; optional bool unverified_lazy = 15; optional bool deprecated = 3; - optional bool weak = 10; + optional bool weak = 10 [deprecated=true]; optional bool debug_redact = 16; optional OptionRetention retention = 17; repeated OptionTargetType targets = 19; @@ -392,13 +395,14 @@ message UninterpretedOption { message FeatureSet { - optional FieldPresence field_presence = 1; - optional EnumType enum_type = 2; - optional RepeatedFieldEncoding repeated_field_encoding = 3; - optional Utf8Validation utf8_validation = 4; - optional MessageEncoding message_encoding = 5; - optional JsonFormat json_format = 6; - optional EnforceNamingStyle enforce_naming_style = 7; + optional FieldPresence field_presence = 1 [retention="RETENTION_RUNTIME", targets="TARGET_TYPE_FILE", feature_support.edition_introduced="EDITION_2023", edition_defaults.edition="EDITION_2023", edition_defaults.value="EXPLICIT"]; + optional EnumType enum_type = 2 [retention="RETENTION_RUNTIME", targets="TARGET_TYPE_FILE", feature_support.edition_introduced="EDITION_2023", edition_defaults.edition="EDITION_PROTO3", edition_defaults.value="OPEN"]; + optional RepeatedFieldEncoding repeated_field_encoding = 3 [retention="RETENTION_RUNTIME", targets="TARGET_TYPE_FILE", feature_support.edition_introduced="EDITION_2023", edition_defaults.edition="EDITION_PROTO3", edition_defaults.value="PACKED"]; + optional Utf8Validation utf8_validation = 4 [retention="RETENTION_RUNTIME", targets="TARGET_TYPE_FILE", feature_support.edition_introduced="EDITION_2023", edition_defaults.edition="EDITION_PROTO3", edition_defaults.value="VERIFY"]; + optional MessageEncoding message_encoding = 5 [retention="RETENTION_RUNTIME", targets="TARGET_TYPE_FILE", feature_support.edition_introduced="EDITION_2023", edition_defaults.edition="EDITION_LEGACY", edition_defaults.value="LENGTH_PREFIXED"]; + optional JsonFormat json_format = 6 [retention="RETENTION_RUNTIME", targets="TARGET_TYPE_FILE", feature_support.edition_introduced="EDITION_2023", edition_defaults.edition="EDITION_PROTO3", edition_defaults.value="ALLOW"]; + optional EnforceNamingStyle enforce_naming_style = 7 [retention="RETENTION_SOURCE", targets="TARGET_TYPE_METHOD", feature_support.edition_introduced="EDITION_2024", edition_defaults.edition="EDITION_2024", edition_defaults.value="STYLE2024"]; + optional VisibilityFeature.DefaultSymbolVisibility default_symbol_visibility = 8 [retention="RETENTION_SOURCE", targets="TARGET_TYPE_FILE", feature_support.edition_introduced="EDITION_2024", edition_defaults.edition="EDITION_2024", edition_defaults.value="EXPORT_TOP_LEVEL"]; enum FieldPresence { @@ -450,6 +454,20 @@ message FeatureSet { STYLE_LEGACY = 2; } + message VisibilityFeature { + + enum DefaultSymbolVisibility { + + DEFAULT_SYMBOL_VISIBILITY_UNKNOWN = 0; + EXPORT_ALL = 1; + EXPORT_TOP_LEVEL = 2; + LOCAL_ALL = 3; + STRICT = 4; + } + + reserved 1 to max; + } + extensions 1000 to 9994, 9995 to 9999, 10000; reserved 999; @@ -506,4 +524,11 @@ message GeneratedCodeInfo { ALIAS = 2; } } +} + +enum SymbolVisibility { + + VISIBILITY_UNSET = 0; + VISIBILITY_LOCAL = 1; + VISIBILITY_EXPORT = 2; } \ No newline at end of file diff --git a/tests/data/google/protobuf/descriptor.proto b/tests/data/google/protobuf/descriptor.proto index c023fce8a..cf9a9f186 100644 --- a/tests/data/google/protobuf/descriptor.proto +++ b/tests/data/google/protobuf/descriptor.proto @@ -113,6 +113,10 @@ message FileDescriptorProto { // For Google-internal migration only. Do not use. repeated int32 weak_dependency = 11; + // Names of files imported by this file purely for the purpose of providing + // option extensions. These are excluded from the dependency list above. + repeated string option_dependency = 15; + // All top-level definitions in this file. repeated DescriptorProto message_type = 4; repeated EnumDescriptorProto enum_type = 5; @@ -176,6 +180,9 @@ message DescriptorProto { // Reserved field names, which may not be used by fields in the same message. // A given name may only be reserved once. repeated string reserved_name = 10; + + // Support for `export` and `local` keywords on enums. + optional SymbolVisibility visibility = 11; } message ExtensionRangeOptions { @@ -372,6 +379,9 @@ message EnumDescriptorProto { // Reserved enum value names, which may not be reused. A given name may only // be reserved once. repeated string reserved_name = 5; + + // Support for `export` and `local` keywords on enums. + optional SymbolVisibility visibility = 6; } // Describes a value within an enum. @@ -743,8 +753,9 @@ message FieldOptions { // is a formalization for deprecating fields. optional bool deprecated = 3 [default = false]; + // DEPRECATED. DO NOT USE! // For Google-internal migration only. Do not use. - optional bool weak = 10 [default = false]; + optional bool weak = 10 [default = false, deprecated = true]; // Indicate that the field value should not be printed out when using debug // formats, e.g. when the field contains sensitive credentials. @@ -1005,21 +1016,49 @@ message FeatureSet { IMPLICIT = 2; LEGACY_REQUIRED = 3; } - optional FieldPresence field_presence = 1; + optional FieldPresence field_presence = 1 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "EXPLICIT" }, + edition_defaults = { edition: EDITION_PROTO3, value: "IMPLICIT" }, + edition_defaults = { edition: EDITION_2023, value: "EXPLICIT" } + ]; enum EnumType { ENUM_TYPE_UNKNOWN = 0; OPEN = 1; CLOSED = 2; } - optional EnumType enum_type = 2; + optional EnumType enum_type = 2 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "CLOSED" }, + edition_defaults = { edition: EDITION_PROTO3, value: "OPEN" } + ]; enum RepeatedFieldEncoding { REPEATED_FIELD_ENCODING_UNKNOWN = 0; PACKED = 1; EXPANDED = 2; } - optional RepeatedFieldEncoding repeated_field_encoding = 3; + optional RepeatedFieldEncoding repeated_field_encoding = 3 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "EXPANDED" }, + edition_defaults = { edition: EDITION_PROTO3, value: "PACKED" } + ]; enum Utf8Validation { UTF8_VALIDATION_UNKNOWN = 0; @@ -1027,28 +1066,102 @@ message FeatureSet { NONE = 3; reserved 1; } - optional Utf8Validation utf8_validation = 4; + optional Utf8Validation utf8_validation = 4 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "NONE" }, + edition_defaults = { edition: EDITION_PROTO3, value: "VERIFY" } + ]; enum MessageEncoding { MESSAGE_ENCODING_UNKNOWN = 0; LENGTH_PREFIXED = 1; DELIMITED = 2; } - optional MessageEncoding message_encoding = 5; + optional MessageEncoding message_encoding = 5 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "LENGTH_PREFIXED" } + ]; enum JsonFormat { JSON_FORMAT_UNKNOWN = 0; ALLOW = 1; LEGACY_BEST_EFFORT = 2; } - optional JsonFormat json_format = 6; + optional JsonFormat json_format = 6 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_MESSAGE, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "LEGACY_BEST_EFFORT" }, + edition_defaults = { edition: EDITION_PROTO3, value: "ALLOW" } + ]; enum EnforceNamingStyle { ENFORCE_NAMING_STYLE_UNKNOWN = 0; STYLE2024 = 1; STYLE_LEGACY = 2; } - optional EnforceNamingStyle enforce_naming_style = 7; + optional EnforceNamingStyle enforce_naming_style = 7 [ + retention = RETENTION_SOURCE, + targets = TARGET_TYPE_FILE, + targets = TARGET_TYPE_EXTENSION_RANGE, + targets = TARGET_TYPE_MESSAGE, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_ONEOF, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_ENUM_ENTRY, + targets = TARGET_TYPE_SERVICE, + targets = TARGET_TYPE_METHOD, + feature_support = { + edition_introduced: EDITION_2024, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "STYLE_LEGACY" }, + edition_defaults = { edition: EDITION_2024, value: "STYLE2024" } + ]; + + message VisibilityFeature { + enum DefaultSymbolVisibility { + DEFAULT_SYMBOL_VISIBILITY_UNKNOWN = 0; + + // Default pre-EDITION_2024, all UNSET visibility are export. + EXPORT_ALL = 1; + + // All top-level symbols default to export, nested default to local. + EXPORT_TOP_LEVEL = 2; + + // All symbols default to local. + LOCAL_ALL = 3; + + // All symbols local by default. Nested types cannot be exported. + // With special case caveat for message { enum {} reserved 1 to max; } + // This is the recommended setting for new protos. + STRICT = 4; + } + reserved 1 to max; + } + optional VisibilityFeature.DefaultSymbolVisibility default_symbol_visibility = + 8 [ + retention = RETENTION_SOURCE, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2024, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "EXPORT_ALL" }, + edition_defaults = { edition: EDITION_2024, value: "EXPORT_TOP_LEVEL" } + ]; reserved 999; @@ -1069,6 +1182,11 @@ message FeatureSet { full_name: ".pb.python", type: ".pb.PythonFeatures" }, + declaration = { + number: 9989, + full_name: ".pb.java_mutable", + type: ".pb.JavaMutableFeatures" + }, declaration = { number: 9990, full_name: ".pb.proto1", @@ -1291,4 +1409,15 @@ message GeneratedCodeInfo { } optional Semantic semantic = 5; } +} + +// Describes the 'visibility' of a symbol with respect to the proto import +// system. Symbols can only be imported when the visibility rules do not prevent +// it (ex: local symbols cannot be imported). Visibility modifiers can only set +// on `message` and `enum` as they are the only types available to be referenced +// from other files. +enum SymbolVisibility { + VISIBILITY_UNSET = 0; + VISIBILITY_LOCAL = 1; + VISIBILITY_EXPORT = 2; } \ No newline at end of file From f39bb00221b532d60be487e78fb4fbe8a59b85c0 Mon Sep 17 00:00:00 2001 From: Mike Kruskal <62662355+mkruskal-google@users.noreply.github.com> Date: Tue, 27 May 2025 15:09:54 -0700 Subject: [PATCH 6/6] Handle repeated options properly --- ext/descriptor/index.js | 21 +++++++++++++-------- tests/comp_import_extend.js | 1 + 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/ext/descriptor/index.js b/ext/descriptor/index.js index 37cad97a9..77ba8d200 100644 --- a/ext/descriptor/index.js +++ b/ext/descriptor/index.js @@ -863,7 +863,7 @@ function toDescriptorType(type, resolvedType, delimited) { throw Error("illegal type: " + type); } -function fixDescriptorOptionsRecursive(obj, type) { +function fromDescriptorOptionsRecursive(obj, type) { var val = {}; for (var i = 0, field, key; i < type.fieldsArray.length; ++i) { if ((key = (field = type._fieldsArray[i]).name) === "uninterpretedOption") continue; @@ -871,7 +871,7 @@ function fixDescriptorOptionsRecursive(obj, type) { var newKey = underScore(key); if (field.resolvedType instanceof Type) { - val[newKey] = fixDescriptorOptionsRecursive(obj[key], field.resolvedType); + val[newKey] = fromDescriptorOptionsRecursive(obj[key], field.resolvedType); } else if(field.resolvedType instanceof Enum) { val[newKey] = field.resolvedType.valuesById[obj[key]]; } else { @@ -885,19 +885,24 @@ function fixDescriptorOptionsRecursive(obj, type) { function fromDescriptorOptions(options, type) { if (!options) return undefined; - return fixDescriptorOptionsRecursive(type.toObject(options), type); + return fromDescriptorOptionsRecursive(type.toObject(options), type); } -function camelCaseRecursive(obj) { +function toDescriptorOptionsRecursive(obj, type) { var val = {}; var keys = Object.keys(obj); for (var i = 0; i < keys.length; ++i) { var key = keys[i]; var newKey = $protobuf.util.camelCase(key); - if (typeof obj[key] !== "object") { - val[newKey] = obj[key]; + if (!Object.prototype.hasOwnProperty.call(type.fields, newKey)) continue; + var field = type.fields[newKey]; + if (field.resolvedType instanceof Type) { + val[newKey] = toDescriptorOptionsRecursive(obj[key], field.resolvedType); } else { - val[newKey] = camelCaseRecursive(obj[key]); + val[newKey] = obj[key]; + } + if (field.repeated && !Array.isArray(val[newKey])) { + val[newKey] = [val[newKey]]; } } return val; @@ -907,7 +912,7 @@ function camelCaseRecursive(obj) { function toDescriptorOptions(options, type) { if (!options) return undefined; - return type.fromObject(camelCaseRecursive(options)); + return type.fromObject(toDescriptorOptionsRecursive(options, type)); } // Calculates the shortest relative path from `from` to `to`. diff --git a/tests/comp_import_extend.js b/tests/comp_import_extend.js index 574690415..7a3d2ec19 100644 --- a/tests/comp_import_extend.js +++ b/tests/comp_import_extend.js @@ -34,6 +34,7 @@ tape.test("extensions - proto2 roundtrip", function (test) { required string required = 2; repeated int32 packed = 3 [packed = true]; repeated int32 unpacked = 4; + optional int32 repeated_options = 5 [targets = TARGET_TYPE_SERVICE, targets = TARGET_TYPE_FILE]; } `).root.resolveAll();