Skip to content

Commit

Permalink
Fixed: Types should not clear constructor with cache (fixes decorators)
Browse files Browse the repository at this point in the history
  • Loading branch information
dcodeIO committed Apr 13, 2017
1 parent 3a20968 commit 0589ace
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 15 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
------------
Expand Down
41 changes: 41 additions & 0 deletions examples/js-decorators.js
Original file line number Diff line number Diff line change
@@ -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);
12 changes: 6 additions & 6 deletions ext/descriptor/README.md
Original file line number Diff line number Diff line change
@@ -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
-----
Expand Down Expand Up @@ -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 |
Expand All @@ -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.
46 changes: 40 additions & 6 deletions ext/descriptor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
};
Expand Down Expand Up @@ -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 ---

/**
Expand Down
2 changes: 1 addition & 1 deletion ext/descriptor/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down
4 changes: 2 additions & 2 deletions src/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 0589ace

Please sign in to comment.