635 lines
26 KiB
JavaScript
635 lines
26 KiB
JavaScript
|
/**
|
||
|
* @alias ProtoBuf.Builder
|
||
|
* @expose
|
||
|
*/
|
||
|
ProtoBuf.Builder = (function(ProtoBuf, Lang, Reflect) {
|
||
|
"use strict";
|
||
|
|
||
|
/**
|
||
|
* Constructs a new Builder.
|
||
|
* @exports ProtoBuf.Builder
|
||
|
* @class Provides the functionality to build protocol messages.
|
||
|
* @param {Object.<string,*>=} options Options
|
||
|
* @constructor
|
||
|
*/
|
||
|
var Builder = function(options) {
|
||
|
|
||
|
/**
|
||
|
* Namespace.
|
||
|
* @type {ProtoBuf.Reflect.Namespace}
|
||
|
* @expose
|
||
|
*/
|
||
|
this.ns = new Reflect.Namespace(this, null, ""); // Global namespace
|
||
|
|
||
|
/**
|
||
|
* Namespace pointer.
|
||
|
* @type {ProtoBuf.Reflect.T}
|
||
|
* @expose
|
||
|
*/
|
||
|
this.ptr = this.ns;
|
||
|
|
||
|
/**
|
||
|
* Resolved flag.
|
||
|
* @type {boolean}
|
||
|
* @expose
|
||
|
*/
|
||
|
this.resolved = false;
|
||
|
|
||
|
/**
|
||
|
* The current building result.
|
||
|
* @type {Object.<string,ProtoBuf.Builder.Message|Object>|null}
|
||
|
* @expose
|
||
|
*/
|
||
|
this.result = null;
|
||
|
|
||
|
/**
|
||
|
* Imported files.
|
||
|
* @type {Array.<string>}
|
||
|
* @expose
|
||
|
*/
|
||
|
this.files = {};
|
||
|
|
||
|
/**
|
||
|
* Import root override.
|
||
|
* @type {?string}
|
||
|
* @expose
|
||
|
*/
|
||
|
this.importRoot = null;
|
||
|
|
||
|
/**
|
||
|
* Options.
|
||
|
* @type {!Object.<string, *>}
|
||
|
* @expose
|
||
|
*/
|
||
|
this.options = options || {};
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @alias ProtoBuf.Builder.prototype
|
||
|
* @inner
|
||
|
*/
|
||
|
var BuilderPrototype = Builder.prototype;
|
||
|
|
||
|
// ----- Definition tests -----
|
||
|
|
||
|
/**
|
||
|
* Tests if a definition most likely describes a message.
|
||
|
* @param {!Object} def
|
||
|
* @returns {boolean}
|
||
|
* @expose
|
||
|
*/
|
||
|
Builder.isMessage = function(def) {
|
||
|
// Messages require a string name
|
||
|
if (typeof def["name"] !== 'string')
|
||
|
return false;
|
||
|
// Messages do not contain values (enum) or rpc methods (service)
|
||
|
if (typeof def["values"] !== 'undefined' || typeof def["rpc"] !== 'undefined')
|
||
|
return false;
|
||
|
return true;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Tests if a definition most likely describes a message field.
|
||
|
* @param {!Object} def
|
||
|
* @returns {boolean}
|
||
|
* @expose
|
||
|
*/
|
||
|
Builder.isMessageField = function(def) {
|
||
|
// Message fields require a string rule, name and type and an id
|
||
|
if (typeof def["rule"] !== 'string' || typeof def["name"] !== 'string' || typeof def["type"] !== 'string' || typeof def["id"] === 'undefined')
|
||
|
return false;
|
||
|
return true;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Tests if a definition most likely describes an enum.
|
||
|
* @param {!Object} def
|
||
|
* @returns {boolean}
|
||
|
* @expose
|
||
|
*/
|
||
|
Builder.isEnum = function(def) {
|
||
|
// Enums require a string name
|
||
|
if (typeof def["name"] !== 'string')
|
||
|
return false;
|
||
|
// Enums require at least one value
|
||
|
if (typeof def["values"] === 'undefined' || !Array.isArray(def["values"]) || def["values"].length === 0)
|
||
|
return false;
|
||
|
return true;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Tests if a definition most likely describes a service.
|
||
|
* @param {!Object} def
|
||
|
* @returns {boolean}
|
||
|
* @expose
|
||
|
*/
|
||
|
Builder.isService = function(def) {
|
||
|
// Services require a string name and an rpc object
|
||
|
if (typeof def["name"] !== 'string' || typeof def["rpc"] !== 'object' || !def["rpc"])
|
||
|
return false;
|
||
|
return true;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Tests if a definition most likely describes an extended message
|
||
|
* @param {!Object} def
|
||
|
* @returns {boolean}
|
||
|
* @expose
|
||
|
*/
|
||
|
Builder.isExtend = function(def) {
|
||
|
// Extends rquire a string ref
|
||
|
if (typeof def["ref"] !== 'string')
|
||
|
return false;
|
||
|
return true;
|
||
|
};
|
||
|
|
||
|
// ----- Building -----
|
||
|
|
||
|
/**
|
||
|
* Resets the pointer to the root namespace.
|
||
|
* @returns {!ProtoBuf.Builder} this
|
||
|
* @expose
|
||
|
*/
|
||
|
BuilderPrototype.reset = function() {
|
||
|
this.ptr = this.ns;
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Defines a namespace on top of the current pointer position and places the pointer on it.
|
||
|
* @param {string} namespace
|
||
|
* @return {!ProtoBuf.Builder} this
|
||
|
* @expose
|
||
|
*/
|
||
|
BuilderPrototype.define = function(namespace) {
|
||
|
if (typeof namespace !== 'string' || !Lang.TYPEREF.test(namespace))
|
||
|
throw Error("illegal namespace: "+namespace);
|
||
|
namespace.split(".").forEach(function(part) {
|
||
|
var ns = this.ptr.getChild(part);
|
||
|
if (ns === null) // Keep existing
|
||
|
this.ptr.addChild(ns = new Reflect.Namespace(this, this.ptr, part));
|
||
|
this.ptr = ns;
|
||
|
}, this);
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Creates the specified definitions at the current pointer position.
|
||
|
* @param {!Array.<!Object>} defs Messages, enums or services to create
|
||
|
* @returns {!ProtoBuf.Builder} this
|
||
|
* @throws {Error} If a message definition is invalid
|
||
|
* @expose
|
||
|
*/
|
||
|
BuilderPrototype.create = function(defs) {
|
||
|
if (!defs)
|
||
|
return this; // Nothing to create
|
||
|
if (!Array.isArray(defs))
|
||
|
defs = [defs];
|
||
|
else {
|
||
|
if (defs.length === 0)
|
||
|
return this;
|
||
|
defs = defs.slice();
|
||
|
}
|
||
|
|
||
|
// It's quite hard to keep track of scopes and memory here, so let's do this iteratively.
|
||
|
var stack = [defs];
|
||
|
while (stack.length > 0) {
|
||
|
defs = stack.pop();
|
||
|
|
||
|
if (!Array.isArray(defs)) // Stack always contains entire namespaces
|
||
|
throw Error("not a valid namespace: "+JSON.stringify(defs));
|
||
|
|
||
|
while (defs.length > 0) {
|
||
|
var def = defs.shift(); // Namespaces always contain an array of messages, enums and services
|
||
|
|
||
|
if (Builder.isMessage(def)) {
|
||
|
var obj = new Reflect.Message(this, this.ptr, def["name"], def["options"], def["isGroup"], def["syntax"]);
|
||
|
|
||
|
// Create OneOfs
|
||
|
var oneofs = {};
|
||
|
if (def["oneofs"])
|
||
|
Object.keys(def["oneofs"]).forEach(function(name) {
|
||
|
obj.addChild(oneofs[name] = new Reflect.Message.OneOf(this, obj, name));
|
||
|
}, this);
|
||
|
|
||
|
// Create fields
|
||
|
if (def["fields"])
|
||
|
def["fields"].forEach(function(fld) {
|
||
|
if (obj.getChild(fld["id"]|0) !== null)
|
||
|
throw Error("duplicate or invalid field id in "+obj.name+": "+fld['id']);
|
||
|
if (fld["options"] && typeof fld["options"] !== 'object')
|
||
|
throw Error("illegal field options in "+obj.name+"#"+fld["name"]);
|
||
|
var oneof = null;
|
||
|
if (typeof fld["oneof"] === 'string' && !(oneof = oneofs[fld["oneof"]]))
|
||
|
throw Error("illegal oneof in "+obj.name+"#"+fld["name"]+": "+fld["oneof"]);
|
||
|
fld = new Reflect.Message.Field(this, obj, fld["rule"], fld["keytype"], fld["type"], fld["name"], fld["id"], fld["options"], oneof, def["syntax"]);
|
||
|
if (oneof)
|
||
|
oneof.fields.push(fld);
|
||
|
obj.addChild(fld);
|
||
|
}, this);
|
||
|
|
||
|
// Push children to stack
|
||
|
var subObj = [];
|
||
|
if (def["enums"])
|
||
|
def["enums"].forEach(function(enm) {
|
||
|
subObj.push(enm);
|
||
|
});
|
||
|
if (def["messages"])
|
||
|
def["messages"].forEach(function(msg) {
|
||
|
subObj.push(msg);
|
||
|
});
|
||
|
if (def["services"])
|
||
|
def["services"].forEach(function(svc) {
|
||
|
subObj.push(svc);
|
||
|
});
|
||
|
|
||
|
// Set extension ranges
|
||
|
if (def["extensions"]) {
|
||
|
if (typeof def["extensions"][0] === 'number') // pre 5.0.1
|
||
|
obj.extensions = [ def["extensions"] ];
|
||
|
else
|
||
|
obj.extensions = def["extensions"];
|
||
|
}
|
||
|
|
||
|
// Create on top of current namespace
|
||
|
this.ptr.addChild(obj);
|
||
|
if (subObj.length > 0) {
|
||
|
stack.push(defs); // Push the current level back
|
||
|
defs = subObj; // Continue processing sub level
|
||
|
subObj = null;
|
||
|
this.ptr = obj; // And move the pointer to this namespace
|
||
|
obj = null;
|
||
|
continue;
|
||
|
}
|
||
|
subObj = null;
|
||
|
|
||
|
} else if (Builder.isEnum(def)) {
|
||
|
|
||
|
obj = new Reflect.Enum(this, this.ptr, def["name"], def["options"], def["syntax"]);
|
||
|
def["values"].forEach(function(val) {
|
||
|
obj.addChild(new Reflect.Enum.Value(this, obj, val["name"], val["id"]));
|
||
|
}, this);
|
||
|
this.ptr.addChild(obj);
|
||
|
|
||
|
} else if (Builder.isService(def)) {
|
||
|
|
||
|
obj = new Reflect.Service(this, this.ptr, def["name"], def["options"]);
|
||
|
Object.keys(def["rpc"]).forEach(function(name) {
|
||
|
var mtd = def["rpc"][name];
|
||
|
obj.addChild(new Reflect.Service.RPCMethod(this, obj, name, mtd["request"], mtd["response"], !!mtd["request_stream"], !!mtd["response_stream"], mtd["options"]));
|
||
|
}, this);
|
||
|
this.ptr.addChild(obj);
|
||
|
|
||
|
} else if (Builder.isExtend(def)) {
|
||
|
|
||
|
obj = this.ptr.resolve(def["ref"], true);
|
||
|
if (obj) {
|
||
|
def["fields"].forEach(function(fld) {
|
||
|
if (obj.getChild(fld['id']|0) !== null)
|
||
|
throw Error("duplicate extended field id in "+obj.name+": "+fld['id']);
|
||
|
// Check if field id is allowed to be extended
|
||
|
if (obj.extensions) {
|
||
|
var valid = false;
|
||
|
obj.extensions.forEach(function(range) {
|
||
|
if (fld["id"] >= range[0] && fld["id"] <= range[1])
|
||
|
valid = true;
|
||
|
});
|
||
|
if (!valid)
|
||
|
throw Error("illegal extended field id in "+obj.name+": "+fld['id']+" (not within valid ranges)");
|
||
|
}
|
||
|
// Convert extension field names to camel case notation if the override is set
|
||
|
var name = fld["name"];
|
||
|
if (this.options['convertFieldsToCamelCase'])
|
||
|
name = ProtoBuf.Util.toCamelCase(name);
|
||
|
// see #161: Extensions use their fully qualified name as their runtime key and...
|
||
|
var field = new Reflect.Message.ExtensionField(this, obj, fld["rule"], fld["type"], this.ptr.fqn()+'.'+name, fld["id"], fld["options"]);
|
||
|
// ...are added on top of the current namespace as an extension which is used for
|
||
|
// resolving their type later on (the extension always keeps the original name to
|
||
|
// prevent naming collisions)
|
||
|
var ext = new Reflect.Extension(this, this.ptr, fld["name"], field);
|
||
|
field.extension = ext;
|
||
|
this.ptr.addChild(ext);
|
||
|
obj.addChild(field);
|
||
|
}, this);
|
||
|
|
||
|
} else if (!/\.?google\.protobuf\./.test(def["ref"])) // Silently skip internal extensions
|
||
|
throw Error("extended message "+def["ref"]+" is not defined");
|
||
|
|
||
|
} else
|
||
|
throw Error("not a valid definition: "+JSON.stringify(def));
|
||
|
|
||
|
def = null;
|
||
|
obj = null;
|
||
|
}
|
||
|
// Break goes here
|
||
|
defs = null;
|
||
|
this.ptr = this.ptr.parent; // Namespace done, continue at parent
|
||
|
}
|
||
|
this.resolved = false; // Require re-resolve
|
||
|
this.result = null; // Require re-build
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Propagates syntax to all children.
|
||
|
* @param {!Object} parent
|
||
|
* @inner
|
||
|
*/
|
||
|
function propagateSyntax(parent) {
|
||
|
if (parent['messages']) {
|
||
|
parent['messages'].forEach(function(child) {
|
||
|
child["syntax"] = parent["syntax"];
|
||
|
propagateSyntax(child);
|
||
|
});
|
||
|
}
|
||
|
if (parent['enums']) {
|
||
|
parent['enums'].forEach(function(child) {
|
||
|
child["syntax"] = parent["syntax"];
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Imports another definition into this builder.
|
||
|
* @param {Object.<string,*>} json Parsed import
|
||
|
* @param {(string|{root: string, file: string})=} filename Imported file name
|
||
|
* @returns {!ProtoBuf.Builder} this
|
||
|
* @throws {Error} If the definition or file cannot be imported
|
||
|
* @expose
|
||
|
*/
|
||
|
BuilderPrototype["import"] = function(json, filename) {
|
||
|
var delim = '/';
|
||
|
|
||
|
// Make sure to skip duplicate imports
|
||
|
|
||
|
if (typeof filename === 'string') {
|
||
|
|
||
|
if (ProtoBuf.Util.IS_NODE)
|
||
|
filename = require("path")['resolve'](filename);
|
||
|
if (this.files[filename] === true)
|
||
|
return this.reset();
|
||
|
this.files[filename] = true;
|
||
|
|
||
|
} else if (typeof filename === 'object') { // Object with root, file.
|
||
|
|
||
|
var root = filename.root;
|
||
|
if (ProtoBuf.Util.IS_NODE)
|
||
|
root = require("path")['resolve'](root);
|
||
|
if (root.indexOf("\\") >= 0 || filename.file.indexOf("\\") >= 0)
|
||
|
delim = '\\';
|
||
|
var fname;
|
||
|
if (ProtoBuf.Util.IS_NODE)
|
||
|
fname = require("path")['join'](root, filename.file);
|
||
|
else
|
||
|
fname = root + delim + filename.file;
|
||
|
if (this.files[fname] === true)
|
||
|
return this.reset();
|
||
|
this.files[fname] = true;
|
||
|
}
|
||
|
|
||
|
// Import imports
|
||
|
|
||
|
if (json['imports'] && json['imports'].length > 0) {
|
||
|
var importRoot,
|
||
|
resetRoot = false;
|
||
|
|
||
|
if (typeof filename === 'object') { // If an import root is specified, override
|
||
|
|
||
|
this.importRoot = filename["root"]; resetRoot = true; // ... and reset afterwards
|
||
|
importRoot = this.importRoot;
|
||
|
filename = filename["file"];
|
||
|
if (importRoot.indexOf("\\") >= 0 || filename.indexOf("\\") >= 0)
|
||
|
delim = '\\';
|
||
|
|
||
|
} else if (typeof filename === 'string') {
|
||
|
|
||
|
if (this.importRoot) // If import root is overridden, use it
|
||
|
importRoot = this.importRoot;
|
||
|
else { // Otherwise compute from filename
|
||
|
if (filename.indexOf("/") >= 0) { // Unix
|
||
|
importRoot = filename.replace(/\/[^\/]*$/, "");
|
||
|
if (/* /file.proto */ importRoot === "")
|
||
|
importRoot = "/";
|
||
|
} else if (filename.indexOf("\\") >= 0) { // Windows
|
||
|
importRoot = filename.replace(/\\[^\\]*$/, "");
|
||
|
delim = '\\';
|
||
|
} else
|
||
|
importRoot = ".";
|
||
|
}
|
||
|
|
||
|
} else
|
||
|
importRoot = null;
|
||
|
|
||
|
for (var i=0; i<json['imports'].length; i++) {
|
||
|
if (typeof json['imports'][i] === 'string') { // Import file
|
||
|
if (!importRoot)
|
||
|
throw Error("cannot determine import root");
|
||
|
var importFilename = json['imports'][i];
|
||
|
if (importFilename === "google/protobuf/descriptor.proto")
|
||
|
continue; // Not needed and therefore not used
|
||
|
if (ProtoBuf.Util.IS_NODE)
|
||
|
importFilename = require("path")['join'](importRoot, importFilename);
|
||
|
else
|
||
|
importFilename = importRoot + delim + importFilename;
|
||
|
if (this.files[importFilename] === true)
|
||
|
continue; // Already imported
|
||
|
if (/\.proto$/i.test(importFilename) && !ProtoBuf.DotProto) // If this is a light build
|
||
|
importFilename = importFilename.replace(/\.proto$/, ".json"); // always load the JSON file
|
||
|
var contents = ProtoBuf.Util.fetch(importFilename);
|
||
|
if (contents === null)
|
||
|
throw Error("failed to import '"+importFilename+"' in '"+filename+"': file not found");
|
||
|
if (/\.json$/i.test(importFilename)) // Always possible
|
||
|
this["import"](JSON.parse(contents+""), importFilename); // May throw
|
||
|
else
|
||
|
this["import"](ProtoBuf.DotProto.Parser.parse(contents), importFilename); // May throw
|
||
|
} else // Import structure
|
||
|
if (!filename)
|
||
|
this["import"](json['imports'][i]);
|
||
|
else if (/\.(\w+)$/.test(filename)) // With extension: Append _importN to the name portion to make it unique
|
||
|
this["import"](json['imports'][i], filename.replace(/^(.+)\.(\w+)$/, function($0, $1, $2) { return $1+"_import"+i+"."+$2; }));
|
||
|
else // Without extension: Append _importN to make it unique
|
||
|
this["import"](json['imports'][i], filename+"_import"+i);
|
||
|
}
|
||
|
if (resetRoot) // Reset import root override when all imports are done
|
||
|
this.importRoot = null;
|
||
|
}
|
||
|
|
||
|
// Import structures
|
||
|
|
||
|
if (json['package'])
|
||
|
this.define(json['package']);
|
||
|
if (json['syntax'])
|
||
|
propagateSyntax(json);
|
||
|
var base = this.ptr;
|
||
|
if (json['options'])
|
||
|
Object.keys(json['options']).forEach(function(key) {
|
||
|
base.options[key] = json['options'][key];
|
||
|
});
|
||
|
if (json['messages'])
|
||
|
this.create(json['messages']),
|
||
|
this.ptr = base;
|
||
|
if (json['enums'])
|
||
|
this.create(json['enums']),
|
||
|
this.ptr = base;
|
||
|
if (json['services'])
|
||
|
this.create(json['services']),
|
||
|
this.ptr = base;
|
||
|
if (json['extends'])
|
||
|
this.create(json['extends']);
|
||
|
|
||
|
return this.reset();
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Resolves all namespace objects.
|
||
|
* @throws {Error} If a type cannot be resolved
|
||
|
* @returns {!ProtoBuf.Builder} this
|
||
|
* @expose
|
||
|
*/
|
||
|
BuilderPrototype.resolveAll = function() {
|
||
|
// Resolve all reflected objects
|
||
|
var res;
|
||
|
if (this.ptr == null || typeof this.ptr.type === 'object')
|
||
|
return this; // Done (already resolved)
|
||
|
|
||
|
if (this.ptr instanceof Reflect.Namespace) { // Resolve children
|
||
|
|
||
|
this.ptr.children.forEach(function(child) {
|
||
|
this.ptr = child;
|
||
|
this.resolveAll();
|
||
|
}, this);
|
||
|
|
||
|
} else if (this.ptr instanceof Reflect.Message.Field) { // Resolve type
|
||
|
|
||
|
if (!Lang.TYPE.test(this.ptr.type)) {
|
||
|
if (!Lang.TYPEREF.test(this.ptr.type))
|
||
|
throw Error("illegal type reference in "+this.ptr.toString(true)+": "+this.ptr.type);
|
||
|
res = (this.ptr instanceof Reflect.Message.ExtensionField ? this.ptr.extension.parent : this.ptr.parent).resolve(this.ptr.type, true);
|
||
|
if (!res)
|
||
|
throw Error("unresolvable type reference in "+this.ptr.toString(true)+": "+this.ptr.type);
|
||
|
this.ptr.resolvedType = res;
|
||
|
if (res instanceof Reflect.Enum) {
|
||
|
this.ptr.type = ProtoBuf.TYPES["enum"];
|
||
|
if (this.ptr.syntax === 'proto3' && res.syntax !== 'proto3')
|
||
|
throw Error("proto3 message cannot reference proto2 enum");
|
||
|
}
|
||
|
else if (res instanceof Reflect.Message)
|
||
|
this.ptr.type = res.isGroup ? ProtoBuf.TYPES["group"] : ProtoBuf.TYPES["message"];
|
||
|
else
|
||
|
throw Error("illegal type reference in "+this.ptr.toString(true)+": "+this.ptr.type);
|
||
|
} else
|
||
|
this.ptr.type = ProtoBuf.TYPES[this.ptr.type];
|
||
|
|
||
|
// If it's a map field, also resolve the key type. The key type can be only a numeric, string, or bool type
|
||
|
// (i.e., no enums or messages), so we don't need to resolve against the current namespace.
|
||
|
if (this.ptr.map) {
|
||
|
if (!Lang.TYPE.test(this.ptr.keyType))
|
||
|
throw Error("illegal key type for map field in "+this.ptr.toString(true)+": "+this.ptr.keyType);
|
||
|
this.ptr.keyType = ProtoBuf.TYPES[this.ptr.keyType];
|
||
|
}
|
||
|
|
||
|
// If it's a repeated and packable field then proto3 mandates it should be packed by
|
||
|
// default
|
||
|
if (
|
||
|
this.ptr.syntax === 'proto3' &&
|
||
|
this.ptr.repeated && this.ptr.options.packed === undefined &&
|
||
|
ProtoBuf.PACKABLE_WIRE_TYPES.indexOf(this.ptr.type.wireType) !== -1
|
||
|
) {
|
||
|
this.ptr.options.packed = true;
|
||
|
}
|
||
|
|
||
|
} else if (this.ptr instanceof ProtoBuf.Reflect.Service.Method) {
|
||
|
|
||
|
if (this.ptr instanceof ProtoBuf.Reflect.Service.RPCMethod) {
|
||
|
res = this.ptr.parent.resolve(this.ptr.requestName, true);
|
||
|
if (!res || !(res instanceof ProtoBuf.Reflect.Message))
|
||
|
throw Error("Illegal type reference in "+this.ptr.toString(true)+": "+this.ptr.requestName);
|
||
|
this.ptr.resolvedRequestType = res;
|
||
|
res = this.ptr.parent.resolve(this.ptr.responseName, true);
|
||
|
if (!res || !(res instanceof ProtoBuf.Reflect.Message))
|
||
|
throw Error("Illegal type reference in "+this.ptr.toString(true)+": "+this.ptr.responseName);
|
||
|
this.ptr.resolvedResponseType = res;
|
||
|
} else // Should not happen as nothing else is implemented
|
||
|
throw Error("illegal service type in "+this.ptr.toString(true));
|
||
|
|
||
|
} else if (
|
||
|
!(this.ptr instanceof ProtoBuf.Reflect.Message.OneOf) && // Not built
|
||
|
!(this.ptr instanceof ProtoBuf.Reflect.Extension) && // Not built
|
||
|
!(this.ptr instanceof ProtoBuf.Reflect.Enum.Value) // Built in enum
|
||
|
)
|
||
|
throw Error("illegal object in namespace: "+typeof(this.ptr)+": "+this.ptr);
|
||
|
|
||
|
return this.reset();
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Builds the protocol. This will first try to resolve all definitions and, if this has been successful,
|
||
|
* return the built package.
|
||
|
* @param {(string|Array.<string>)=} path Specifies what to return. If omitted, the entire namespace will be returned.
|
||
|
* @returns {!ProtoBuf.Builder.Message|!Object.<string,*>}
|
||
|
* @throws {Error} If a type could not be resolved
|
||
|
* @expose
|
||
|
*/
|
||
|
BuilderPrototype.build = function(path) {
|
||
|
this.reset();
|
||
|
if (!this.resolved)
|
||
|
this.resolveAll(),
|
||
|
this.resolved = true,
|
||
|
this.result = null; // Require re-build
|
||
|
if (this.result === null) // (Re-)Build
|
||
|
this.result = this.ns.build();
|
||
|
if (!path)
|
||
|
return this.result;
|
||
|
var part = typeof path === 'string' ? path.split(".") : path,
|
||
|
ptr = this.result; // Build namespace pointer (no hasChild etc.)
|
||
|
for (var i=0; i<part.length; i++)
|
||
|
if (ptr[part[i]])
|
||
|
ptr = ptr[part[i]];
|
||
|
else {
|
||
|
ptr = null;
|
||
|
break;
|
||
|
}
|
||
|
return ptr;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Similar to {@link ProtoBuf.Builder#build}, but looks up the internal reflection descriptor.
|
||
|
* @param {string=} path Specifies what to return. If omitted, the entire namespace wiil be returned.
|
||
|
* @param {boolean=} excludeNonNamespace Excludes non-namespace types like fields, defaults to `false`
|
||
|
* @returns {?ProtoBuf.Reflect.T} Reflection descriptor or `null` if not found
|
||
|
*/
|
||
|
BuilderPrototype.lookup = function(path, excludeNonNamespace) {
|
||
|
return path ? this.ns.resolve(path, excludeNonNamespace) : this.ns;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Returns a string representation of this object.
|
||
|
* @return {string} String representation as of "Builder"
|
||
|
* @expose
|
||
|
*/
|
||
|
BuilderPrototype.toString = function() {
|
||
|
return "Builder";
|
||
|
};
|
||
|
|
||
|
// ----- Base classes -----
|
||
|
// Exist for the sole purpose of being able to "... instanceof ProtoBuf.Builder.Message" etc.
|
||
|
|
||
|
/**
|
||
|
* @alias ProtoBuf.Builder.Message
|
||
|
*/
|
||
|
Builder.Message = function() {};
|
||
|
|
||
|
/**
|
||
|
* @alias ProtoBuf.Builder.Enum
|
||
|
*/
|
||
|
Builder.Enum = function() {};
|
||
|
|
||
|
/**
|
||
|
* @alias ProtoBuf.Builder.Message
|
||
|
*/
|
||
|
Builder.Service = function() {};
|
||
|
|
||
|
return Builder;
|
||
|
|
||
|
})(ProtoBuf, ProtoBuf.Lang, ProtoBuf.Reflect);
|