diff --git a/README.md b/README.md index 861d96f9e..af0ccc7e4 100644 --- a/README.md +++ b/README.md @@ -578,6 +578,8 @@ Other notes: * Default values must be specified as arguments to the decorator instead of using a property initializer for proper prototype behavior. * Property names on decorated classes must not be renamed on compile time (i.e. by a minifier) because decorators just receive the original field name as a string. +**ProTip!** Not as pretty, but you can [use decorators in plain JavaScript](https://github.com/dcodeIO/protobuf.js/blob/master/examples/js-decorators.js) as well. + Command line ------------ diff --git a/examples/js-decorators.js b/examples/js-decorators.js new file mode 100644 index 000000000..440c9a08a --- /dev/null +++ b/examples/js-decorators.js @@ -0,0 +1,41 @@ +// This example shows how decorators can be used with plain JavaScript. It's otherwise identical to +// the README example. + +/*eslint-disable strict, no-console*/ +var protobuf = require(".."); + +var Type = protobuf.Type, + Field = protobuf.Field, + OneOf = protobuf.OneOf; + +function AwesomeSubMessage(properties) { + protobuf.Message.call(this, properties); +} + +(AwesomeSubMessage.prototype = Object.create(protobuf.Message)).constructor = AwesomeSubMessage; + +Field.d(1, "string", "optional", "awesome default string")(AwesomeSubMessage.prototype, "awesomeField"); + +var AwesomeEnum = { + ONE: 1, + TWO: 2 +}; + +Type.d("SuperAwesomeMessage")(AwesomeMessage); +function AwesomeMessage(properties) { + protobuf.Message.call(this, properties); +} + +(AwesomeMessage.prototype = Object.create(protobuf.Message)).constructor = AwesomeMessage; + +Field.d(1, "string", "optional", "awesome default string")(AwesomeMessage.prototype, "awesomeField"); +Field.d(2, AwesomeSubMessage)(AwesomeMessage.prototype, "awesomeSubMessage"); +Field.d(3, AwesomeEnum, "optional", AwesomeEnum.ONE)(AwesomeMessage.prototype, "awesomeEnum"); +OneOf.d("awesomeSubMessage", "awesomeEnum")(AwesomeMessage.prototype, "which"); + +// example code +var message = new AwesomeMessage({ awesomeField: "hello" }); +var buffer = AwesomeMessage.encode(message).finish(); +var decoded = AwesomeMessage.decode(buffer); + +console.log(decoded, "internal name: " + AwesomeMessage.$type.name); diff --git a/ext/descriptor/README.md b/ext/descriptor/README.md index b76f28435..c169c3f60 100644 --- a/ext/descriptor/README.md +++ b/ext/descriptor/README.md @@ -1,7 +1,7 @@ protobufjs/ext/descriptor ========================= -Experimental extension for interoperability with descriptor.proto types. +Experimental extension for interoperability with [descriptor.proto](https://github.com/google/protobuf/blob/master/src/google/protobuf/descriptor.proto) types. Usage ----- @@ -31,17 +31,17 @@ root = protobuf.Root.fromDescriptor(decoded); API --- -The extension adds `.fromDescriptor(descriptor[, syntax])` and `#toDescriptor([syntax])` methods to reflection objects and exports the `.google.protobuf` namespace of the internally used `Root` instance containing the following types present in descriptor.proto. +The extension adds `.fromDescriptor(descriptor[, syntax])` and `#toDescriptor([syntax])` methods to reflection objects and exports the `.google.protobuf` namespace of the internally used `Root` instance containing the following types present in descriptor.proto, including sub-types: | Descriptor type | protobuf.js type | Remarks |--------------------------|------------------|--------- | FileDescriptorSet | Root | -| FileDescriptorProto | Root | not supported: dependencies, sourceCodeInfo +| FileDescriptorProto | Root | except dependencies, sourceCodeInfo | FileOptions | Root | not supported | DescriptorProto | Type | | MessageOptions | Type | not supported -| FieldDescriptorProto | Field | not supported: defaultValue, jsonValue -| FieldOptions | Field | only packed +| FieldDescriptorProto | Field | except defaultValue +| FieldOptions | Field | | OneofDescriptorProto | OneOf | | OneofOptions | OneOf | not supported | EnumDescriptorProto | Enum | @@ -56,4 +56,4 @@ The extension adds `.fromDescriptor(descriptor[, syntax])` and `#toDescriptor([s | SourceCodeInfo | | not supported | GeneratedCodeInfo | | not supported -Additionally, not all features of descriptor.proto translate perfectly to a protobuf.js root instance. A root instance has only limited knowlege of packages or individual files for example, which is then compensated by guessing. +Note that not all features of descriptor.proto translate perfectly to a protobuf.js root instance. A root instance has only limited knowlege of packages or individual files for example, which is then compensated by guessing and generating fictional file names. diff --git a/ext/descriptor/index.js b/ext/descriptor/index.js index e2eedd502..27c47bb6a 100644 --- a/ext/descriptor/index.js +++ b/ext/descriptor/index.js @@ -282,8 +282,8 @@ Type.prototype.toDescriptor = function toDescriptor(syntax) { * @type {Object} * @property {string} [name] Field name * @property {number} [number] Field id - * @property {FieldDescriptorProtoLabel} [label] Field rule - * @property {FieldDescriptorProtoType} [type] Field basic type + * @property {FieldDescriptorProto_Label} [label] Field rule + * @property {FieldDescriptorProto_Type} [type] Field basic type * @property {string} [typeName] Field type name * @property {string} [extendee] Extended type name * @property {*} [defaultValue] Not supported @@ -295,7 +295,7 @@ Type.prototype.toDescriptor = function toDescriptor(syntax) { /** * Values of the FieldDescriptorProto.Label enum. - * @typedef FieldDescriptorProtoLabel + * @typedef FieldDescriptorProto_Label * @type {number} * @property {number} LABEL_OPTIONAL=1 * @property {number} LABEL_REQUIRED=2 @@ -305,7 +305,7 @@ Type.prototype.toDescriptor = function toDescriptor(syntax) { /** * Values of the FieldDescriptorProto.Type enum. - * @typedef FieldDescriptorProtoType + * @typedef FieldDescriptorProto_Type * @type {number} * @property {number} TYPE_DOUBLE=1 * @property {number} TYPE_FLOAT=2 @@ -333,9 +333,19 @@ Type.prototype.toDescriptor = function toDescriptor(syntax) { * @typedef FieldOptionsProperties * @type {Object} * @property {boolean} [packed] Whether packed or not (defaults to `false` for proto2 and `true` for proto3) + * @property {FieldOptions_JSType} [jstype] JavaScript value type (not used by protobuf.js) * @see Part of the {@link descriptor} extension (ext/descriptor) */ +/** + * Values of the FieldOptions.JSType enum. + * @typedef FieldOptions_JSType + * @type {number} + * @property {number} JS_NORMAL=0 + * @property {number} JS_STRING=1 + * @property {number} JS_NUMBER=2 + */ + // Converts a descriptor type to a protobuf.js basic type function fromDescriptorType(type) { switch (type) { @@ -422,6 +432,9 @@ Field.fromDescriptor = function fromDescriptor(descriptor, syntax) { descriptor.extendee.length ? descriptor.extendee : undefined ); + if (descriptor.options) + field.options = fromDescriptorOptions(descriptor.options, exports.FieldOptions); + if (packableDescriptorType(descriptor.type)) { if (syntax === "proto3") { // defaults to packed=true (internal preset is packed=true) if (descriptor.options && !descriptor.options.packed) @@ -503,11 +516,14 @@ Field.prototype.toDescriptor = function toDescriptor(syntax) { if ((descriptor.oneofIndex = this.parent.oneofsArray.indexOf(this.partOf)) < 0) throw Error("missing oneof"); + if (this.options) + descriptor.options = toDescriptorOptions(this.options, exports.FieldOptions); + if (syntax === "proto3") { // defaults to packed=true if (!this.packed) - descriptor.options = exports.FieldOptions.create({ packed: false }); + (descriptor.options || (descriptor.options = exports.FieldOptions.create())).packed = false; } else if (this.packed) // defaults to packed=false - descriptor.options = exports.FieldOptions.create({ packed: true }); + (descriptor.options || (descriptor.options = exports.FieldOptions.create())).packed = true; return descriptor; }; @@ -739,6 +755,24 @@ Method.prototype.toDescriptor = function toDescriptor() { }); }; +// --- utility --- + +function fromDescriptorOptions(options, type) { + var out = []; + for (var i = 0, key; i < type.fieldsArray.length; ++i) + if ((key = type._fieldsArray[i].name) !== "uninterpretedOption") + if (options.hasOwnProperty(key)) // eslint-disable-line no-prototype-builtins + out.push(key, options[key]); + return out.length ? $protobuf.util.toObject(out) : undefined; +} + +function toDescriptorOptions(options, type) { + var out = []; + for (var i = 0, key; i < type.fieldsArray.length; ++i) + out.push(key = type._fieldsArray[i].name, options[key]); + return out.length ? type.fromObject($protobuf.util.toObject(out)) : undefined; +} + // --- exports --- /** diff --git a/ext/descriptor/test.js b/ext/descriptor/test.js index 43a92fbfd..44dc7ba1c 100644 --- a/ext/descriptor/test.js +++ b/ext/descriptor/test.js @@ -36,7 +36,7 @@ var msg = root.toDescriptor(); // console.log("\nDescriptor", JSON.stringify(msg.toObject(), null, 2)); var buf = descriptor.FileDescriptorSet.encode(msg).finish(); -var root2 = protobuf.Root.fromDescriptor(buf, "proto2"); +var root2 = protobuf.Root.fromDescriptor(buf, "proto2").resolveAll(); // console.log("\nDecoded proto", JSON.stringify(root2, null, 2)); diff --git a/src/type.js b/src/type.js index 1d5648ce2..a36505492 100644 --- a/src/type.js +++ b/src/type.js @@ -166,7 +166,7 @@ Object.defineProperties(Type.prototype, { // Classes and messages reference their reflected type ctor.$type = ctor.prototype.$type = this; - // Mixin static methods + // Mix in static methods util.merge(ctor, Message, true); this._ctor = ctor; @@ -210,7 +210,7 @@ Type.generateConstructor = function generateConstructor(type) { }; function clearCache(type) { - type._fieldsById = type._fieldsArray = type._oneofsArray = type._ctor = null; + type._fieldsById = type._fieldsArray = type._oneofsArray = null; delete type.encode; delete type.decode; delete type.verify; diff --git a/src/util.js b/src/util.js index dc5e059e0..830d15641 100644 --- a/src/util.js +++ b/src/util.js @@ -107,6 +107,7 @@ util.decorateType = function decorateType(ctor, typeName) { var type = new Type(typeName || ctor.name); util.decorateRoot.add(type); + type.ctor = ctor; // sets up .encode, .decode etc. Object.defineProperty(ctor, "$type", { value: type, enumerable: false }); Object.defineProperty(ctor.prototype, "$type", { value: type, enumerable: false }); return type;