2 * @alias ProtoBuf.Builder
\r
5 ProtoBuf.Builder = (function(ProtoBuf, Lang, Reflect) {
\r
9 * Constructs a new Builder.
\r
10 * @exports ProtoBuf.Builder
\r
11 * @class Provides the functionality to build protocol messages.
\r
12 * @param {Object.<string,*>=} options Options
\r
15 var Builder = function(options) {
\r
19 * @type {ProtoBuf.Reflect.Namespace}
\r
22 this.ns = new Reflect.Namespace(this, null, ""); // Global namespace
\r
25 * Namespace pointer.
\r
26 * @type {ProtoBuf.Reflect.T}
\r
36 this.resolved = false;
\r
39 * The current building result.
\r
40 * @type {Object.<string,ProtoBuf.Builder.Message|Object>|null}
\r
47 * @type {Array.<string>}
\r
53 * Import root override.
\r
57 this.importRoot = null;
\r
61 * @type {!Object.<string, *>}
\r
64 this.options = options || {};
\r
68 * @alias ProtoBuf.Builder.prototype
\r
71 var BuilderPrototype = Builder.prototype;
\r
73 // ----- Definition tests -----
\r
76 * Tests if a definition most likely describes a message.
\r
77 * @param {!Object} def
\r
78 * @returns {boolean}
\r
81 Builder.isMessage = function(def) {
\r
82 // Messages require a string name
\r
83 if (typeof def["name"] !== 'string')
\r
85 // Messages do not contain values (enum) or rpc methods (service)
\r
86 if (typeof def["values"] !== 'undefined' || typeof def["rpc"] !== 'undefined')
\r
92 * Tests if a definition most likely describes a message field.
\r
93 * @param {!Object} def
\r
94 * @returns {boolean}
\r
97 Builder.isMessageField = function(def) {
\r
98 // Message fields require a string rule, name and type and an id
\r
99 if (typeof def["rule"] !== 'string' || typeof def["name"] !== 'string' || typeof def["type"] !== 'string' || typeof def["id"] === 'undefined')
\r
105 * Tests if a definition most likely describes an enum.
\r
106 * @param {!Object} def
\r
107 * @returns {boolean}
\r
110 Builder.isEnum = function(def) {
\r
111 // Enums require a string name
\r
112 if (typeof def["name"] !== 'string')
\r
114 // Enums require at least one value
\r
115 if (typeof def["values"] === 'undefined' || !Array.isArray(def["values"]) || def["values"].length === 0)
\r
121 * Tests if a definition most likely describes a service.
\r
122 * @param {!Object} def
\r
123 * @returns {boolean}
\r
126 Builder.isService = function(def) {
\r
127 // Services require a string name and an rpc object
\r
128 if (typeof def["name"] !== 'string' || typeof def["rpc"] !== 'object' || !def["rpc"])
\r
134 * Tests if a definition most likely describes an extended message
\r
135 * @param {!Object} def
\r
136 * @returns {boolean}
\r
139 Builder.isExtend = function(def) {
\r
140 // Extends rquire a string ref
\r
141 if (typeof def["ref"] !== 'string')
\r
146 // ----- Building -----
\r
149 * Resets the pointer to the root namespace.
\r
150 * @returns {!ProtoBuf.Builder} this
\r
153 BuilderPrototype.reset = function() {
\r
154 this.ptr = this.ns;
\r
159 * Defines a namespace on top of the current pointer position and places the pointer on it.
\r
160 * @param {string} namespace
\r
161 * @return {!ProtoBuf.Builder} this
\r
164 BuilderPrototype.define = function(namespace) {
\r
165 if (typeof namespace !== 'string' || !Lang.TYPEREF.test(namespace))
\r
166 throw Error("illegal namespace: "+namespace);
\r
167 namespace.split(".").forEach(function(part) {
\r
168 var ns = this.ptr.getChild(part);
\r
169 if (ns === null) // Keep existing
\r
170 this.ptr.addChild(ns = new Reflect.Namespace(this, this.ptr, part));
\r
177 * Creates the specified definitions at the current pointer position.
\r
178 * @param {!Array.<!Object>} defs Messages, enums or services to create
\r
179 * @returns {!ProtoBuf.Builder} this
\r
180 * @throws {Error} If a message definition is invalid
\r
183 BuilderPrototype.create = function(defs) {
\r
185 return this; // Nothing to create
\r
186 if (!Array.isArray(defs))
\r
189 if (defs.length === 0)
\r
191 defs = defs.slice();
\r
194 // It's quite hard to keep track of scopes and memory here, so let's do this iteratively.
\r
195 var stack = [defs];
\r
196 while (stack.length > 0) {
\r
197 defs = stack.pop();
\r
199 if (!Array.isArray(defs)) // Stack always contains entire namespaces
\r
200 throw Error("not a valid namespace: "+JSON.stringify(defs));
\r
202 while (defs.length > 0) {
\r
203 var def = defs.shift(); // Namespaces always contain an array of messages, enums and services
\r
205 if (Builder.isMessage(def)) {
\r
206 var obj = new Reflect.Message(this, this.ptr, def["name"], def["options"], def["isGroup"], def["syntax"]);
\r
211 Object.keys(def["oneofs"]).forEach(function(name) {
\r
212 obj.addChild(oneofs[name] = new Reflect.Message.OneOf(this, obj, name));
\r
217 def["fields"].forEach(function(fld) {
\r
218 if (obj.getChild(fld["id"]|0) !== null)
\r
219 throw Error("duplicate or invalid field id in "+obj.name+": "+fld['id']);
\r
220 if (fld["options"] && typeof fld["options"] !== 'object')
\r
221 throw Error("illegal field options in "+obj.name+"#"+fld["name"]);
\r
223 if (typeof fld["oneof"] === 'string' && !(oneof = oneofs[fld["oneof"]]))
\r
224 throw Error("illegal oneof in "+obj.name+"#"+fld["name"]+": "+fld["oneof"]);
\r
225 fld = new Reflect.Message.Field(this, obj, fld["rule"], fld["keytype"], fld["type"], fld["name"], fld["id"], fld["options"], oneof, def["syntax"]);
\r
227 oneof.fields.push(fld);
\r
231 // Push children to stack
\r
234 def["enums"].forEach(function(enm) {
\r
237 if (def["messages"])
\r
238 def["messages"].forEach(function(msg) {
\r
241 if (def["services"])
\r
242 def["services"].forEach(function(svc) {
\r
246 // Set extension ranges
\r
247 if (def["extensions"]) {
\r
248 if (typeof def["extensions"][0] === 'number') // pre 5.0.1
\r
249 obj.extensions = [ def["extensions"] ];
\r
251 obj.extensions = def["extensions"];
\r
254 // Create on top of current namespace
\r
255 this.ptr.addChild(obj);
\r
256 if (subObj.length > 0) {
\r
257 stack.push(defs); // Push the current level back
\r
258 defs = subObj; // Continue processing sub level
\r
260 this.ptr = obj; // And move the pointer to this namespace
\r
266 } else if (Builder.isEnum(def)) {
\r
268 obj = new Reflect.Enum(this, this.ptr, def["name"], def["options"], def["syntax"]);
\r
269 def["values"].forEach(function(val) {
\r
270 obj.addChild(new Reflect.Enum.Value(this, obj, val["name"], val["id"]));
\r
272 this.ptr.addChild(obj);
\r
274 } else if (Builder.isService(def)) {
\r
276 obj = new Reflect.Service(this, this.ptr, def["name"], def["options"]);
\r
277 Object.keys(def["rpc"]).forEach(function(name) {
\r
278 var mtd = def["rpc"][name];
\r
279 obj.addChild(new Reflect.Service.RPCMethod(this, obj, name, mtd["request"], mtd["response"], !!mtd["request_stream"], !!mtd["response_stream"], mtd["options"]));
\r
281 this.ptr.addChild(obj);
\r
283 } else if (Builder.isExtend(def)) {
\r
285 obj = this.ptr.resolve(def["ref"], true);
\r
287 def["fields"].forEach(function(fld) {
\r
288 if (obj.getChild(fld['id']|0) !== null)
\r
289 throw Error("duplicate extended field id in "+obj.name+": "+fld['id']);
\r
290 // Check if field id is allowed to be extended
\r
291 if (obj.extensions) {
\r
293 obj.extensions.forEach(function(range) {
\r
294 if (fld["id"] >= range[0] && fld["id"] <= range[1])
\r
298 throw Error("illegal extended field id in "+obj.name+": "+fld['id']+" (not within valid ranges)");
\r
300 // Convert extension field names to camel case notation if the override is set
\r
301 var name = fld["name"];
\r
302 if (this.options['convertFieldsToCamelCase'])
\r
303 name = ProtoBuf.Util.toCamelCase(name);
\r
304 // see #161: Extensions use their fully qualified name as their runtime key and...
\r
305 var field = new Reflect.Message.ExtensionField(this, obj, fld["rule"], fld["type"], this.ptr.fqn()+'.'+name, fld["id"], fld["options"]);
\r
306 // ...are added on top of the current namespace as an extension which is used for
\r
307 // resolving their type later on (the extension always keeps the original name to
\r
308 // prevent naming collisions)
\r
309 var ext = new Reflect.Extension(this, this.ptr, fld["name"], field);
\r
310 field.extension = ext;
\r
311 this.ptr.addChild(ext);
\r
312 obj.addChild(field);
\r
315 } else if (!/\.?google\.protobuf\./.test(def["ref"])) // Silently skip internal extensions
\r
316 throw Error("extended message "+def["ref"]+" is not defined");
\r
319 throw Error("not a valid definition: "+JSON.stringify(def));
\r
326 this.ptr = this.ptr.parent; // Namespace done, continue at parent
\r
328 this.resolved = false; // Require re-resolve
\r
329 this.result = null; // Require re-build
\r
334 * Propagates syntax to all children.
\r
335 * @param {!Object} parent
\r
338 function propagateSyntax(parent) {
\r
339 if (parent['messages']) {
\r
340 parent['messages'].forEach(function(child) {
\r
341 child["syntax"] = parent["syntax"];
\r
342 propagateSyntax(child);
\r
345 if (parent['enums']) {
\r
346 parent['enums'].forEach(function(child) {
\r
347 child["syntax"] = parent["syntax"];
\r
353 * Imports another definition into this builder.
\r
354 * @param {Object.<string,*>} json Parsed import
\r
355 * @param {(string|{root: string, file: string})=} filename Imported file name
\r
356 * @returns {!ProtoBuf.Builder} this
\r
357 * @throws {Error} If the definition or file cannot be imported
\r
360 BuilderPrototype["import"] = function(json, filename) {
\r
363 // Make sure to skip duplicate imports
\r
365 if (typeof filename === 'string') {
\r
367 if (ProtoBuf.Util.IS_NODE)
\r
368 filename = require("path")['resolve'](filename);
\r
369 if (this.files[filename] === true)
\r
370 return this.reset();
\r
371 this.files[filename] = true;
\r
373 } else if (typeof filename === 'object') { // Object with root, file.
\r
375 var root = filename.root;
\r
376 if (ProtoBuf.Util.IS_NODE)
\r
377 root = require("path")['resolve'](root);
\r
378 if (root.indexOf("\\") >= 0 || filename.file.indexOf("\\") >= 0)
\r
381 if (ProtoBuf.Util.IS_NODE)
\r
382 fname = require("path")['join'](root, filename.file);
\r
384 fname = root + delim + filename.file;
\r
385 if (this.files[fname] === true)
\r
386 return this.reset();
\r
387 this.files[fname] = true;
\r
392 if (json['imports'] && json['imports'].length > 0) {
\r
396 if (typeof filename === 'object') { // If an import root is specified, override
\r
398 this.importRoot = filename["root"]; resetRoot = true; // ... and reset afterwards
\r
399 importRoot = this.importRoot;
\r
400 filename = filename["file"];
\r
401 if (importRoot.indexOf("\\") >= 0 || filename.indexOf("\\") >= 0)
\r
404 } else if (typeof filename === 'string') {
\r
406 if (this.importRoot) // If import root is overridden, use it
\r
407 importRoot = this.importRoot;
\r
408 else { // Otherwise compute from filename
\r
409 if (filename.indexOf("/") >= 0) { // Unix
\r
410 importRoot = filename.replace(/\/[^\/]*$/, "");
\r
411 if (/* /file.proto */ importRoot === "")
\r
413 } else if (filename.indexOf("\\") >= 0) { // Windows
\r
414 importRoot = filename.replace(/\\[^\\]*$/, "");
\r
423 for (var i=0; i<json['imports'].length; i++) {
\r
424 if (typeof json['imports'][i] === 'string') { // Import file
\r
426 throw Error("cannot determine import root");
\r
427 var importFilename = json['imports'][i];
\r
428 if (importFilename === "google/protobuf/descriptor.proto")
\r
429 continue; // Not needed and therefore not used
\r
430 if (ProtoBuf.Util.IS_NODE)
\r
431 importFilename = require("path")['join'](importRoot, importFilename);
\r
433 importFilename = importRoot + delim + importFilename;
\r
434 if (this.files[importFilename] === true)
\r
435 continue; // Already imported
\r
436 if (/\.proto$/i.test(importFilename) && !ProtoBuf.DotProto) // If this is a light build
\r
437 importFilename = importFilename.replace(/\.proto$/, ".json"); // always load the JSON file
\r
438 var contents = ProtoBuf.Util.fetch(importFilename);
\r
439 if (contents === null)
\r
440 throw Error("failed to import '"+importFilename+"' in '"+filename+"': file not found");
\r
441 if (/\.json$/i.test(importFilename)) // Always possible
\r
442 this["import"](JSON.parse(contents+""), importFilename); // May throw
\r
444 this["import"](ProtoBuf.DotProto.Parser.parse(contents), importFilename); // May throw
\r
445 } else // Import structure
\r
447 this["import"](json['imports'][i]);
\r
448 else if (/\.(\w+)$/.test(filename)) // With extension: Append _importN to the name portion to make it unique
\r
449 this["import"](json['imports'][i], filename.replace(/^(.+)\.(\w+)$/, function($0, $1, $2) { return $1+"_import"+i+"."+$2; }));
\r
450 else // Without extension: Append _importN to make it unique
\r
451 this["import"](json['imports'][i], filename+"_import"+i);
\r
453 if (resetRoot) // Reset import root override when all imports are done
\r
454 this.importRoot = null;
\r
457 // Import structures
\r
459 if (json['package'])
\r
460 this.define(json['package']);
\r
461 if (json['syntax'])
\r
462 propagateSyntax(json);
\r
463 var base = this.ptr;
\r
464 if (json['options'])
\r
465 Object.keys(json['options']).forEach(function(key) {
\r
466 base.options[key] = json['options'][key];
\r
468 if (json['messages'])
\r
469 this.create(json['messages']),
\r
472 this.create(json['enums']),
\r
474 if (json['services'])
\r
475 this.create(json['services']),
\r
477 if (json['extends'])
\r
478 this.create(json['extends']);
\r
480 return this.reset();
\r
484 * Resolves all namespace objects.
\r
485 * @throws {Error} If a type cannot be resolved
\r
486 * @returns {!ProtoBuf.Builder} this
\r
489 BuilderPrototype.resolveAll = function() {
\r
490 // Resolve all reflected objects
\r
492 if (this.ptr == null || typeof this.ptr.type === 'object')
\r
493 return this; // Done (already resolved)
\r
495 if (this.ptr instanceof Reflect.Namespace) { // Resolve children
\r
497 this.ptr.children.forEach(function(child) {
\r
502 } else if (this.ptr instanceof Reflect.Message.Field) { // Resolve type
\r
504 if (!Lang.TYPE.test(this.ptr.type)) {
\r
505 if (!Lang.TYPEREF.test(this.ptr.type))
\r
506 throw Error("illegal type reference in "+this.ptr.toString(true)+": "+this.ptr.type);
\r
507 res = (this.ptr instanceof Reflect.Message.ExtensionField ? this.ptr.extension.parent : this.ptr.parent).resolve(this.ptr.type, true);
\r
509 throw Error("unresolvable type reference in "+this.ptr.toString(true)+": "+this.ptr.type);
\r
510 this.ptr.resolvedType = res;
\r
511 if (res instanceof Reflect.Enum) {
\r
512 this.ptr.type = ProtoBuf.TYPES["enum"];
\r
513 if (this.ptr.syntax === 'proto3' && res.syntax !== 'proto3')
\r
514 throw Error("proto3 message cannot reference proto2 enum");
\r
516 else if (res instanceof Reflect.Message)
\r
517 this.ptr.type = res.isGroup ? ProtoBuf.TYPES["group"] : ProtoBuf.TYPES["message"];
\r
519 throw Error("illegal type reference in "+this.ptr.toString(true)+": "+this.ptr.type);
\r
521 this.ptr.type = ProtoBuf.TYPES[this.ptr.type];
\r
523 // If it's a map field, also resolve the key type. The key type can be only a numeric, string, or bool type
\r
524 // (i.e., no enums or messages), so we don't need to resolve against the current namespace.
\r
525 if (this.ptr.map) {
\r
526 if (!Lang.TYPE.test(this.ptr.keyType))
\r
527 throw Error("illegal key type for map field in "+this.ptr.toString(true)+": "+this.ptr.keyType);
\r
528 this.ptr.keyType = ProtoBuf.TYPES[this.ptr.keyType];
\r
531 // If it's a repeated and packable field then proto3 mandates it should be packed by
\r
534 this.ptr.syntax === 'proto3' &&
\r
535 this.ptr.repeated && this.ptr.options.packed === undefined &&
\r
536 ProtoBuf.PACKABLE_WIRE_TYPES.indexOf(this.ptr.type.wireType) !== -1
\r
538 this.ptr.options.packed = true;
\r
541 } else if (this.ptr instanceof ProtoBuf.Reflect.Service.Method) {
\r
543 if (this.ptr instanceof ProtoBuf.Reflect.Service.RPCMethod) {
\r
544 res = this.ptr.parent.resolve(this.ptr.requestName, true);
\r
545 if (!res || !(res instanceof ProtoBuf.Reflect.Message))
\r
546 throw Error("Illegal type reference in "+this.ptr.toString(true)+": "+this.ptr.requestName);
\r
547 this.ptr.resolvedRequestType = res;
\r
548 res = this.ptr.parent.resolve(this.ptr.responseName, true);
\r
549 if (!res || !(res instanceof ProtoBuf.Reflect.Message))
\r
550 throw Error("Illegal type reference in "+this.ptr.toString(true)+": "+this.ptr.responseName);
\r
551 this.ptr.resolvedResponseType = res;
\r
552 } else // Should not happen as nothing else is implemented
\r
553 throw Error("illegal service type in "+this.ptr.toString(true));
\r
556 !(this.ptr instanceof ProtoBuf.Reflect.Message.OneOf) && // Not built
\r
557 !(this.ptr instanceof ProtoBuf.Reflect.Extension) && // Not built
\r
558 !(this.ptr instanceof ProtoBuf.Reflect.Enum.Value) // Built in enum
\r
560 throw Error("illegal object in namespace: "+typeof(this.ptr)+": "+this.ptr);
\r
562 return this.reset();
\r
566 * Builds the protocol. This will first try to resolve all definitions and, if this has been successful,
\r
567 * return the built package.
\r
568 * @param {(string|Array.<string>)=} path Specifies what to return. If omitted, the entire namespace will be returned.
\r
569 * @returns {!ProtoBuf.Builder.Message|!Object.<string,*>}
\r
570 * @throws {Error} If a type could not be resolved
\r
573 BuilderPrototype.build = function(path) {
\r
575 if (!this.resolved)
\r
577 this.resolved = true,
\r
578 this.result = null; // Require re-build
\r
579 if (this.result === null) // (Re-)Build
\r
580 this.result = this.ns.build();
\r
582 return this.result;
\r
583 var part = typeof path === 'string' ? path.split(".") : path,
\r
584 ptr = this.result; // Build namespace pointer (no hasChild etc.)
\r
585 for (var i=0; i<part.length; i++)
\r
587 ptr = ptr[part[i]];
\r
596 * Similar to {@link ProtoBuf.Builder#build}, but looks up the internal reflection descriptor.
\r
597 * @param {string=} path Specifies what to return. If omitted, the entire namespace wiil be returned.
\r
598 * @param {boolean=} excludeNonNamespace Excludes non-namespace types like fields, defaults to `false`
\r
599 * @returns {?ProtoBuf.Reflect.T} Reflection descriptor or `null` if not found
\r
601 BuilderPrototype.lookup = function(path, excludeNonNamespace) {
\r
602 return path ? this.ns.resolve(path, excludeNonNamespace) : this.ns;
\r
606 * Returns a string representation of this object.
\r
607 * @return {string} String representation as of "Builder"
\r
610 BuilderPrototype.toString = function() {
\r
614 // ----- Base classes -----
\r
615 // Exist for the sole purpose of being able to "... instanceof ProtoBuf.Builder.Message" etc.
\r
618 * @alias ProtoBuf.Builder.Message
\r
620 Builder.Message = function() {};
\r
623 * @alias ProtoBuf.Builder.Enum
\r
625 Builder.Enum = function() {};
\r
628 * @alias ProtoBuf.Builder.Message
\r
630 Builder.Service = function() {};
\r
634 })(ProtoBuf, ProtoBuf.Lang, ProtoBuf.Reflect);
\r