From 57d7d35ddbb9e3a28c396b4ef1ae3b150eeb8035 Mon Sep 17 00:00:00 2001 From: dcodeIO Date: Wed, 12 Apr 2017 18:17:32 +0200 Subject: [PATCH] New: ext/descriptor enables interoperability between reflection and descriptor.proto (experimental), see #757 --- ext/descriptor.js | 571 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 448 insertions(+), 123 deletions(-) diff --git a/ext/descriptor.js b/ext/descriptor.js index 743c56a7e..88796fab3 100644 --- a/ext/descriptor.js +++ b/ext/descriptor.js @@ -9,10 +9,8 @@ var protobuf = require(".."); /** * Descriptor extension (ext/descriptor). * @namespace - * @property {Type} FileDescriptorSet Descriptor set describing a root - * @property {Type} DescriptorProto Descriptor describing a type - * @property {Type} FieldDescriptorProto Descriptor describing a field */ + var descriptor = module.exports = protobuf.Root.fromJSON(require("../google/protobuf/descriptor.json")).lookup(".google.protobuf"); var google = descriptor, @@ -20,27 +18,42 @@ var google = descriptor, Enum = protobuf.Enum, Type = protobuf.Type, Field = protobuf.Field, - OneOf = protobuf.OneOf; - -// Root : FileDescriptorSet -// ------------------------ -// [ ] repeated FileDescriptorProto file = 1; -// ├ [ ] optional string name = 1; -// ├ [ ] optional string package = 2; -// ├ [ ] repeated string dependency = 3; -// ├ [ ] repeated int32 public_dependency = 10; -// ├ [ ] repeated int32 weak_dependency = 11; -// ├ [ ] repeated DescriptorProto message_type = 4; -// ├ [ ] repeated EnumDescriptorProto enum_type = 5; -// ├ [ ] repeated ServiceDescriptorProto service = 6; -// ├ [ ] repeated FieldDescriptorProto extension = 7; -// ├ [ ] optional FileOptions options = 8; -// ├ [ ] optional SourceCodeInfo source_code_info = 9; -// └ [x] optional string syntax = 12; + OneOf = protobuf.OneOf, + Service = protobuf.Service, + Method = protobuf.Method; + +// --- Root --- + +/** + * Reflected type describing a root. + * @name descriptor.FileDescriptorSet + * @type {Type} + */ + +/** + * @interface IFileDescriptorSet + * @property {IFileDescriptorProto[]} file + */ + +/** + * @interface IFileDescriptorProto + * @property {string} [name] + * @property {string} [package] + * @property {*} [dependency] + * @property {*} [publicDependency] + * @property {*} [weakDependency] + * @property {IDescriptorProto[]} [messageType] + * @property {IEnumDescriptorProto[]} [enumType] + * @property {IServiceDescriptorProto[]} [service] + * @property {IFieldDescriptorProto[]} [extension] + * @property {*} [options] + * @property {*} [sourceCodeInfo] + * @property {string} [syntax="proto2"] + */ /** * Creates a root from a descriptor set. - * @param {FileDescriptorSet|Reader|Uint8Array} descriptor Descriptor + * @param {Properties|Reader|Uint8Array} descriptor Descriptor * @returns {Root} Root instance * @see Part of the {@link descriptor} extension (ext/descriptor) */ @@ -51,43 +64,102 @@ Root.fromDescriptor = function fromDescriptor(descriptor) { descriptor = google.FileDescriptorSet.decode(descriptor); var root = new Root(); - root.descriptorRoot = descriptor; - throw Error("not implemented"); + + if (descriptor.file) { + var fileDescriptor, + filePackage; + for (var j = 0, i; j < descriptor.file.length; ++j) { + fileDescriptor = descriptor.file[j]; + filePackage = root; + if (fileDescriptor.name && fileDescriptor.name.length) + root.files.push(fileDescriptor.name); + if (fileDescriptor["package"] && fileDescriptor["package"].length) + filePackage = root.define(fileDescriptor["package"]); + if (fileDescriptor.messageType) + for (i = 0; i < fileDescriptor.messageType.length; ++i) + filePackage.add(Type.fromDescriptor(fileDescriptor.messageType[i], fileDescriptor.syntax)); + if (fileDescriptor.enumType) + for (i = 0; i < fileDescriptor.enumType.length; ++i) + filePackage.add(Enum.fromDescriptor(fileDescriptor.enumType[i])); + if (fileDescriptor.extension) + for (i = 0; i < fileDescriptor.extension.length; ++i) + filePackage.add(Field.fromDescriptor(fileDescriptor.extension[i])); + } + } + + return root; }; +function traverseNamespace(ns, file) { + for (var i = 0; i < ns.nestedArray.length; ++i) + ( ns._nestedArray[i] instanceof Type ? file.messageType + : ns._nestedArray[i] instanceof Enum ? file.enumType + : ns._nestedArray[i] instanceof Field ? file.extension + : ns._nestedArray[i] instanceof Service ? file.service : []).push(ns._nestedArray[i].toDescriptor(file.syntax)); +} + /** * Converts a root to a descriptor set. - * @returns {FileDescriptorSet} Descriptor + * @returns {Message} Descriptor * @param {string} [syntax="proto2"] Syntax * @see Part of the {@link descriptor} extension (ext/descriptor) */ Root.prototype.toDescriptor = function toDescriptor(syntax) { + var file = google.FileDescriptorProto.create({ name: "bundle.proto" }); + if (syntax) + file.syntax = syntax; + traverseNamespace(this, file); + // return google.FileDescriptorSet.create({ file: [ file ] }); + + // not working, packages need to be split to individual files first because there is no support + // for plain namespaces throw Error("not implemented"); }; -// Type : DescriptorProto -// ---------------------- -// [x] optional string name = 1; -// [x] repeated FieldDescriptorProto field = 2; -// [x] repeated FieldDescriptorProto extension = 6; -// [x] repeated DescriptorProto nested_type = 3; -// [x] repeated EnumDescriptorProto enum_type = 4; -// [x] repeated ExtensionRange extension_range = 5; -// ├ optional int32 start = 1; -// └ optional int32 end = 2; -// [x] repeated OneofDescriptorProto oneof_decl = 8; -// [ ] optional MessageOptions options = 7; -// └ [ ] optional bool map_entry = 7; -// [x] repeated ReservedRange reserved_range = 9; -// ├ optional int32 start = 1; -// └ optional int32 end = 2; -// [x] repeated string reserved_name = 10; +// --- Type --- + +/** + * Reflected type describing a type. + * @name descriptor.DescriptorProto + * @type {Type} + */ + +/** + * @interface IDescriptorProto + * @property {string} [name] + * @property {IFieldDescriptorProto[]} [field] + * @property {IFieldDescriptorProto[]} [extension] + * @property {IDescriptorProto[]} [nestedType] + * @property {IEnumDescriptorProto[]} [enumType] + * @property {IExtensionRange[]} [extensionRange] + * @property {IOneofDescriptorProto[]} [oneofDecl] + * @property {IMessageOptions} [options] + * @property {IReservedRange[]} [reservedRange] + * @property {string[]} [reservedName] + */ + +/** + * @interface IMessageOptions + * @property {*} [mapEntry] + */ + +/** + * @interface IExtensionRange + * @property {number} [start] + * @property {number} [end] + */ + +/** + * @interface IReservedRange + * @property {number} [start] + * @property {number} [end] + */ var unnamedMessageIndex = 0; /** * Creates a type from a descriptor. - * @param {DescriptorProto|Reader|Uint8Array} descriptor Descriptor + * @param {Properties|Reader|Uint8Array} descriptor Descriptor * @param {string} [syntax="proto2"] Syntax * @returns {Type} Type instance * @see Part of the {@link descriptor} extension (ext/descriptor) @@ -99,7 +171,7 @@ Type.fromDescriptor = function fromDescriptor(descriptor, syntax) { descriptor = google.DescriptorProto.decode(descriptor); // Create the message type - var type = new Type(descriptor.name.length ? descriptor.name : "Unnamed" + unnamedMessageIndex++), + var type = new Type(descriptor.name.length ? descriptor.name : "Type" + unnamedMessageIndex++), i; /* Fields */ for (i = 0; i < descriptor.field.length; ++i) @@ -130,7 +202,7 @@ Type.fromDescriptor = function fromDescriptor(descriptor, syntax) { /** * Converts a type to a descriptor. - * @returns {DescriptorProto} Descriptor + * @returns {Message} Descriptor * @param {string} [syntax="proto2"] Syntax * @see Part of the {@link descriptor} extension (ext/descriptor) */ @@ -141,15 +213,15 @@ Type.prototype.toDescriptor = function toDescriptor(syntax) { /* Fields */ for (i = 0; i < this.fieldsArray.length; ++i) descriptor.field.push(this._fieldsArray[i].toDescriptor(syntax)); /* Nested... */ for (i = 0; i < this.nestedArray.length; ++i) { - var nested = this.nestedArray[i]; - /* Extension fields */ if (nested instanceof Field) - descriptor.field.push(nested.toDescriptor(syntax)); - /* Oneofs */ else if (nested instanceof OneOf) - descriptor.oneofDecl.push(nested.toDescriptor()); - /* Types */ else if (nested instanceof Type) - descriptor.nestedType.push(nested.toDescriptor(syntax)); - /* Enums */ else if (nested instanceof Enum) - descriptor.enumType.push(nested.toDescriptor()); + /* Extension fields */ if (this._nestedArray[i] instanceof Field) + descriptor.field.push(this._nestedArray[i].toDescriptor(syntax)); + /* Oneofs */ else if (this._nestedArray[i] instanceof OneOf) + descriptor.oneofDecl.push(this._nestedArray[i].toDescriptor()); + /* Types */ else if (this._nestedArray[i] instanceof Type) + descriptor.nestedType.push(this._nestedArray[i].toDescriptor(syntax)); + /* Enums */ else if (this._nestedArray[i] instanceof Enum) + descriptor.enumType.push(this._nestedArray[i].toDescriptor()); + // descriptor doesn't use / support plain nested namespaces } /* Extension ranges */ if (this.extensions) for (i = 0; i < this.extensions.length; ++i) descriptor.extensionRange.push(google.DescriptorProto.ExtensionRange.create({ start: this.extensions[i][0], end: this.extensions[i][1] })); @@ -162,44 +234,69 @@ Type.prototype.toDescriptor = function toDescriptor(syntax) { return descriptor; }; -// Field : FieldDescriptorProto -// ---------------------------- -// [x] optional string name = 1; -// [x] optional int32 number = 3; -// [x] optional Label label = 4; -// ├ LABEL_OPTIONAL = 1; -// ├ LABEL_REQUIRED = 2; -// └ LABEL_REPEATED = 3; -// [x] optional Type type = 5; -// ├ TYPE_DOUBLE = 1; -// ├ TYPE_FLOAT = 2; -// ├ TYPE_INT64 = 3; -// ├ TYPE_UINT64 = 4; -// ├ TYPE_INT32 = 5; -// ├ TYPE_FIXED64 = 6; -// ├ TYPE_FIXED32 = 7; -// ├ TYPE_BOOL = 8; -// ├ TYPE_STRING = 9; -// ├ TYPE_GROUP = 10; -// ├ TYPE_MESSAGE = 11; -// ├ TYPE_BYTES = 12; -// ├ TYPE_UINT32 = 13; -// ├ TYPE_ENUM = 14; -// ├ TYPE_SFIXED32 = 15; -// ├ TYPE_SFIXED64 = 16; -// ├ TYPE_SINT32 = 17; -// └ TYPE_SINT64 = 18; -// [x] optional string type_name = 6; -// [x] optional string extendee = 2; -// [ ] optional string default_value = 7; -// [ ] optional int32 oneof_index = 9; -// [ ] optional string json_name = 10; -// [~] optional FieldOptions options = 8; -// └ [x] optional bool packed = 2; +// --- Field --- + +/** + * Reflected type describing a field. + * @name descriptor.FieldDescriptorProto + * @type {Type} + * @property {Enum} Label Reflected descriptor describing a field label (rule) + * @property {Enum} Type Reflected descriptor describing a field type + */ + +/** + * @interface IFieldDescriptorProto + * @property {string} [name] + * @property {number} [number} + * @property {IFieldDescriptorProto_Label} [label] + * @property {IFieldDescriptorProto_Type} [type] + * @property {string} [typeName] + * @property {string} [extendee] + * @property {*} [defaultValue] + * @property {number} [oneofIndex] + * @property {*} [jsonName] + * @property {IFieldOptions} [options] + */ + +/** + * @typedef IFieldDescriptorProto_Label + * @type {number} + * @property {number} LABEL_OPTIONAL=1 + * @property {number} LABEL_REQUIRED=2 + * @property {number} LABEL_REPEATED=3 + */ + +/** + * @typedef IFieldDescriptorProto_Type + * @type {number} + * @property {number} TYPE_DOUBLE=1 + * @property {number} TYPE_FLOAT=2 + * @property {number} TYPE_INT64=3 + * @property {number} TYPE_UINT64=4 + * @property {number} TYPE_INT32=5 + * @property {number} TYPE_FIXED64=6 + * @property {number} TYPE_FIXED32=7 + * @property {number} TYPE_BOOL=8 + * @property {number} TYPE_STRING=9 + * @property {number} TYPE_GROUP=10 + * @property {number} TYPE_MESSAGE=11 + * @property {number} TYPE_BYTES=12 + * @property {number} TYPE_UINT32=13 + * @property {number} TYPE_ENUM=14 + * @property {number} TYPE_SFIXED32=15 + * @property {number} TYPE_SFIXED64=16 + * @property {number} TYPE_SINT32=17 + * @property {number} TYPE_SINT64=18 + */ + +/** + * @interface IFieldOptions + * @property {boolean} [packed] + */ /** * Creates a field from a descriptor. - * @param {FieldDescriptorProto|Reader|Uint8Array} descriptor Descriptor + * @param {IFieldDescriptorProto|Reader|Uint8Array} descriptor Descriptor * @param {string} [syntax="proto2"] Syntax * @returns {Field} Field instance * @see Part of the {@link descriptor} extension (ext/descriptor) @@ -212,8 +309,8 @@ Field.fromDescriptor = function fromDescriptor(descriptor, syntax) { // Rewire field type var fieldType; - if (descriptor.type_name.length) - fieldType = descriptor.type_name; + if (descriptor.typeName.length) + fieldType = descriptor.typeName; else switch (descriptor.type) { // 0 is reserved for errors case 1: fieldType = "double"; break; @@ -247,18 +344,26 @@ Field.fromDescriptor = function fromDescriptor(descriptor, syntax) { default: throw Error("illegal label: " + descriptor.label); } - var field = new Field(descriptor.name.length ? descriptor.name : "unnamed" + descriptor.number, descriptor.number, fieldType, fieldRule, descriptor.extendee.length ? descriptor.extendee : undefined); + var field = new Field( + descriptor.name.length ? descriptor.name : "field" + descriptor.number, + descriptor.number, + fieldType, + fieldRule, + descriptor.extendee.length ? descriptor.extendee : undefined + ); + if (syntax === "proto3") { if (descriptor.options && !descriptor.options.packed) field.setOption("packed", false); } else if (!(descriptor.options && descriptor.options.packed)) field.setOption("packed", false); + return field; }; /** * Converts a field to a descriptor. - * @returns {FieldDescriptorProto} Descriptor + * @returns {Message} Descriptor * @param {string} [syntax="proto2"] Syntax * @see Part of the {@link descriptor} extension (ext/descriptor) */ @@ -300,43 +405,263 @@ Field.prototype.toDescriptor = function toDescriptor(syntax) { default: descriptor.label = 1; break; } + // Handle extension field descriptor.extendee = this.extensionField ? this.extensionField.parent.fullName : this.extend; + // Handle part of oneof + if (this.partOf) + if ((descriptor.oneofIndex = this.parent.oneofsArray.indexOf(this.partOf)) < 0) + throw Error("missing oneof"); + if (syntax === "proto3") { if (!this.packed) - descriptor.options = new google.FieldOptions({ packed: false }); + descriptor.options = google.FieldOptions.create({ packed: false }); } else if (this.packed) - descriptor.options = new google.FieldOptions({ packed: true }); + descriptor.options = google.FieldOptions.create({ packed: true }); return descriptor; }; -// Enum : EnumDescriptorProto -// -------------------------- -// [ ] optional string name = 1; -// [ ] repeated EnumValueDescriptorProto value = 2; -// ├ [ ] optional string name = 1; -// ├ [ ] optional int32 number = 2; -// └ [ ] optional EnumValueOptions options = 3; -// [ ] optional EnumOptions options = 3; -// └ [ ] optional bool allow_alias = 2; - -// OneOf : OneofDescriptorProto -// ---------------------------- -// [ ] optional string name = 1; -// [ ] optional OneofOptions options = 2; - -// Service : ServiceDescriptorProto -// -------------------------------- -// [ ] optional string name = 1; -// [ ] repeated MethodDescriptorProto method = 2; -// [ ] optional ServiceOptions options = 3; - -// Method: MethodDescriptorProto -// ----------------------------- -// [ ] optional string name = 1; -// [ ] optional string input_type = 2; -// [ ] optional string output_type = 3; -// [ ] optional MethodOptions options = 4; -// [ ] optional bool client_streaming = 5; -// [ ] optional bool server_streaming = 6; +// --- Enum --- + +/** + * Reflected type describing an enum. + * @name descriptor.EnumDescriptorProto + * @type {Type} + */ + +/** + * Reflected type describing an enum value. + * @name descriptor.EnumValueDescriptorProto + * @type {Type} + */ + +/** + * Reflected type describing enum options. + * @name descriptor.EnumOptions + * @type {Type} + */ + +/** + * @interface IEnumDescriptorProto + * @property {string} [name] + * @property {IEnumValueDescriptorProto[]} [value] + * @property {IEnumOptions} [options] + */ + +/** + * @interface IEnumValueDescriptorProto + * @property {string} [name] + * @property {number} [number] + * @property {*} [options] + */ + +/** + * @interface IEnumOptions + * @property {boolean} [allowAlias] + */ + +var unnamedEnumIndex = 0; + +/** + * Creates an enum from a descriptor. + * @param {IEnumDescriptorProto|Reader|Uint8Array} descriptor Descriptor + * @returns {Enum} Enum instance + * @see Part of the {@link descriptor} extension (ext/descriptor) + */ +Enum.fromDescriptor = function fromDescriptor(descriptor) { + + // Decode the descriptor message if specified as a buffer: + if (typeof descriptor.length === "number") + descriptor = google.EnumDescriptorProto.decode(descriptor); + + // Values object + var values = {}; + if (descriptor.value) + for (var i = 0; i < descriptor.value.length; ++i) { + var name = descriptor.value[i].name, + value = descriptor.value[i].number || 0; + values[name && name.length ? name : "NAME" + value] = value; + } + + return new Enum( + descriptor.name && descriptor.name.length ? descriptor.name : "Enum" + unnamedEnumIndex++, + values + ); +}; + +/** + * Converts an enum to a descriptor. + * @returns {Message} Descriptor + * @see Part of the {@link descriptor} extension (ext/descriptor) + */ +Enum.prototype.toDescriptor = function toDescriptor() { + var values = []; + + for (var i = 0, ks = Object.keys(this.values), valueDescriptor; i < ks.length; ++i) { + values.push(valueDescriptor = google.EnumValueDescriptorProto.create({ name: ks[i] })); + if (this.values[ks[i]]) + valueDescriptor.value = this.values[ks[i]]; + } + return google.EnumDescriptorProto.create({ + name: this.name, + values: values + }); +}; + +// --- OneOf --- + +/** + * Reflected type describing a oneof. + * @name descriptor.OneofDescriptorProto + * @type {Type} + */ + +/** + * @interface IOneofDescriptorProto + * @property {string} [name] + * @property {*} [options] + */ + +var unnamedOneOfIndex = 0; + +/** + * Creates a oneof from a descriptor. + * @param {IOneofDescriptorProto|Reader|Uint8Array} descriptor Descriptor + * @returns {OneOf} OneOf instance + * @see Part of the {@link descriptor} extension (ext/descriptor) + */ +OneOf.fromDescriptor = function fromDescriptor(descriptor) { + + // Decode the descriptor message if specified as a buffer: + if (typeof descriptor.length === "number") + descriptor = google.OneofDescriptorProto.decode(descriptor); + + return new OneOf( + // unnamedOneOfIndex is global, not per type, because we have no ref to a type here + descriptor.name && descriptor.name.length ? descriptor.name : "oneof" + unnamedOneOfIndex++ + ); +}; + +/** + * Converts a oneof to a descriptor. + * @returns {Message} Descriptor + * @see Part of the {@link descriptor} extension (ext/descriptor) + */ +OneOf.prototype.toDescriptor = function toDescriptor() { + return google.OneofDescriptorProto.create({ + name: this.name + }); +}; + +// --- Service --- + +/** + * Reflected type describing a service. + * @name descriptor.ServiceDescriptorProto + * @type {Type} + */ + +/** + * @interface IServiceDescriptorProto + * @property {string} [name] + * @property {IMethodDescriptorProto[]} [method] + * @property {*} [options] + */ + +var unnamedServiceIndex = 0; + +/** + * Creates a service from a descriptor. + * @param {Properties|Reader|Uint8Array} descriptor Descriptor + * @returns {Service} Service instance + * @see Part of the {@link descriptor} extension (ext/descriptor) + */ +Service.fromDescriptor = function fromDescriptor(descriptor) { + + // Decode the descriptor message if specified as a buffer: + if (typeof descriptor.length === "number") + descriptor = google.ServiceDescriptorProto.decode(descriptor); + + var service = new Service(descriptor.name && descriptor.name.length ? descriptor.name : "Service" + unnamedServiceIndex++); + if (descriptor.method) + for (var i = 0; i < descriptor.method.length; ++i) + service.add(Method.fromDescriptor(descriptor.method[i])); + + return service; +}; + +/** + * Converts a service to a descriptor. + * @returns {Message} Descriptor + * @see Part of the {@link descriptor} extension (ext/descriptor) + */ +Service.prototype.toDescriptor = function toDescriptor() { + var methods = []; + + for (var i = 0; i < this.methodsArray; ++i) + methods.push(this._methodsArray[i].toDescriptor()); + + return google.ServiceDescriptorProto.create({ + name: this.name, + methods: methods + }); +}; + +// --- Method --- + +/** + * Reflected type describing a method. + * @name descriptor.MethodDescriptorProto + * @type {Type} + */ + +/** + * @interface IMethodDescriptorProto + * @property {string} [name] + * @property {string} [inputType] + * @property {string} [outputType] + * @property {*} [options] + * @property {boolean} [clientStreaming] + * @property {boolean} [serverStreaming] + */ + +var unnamedMethodIndex = 0; + +/** + * Creates a method from a descriptor. + * @param {IMethodDescriptorProto|Reader|Uint8Array} descriptor Descriptor + * @returns {Method} Reflected method instance + * @see Part of the {@link descriptor} extension (ext/descriptor) + */ +Method.fromDescriptor = function fromDescriptor(descriptor) { + + // Decode the descriptor message if specified as a buffer: + if (typeof descriptor.length === "number") + descriptor = google.MethodDescriptorProto.decode(descriptor); + + return new Method( + // unnamedMethodIndex is global, not per service, because we have no ref to a service here + descriptor.name && descriptor.name.length ? descriptor.name : "Method" + unnamedMethodIndex++, + "rpc", + descriptor.inputType, + descriptor.outputType, + Boolean(descriptor.clientStreaming), + Boolean(descriptor.serverStreaming) + ); +}; + +/** + * Converts a method to a descriptor. + * @returns {Message} Descriptor + * @see Part of the {@link descriptor} extension (ext/descriptor) + */ +Method.prototype.toDescriptor = function toDescriptor() { + return google.MethodDescriptorProto.create({ + name: this.name, + inputType: this.resolvedRequestType ? this.resolvedRequestType.fullName : this.requestType, + outputType: this.resolvedResponseType ? this.resolvedResponseType.fullName : this.responseType, + clientStreaming: this.requestStream, + serverStreaming: this.responseStream + }); +};