namespace.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. "use strict";
  2. module.exports = Namespace;
  3. // extends ReflectionObject
  4. var ReflectionObject = require("./object");
  5. ((Namespace.prototype = Object.create(ReflectionObject.prototype)).constructor = Namespace).className = "Namespace";
  6. var Field = require("./field"),
  7. util = require("./util");
  8. var Type, // cyclic
  9. Service,
  10. Enum;
  11. /**
  12. * Constructs a new namespace instance.
  13. * @name Namespace
  14. * @classdesc Reflected namespace.
  15. * @extends NamespaceBase
  16. * @constructor
  17. * @param {string} name Namespace name
  18. * @param {Object.<string,*>} [options] Declared options
  19. */
  20. /**
  21. * Constructs a namespace from JSON.
  22. * @memberof Namespace
  23. * @function
  24. * @param {string} name Namespace name
  25. * @param {Object.<string,*>} json JSON object
  26. * @returns {Namespace} Created namespace
  27. * @throws {TypeError} If arguments are invalid
  28. */
  29. Namespace.fromJSON = function fromJSON(name, json) {
  30. return new Namespace(name, json.options).addJSON(json.nested);
  31. };
  32. /**
  33. * Converts an array of reflection objects to JSON.
  34. * @memberof Namespace
  35. * @param {ReflectionObject[]} array Object array
  36. * @param {IToJSONOptions} [toJSONOptions] JSON conversion options
  37. * @returns {Object.<string,*>|undefined} JSON object or `undefined` when array is empty
  38. */
  39. function arrayToJSON(array, toJSONOptions) {
  40. if (!(array && array.length))
  41. return undefined;
  42. var obj = {};
  43. for (var i = 0; i < array.length; ++i)
  44. obj[array[i].name] = array[i].toJSON(toJSONOptions);
  45. return obj;
  46. }
  47. Namespace.arrayToJSON = arrayToJSON;
  48. /**
  49. * Tests if the specified id is reserved.
  50. * @param {Array.<number[]|string>|undefined} reserved Array of reserved ranges and names
  51. * @param {number} id Id to test
  52. * @returns {boolean} `true` if reserved, otherwise `false`
  53. */
  54. Namespace.isReservedId = function isReservedId(reserved, id) {
  55. if (reserved)
  56. for (var i = 0; i < reserved.length; ++i)
  57. if (typeof reserved[i] !== "string" && reserved[i][0] <= id && reserved[i][1] > id)
  58. return true;
  59. return false;
  60. };
  61. /**
  62. * Tests if the specified name is reserved.
  63. * @param {Array.<number[]|string>|undefined} reserved Array of reserved ranges and names
  64. * @param {string} name Name to test
  65. * @returns {boolean} `true` if reserved, otherwise `false`
  66. */
  67. Namespace.isReservedName = function isReservedName(reserved, name) {
  68. if (reserved)
  69. for (var i = 0; i < reserved.length; ++i)
  70. if (reserved[i] === name)
  71. return true;
  72. return false;
  73. };
  74. /**
  75. * Not an actual constructor. Use {@link Namespace} instead.
  76. * @classdesc Base class of all reflection objects containing nested objects. This is not an actual class but here for the sake of having consistent type definitions.
  77. * @exports NamespaceBase
  78. * @extends ReflectionObject
  79. * @abstract
  80. * @constructor
  81. * @param {string} name Namespace name
  82. * @param {Object.<string,*>} [options] Declared options
  83. * @see {@link Namespace}
  84. */
  85. function Namespace(name, options) {
  86. ReflectionObject.call(this, name, options);
  87. /**
  88. * Nested objects by name.
  89. * @type {Object.<string,ReflectionObject>|undefined}
  90. */
  91. this.nested = undefined; // toJSON
  92. /**
  93. * Cached nested objects as an array.
  94. * @type {ReflectionObject[]|null}
  95. * @private
  96. */
  97. this._nestedArray = null;
  98. }
  99. function clearCache(namespace) {
  100. namespace._nestedArray = null;
  101. return namespace;
  102. }
  103. /**
  104. * Nested objects of this namespace as an array for iteration.
  105. * @name NamespaceBase#nestedArray
  106. * @type {ReflectionObject[]}
  107. * @readonly
  108. */
  109. Object.defineProperty(Namespace.prototype, "nestedArray", {
  110. get: function() {
  111. return this._nestedArray || (this._nestedArray = util.toArray(this.nested));
  112. }
  113. });
  114. /**
  115. * Namespace descriptor.
  116. * @interface INamespace
  117. * @property {Object.<string,*>} [options] Namespace options
  118. * @property {Object.<string,AnyNestedObject>} [nested] Nested object descriptors
  119. */
  120. /**
  121. * Any extension field descriptor.
  122. * @typedef AnyExtensionField
  123. * @type {IExtensionField|IExtensionMapField}
  124. */
  125. /**
  126. * Any nested object descriptor.
  127. * @typedef AnyNestedObject
  128. * @type {IEnum|IType|IService|AnyExtensionField|INamespace}
  129. */
  130. // ^ BEWARE: VSCode hangs forever when using more than 5 types (that's why AnyExtensionField exists in the first place)
  131. /**
  132. * Converts this namespace to a namespace descriptor.
  133. * @param {IToJSONOptions} [toJSONOptions] JSON conversion options
  134. * @returns {INamespace} Namespace descriptor
  135. */
  136. Namespace.prototype.toJSON = function toJSON(toJSONOptions) {
  137. return util.toObject([
  138. "options" , this.options,
  139. "nested" , arrayToJSON(this.nestedArray, toJSONOptions)
  140. ]);
  141. };
  142. /**
  143. * Adds nested objects to this namespace from nested object descriptors.
  144. * @param {Object.<string,AnyNestedObject>} nestedJson Any nested object descriptors
  145. * @returns {Namespace} `this`
  146. */
  147. Namespace.prototype.addJSON = function addJSON(nestedJson) {
  148. var ns = this;
  149. /* istanbul ignore else */
  150. if (nestedJson) {
  151. for (var names = Object.keys(nestedJson), i = 0, nested; i < names.length; ++i) {
  152. nested = nestedJson[names[i]];
  153. ns.add( // most to least likely
  154. ( nested.fields !== undefined
  155. ? Type.fromJSON
  156. : nested.values !== undefined
  157. ? Enum.fromJSON
  158. : nested.methods !== undefined
  159. ? Service.fromJSON
  160. : nested.id !== undefined
  161. ? Field.fromJSON
  162. : Namespace.fromJSON )(names[i], nested)
  163. );
  164. }
  165. }
  166. return this;
  167. };
  168. /**
  169. * Gets the nested object of the specified name.
  170. * @param {string} name Nested object name
  171. * @returns {ReflectionObject|null} The reflection object or `null` if it doesn't exist
  172. */
  173. Namespace.prototype.get = function get(name) {
  174. return this.nested && this.nested[name]
  175. || null;
  176. };
  177. /**
  178. * Gets the values of the nested {@link Enum|enum} of the specified name.
  179. * This methods differs from {@link Namespace#get|get} in that it returns an enum's values directly and throws instead of returning `null`.
  180. * @param {string} name Nested enum name
  181. * @returns {Object.<string,number>} Enum values
  182. * @throws {Error} If there is no such enum
  183. */
  184. Namespace.prototype.getEnum = function getEnum(name) {
  185. if (this.nested && this.nested[name] instanceof Enum)
  186. return this.nested[name].values;
  187. throw Error("no such enum: " + name);
  188. };
  189. /**
  190. * Adds a nested object to this namespace.
  191. * @param {ReflectionObject} object Nested object to add
  192. * @returns {Namespace} `this`
  193. * @throws {TypeError} If arguments are invalid
  194. * @throws {Error} If there is already a nested object with this name
  195. */
  196. Namespace.prototype.add = function add(object) {
  197. if (!(object instanceof Field && object.extend !== undefined || object instanceof Type || object instanceof Enum || object instanceof Service || object instanceof Namespace))
  198. throw TypeError("object must be a valid nested object");
  199. if (!this.nested)
  200. this.nested = {};
  201. else {
  202. var prev = this.get(object.name);
  203. if (prev) {
  204. if (prev instanceof Namespace && object instanceof Namespace && !(prev instanceof Type || prev instanceof Service)) {
  205. // replace plain namespace but keep existing nested elements and options
  206. var nested = prev.nestedArray;
  207. for (var i = 0; i < nested.length; ++i)
  208. object.add(nested[i]);
  209. this.remove(prev);
  210. if (!this.nested)
  211. this.nested = {};
  212. object.setOptions(prev.options, true);
  213. } else
  214. throw Error("duplicate name '" + object.name + "' in " + this);
  215. }
  216. }
  217. this.nested[object.name] = object;
  218. object.onAdd(this);
  219. return clearCache(this);
  220. };
  221. /**
  222. * Removes a nested object from this namespace.
  223. * @param {ReflectionObject} object Nested object to remove
  224. * @returns {Namespace} `this`
  225. * @throws {TypeError} If arguments are invalid
  226. * @throws {Error} If `object` is not a member of this namespace
  227. */
  228. Namespace.prototype.remove = function remove(object) {
  229. if (!(object instanceof ReflectionObject))
  230. throw TypeError("object must be a ReflectionObject");
  231. if (object.parent !== this)
  232. throw Error(object + " is not a member of " + this);
  233. delete this.nested[object.name];
  234. if (!Object.keys(this.nested).length)
  235. this.nested = undefined;
  236. object.onRemove(this);
  237. return clearCache(this);
  238. };
  239. /**
  240. * Defines additial namespaces within this one if not yet existing.
  241. * @param {string|string[]} path Path to create
  242. * @param {*} [json] Nested types to create from JSON
  243. * @returns {Namespace} Pointer to the last namespace created or `this` if path is empty
  244. */
  245. Namespace.prototype.define = function define(path, json) {
  246. if (util.isString(path))
  247. path = path.split(".");
  248. else if (!Array.isArray(path))
  249. throw TypeError("illegal path");
  250. if (path && path.length && path[0] === "")
  251. throw Error("path must be relative");
  252. var ptr = this;
  253. while (path.length > 0) {
  254. var part = path.shift();
  255. if (ptr.nested && ptr.nested[part]) {
  256. ptr = ptr.nested[part];
  257. if (!(ptr instanceof Namespace))
  258. throw Error("path conflicts with non-namespace objects");
  259. } else
  260. ptr.add(ptr = new Namespace(part));
  261. }
  262. if (json)
  263. ptr.addJSON(json);
  264. return ptr;
  265. };
  266. /**
  267. * Resolves this namespace's and all its nested objects' type references. Useful to validate a reflection tree, but comes at a cost.
  268. * @returns {Namespace} `this`
  269. */
  270. Namespace.prototype.resolveAll = function resolveAll() {
  271. var nested = this.nestedArray, i = 0;
  272. while (i < nested.length)
  273. if (nested[i] instanceof Namespace)
  274. nested[i++].resolveAll();
  275. else
  276. nested[i++].resolve();
  277. return this.resolve();
  278. };
  279. /**
  280. * Recursively looks up the reflection object matching the specified path in the scope of this namespace.
  281. * @param {string|string[]} path Path to look up
  282. * @param {*|Array.<*>} filterTypes Filter types, any combination of the constructors of `protobuf.Type`, `protobuf.Enum`, `protobuf.Service` etc.
  283. * @param {boolean} [parentAlreadyChecked=false] If known, whether the parent has already been checked
  284. * @returns {ReflectionObject|null} Looked up object or `null` if none could be found
  285. */
  286. Namespace.prototype.lookup = function lookup(path, filterTypes, parentAlreadyChecked) {
  287. /* istanbul ignore next */
  288. if (typeof filterTypes === "boolean") {
  289. parentAlreadyChecked = filterTypes;
  290. filterTypes = undefined;
  291. } else if (filterTypes && !Array.isArray(filterTypes))
  292. filterTypes = [ filterTypes ];
  293. if (util.isString(path) && path.length) {
  294. if (path === ".")
  295. return this.root;
  296. path = path.split(".");
  297. } else if (!path.length)
  298. return this;
  299. // Start at root if path is absolute
  300. if (path[0] === "")
  301. return this.root.lookup(path.slice(1), filterTypes);
  302. // Test if the first part matches any nested object, and if so, traverse if path contains more
  303. var found = this.get(path[0]);
  304. if (found) {
  305. if (path.length === 1) {
  306. if (!filterTypes || filterTypes.indexOf(found.constructor) > -1)
  307. return found;
  308. } else if (found instanceof Namespace && (found = found.lookup(path.slice(1), filterTypes, true)))
  309. return found;
  310. // Otherwise try each nested namespace
  311. } else
  312. for (var i = 0; i < this.nestedArray.length; ++i)
  313. if (this._nestedArray[i] instanceof Namespace && (found = this._nestedArray[i].lookup(path, filterTypes, true)))
  314. return found;
  315. // If there hasn't been a match, try again at the parent
  316. if (this.parent === null || parentAlreadyChecked)
  317. return null;
  318. return this.parent.lookup(path, filterTypes);
  319. };
  320. /**
  321. * Looks up the reflection object at the specified path, relative to this namespace.
  322. * @name NamespaceBase#lookup
  323. * @function
  324. * @param {string|string[]} path Path to look up
  325. * @param {boolean} [parentAlreadyChecked=false] Whether the parent has already been checked
  326. * @returns {ReflectionObject|null} Looked up object or `null` if none could be found
  327. * @variation 2
  328. */
  329. // lookup(path: string, [parentAlreadyChecked: boolean])
  330. /**
  331. * Looks up the {@link Type|type} at the specified path, relative to this namespace.
  332. * Besides its signature, this methods differs from {@link Namespace#lookup|lookup} in that it throws instead of returning `null`.
  333. * @param {string|string[]} path Path to look up
  334. * @returns {Type} Looked up type
  335. * @throws {Error} If `path` does not point to a type
  336. */
  337. Namespace.prototype.lookupType = function lookupType(path) {
  338. var found = this.lookup(path, [ Type ]);
  339. if (!found)
  340. throw Error("no such type: " + path);
  341. return found;
  342. };
  343. /**
  344. * Looks up the values of the {@link Enum|enum} at the specified path, relative to this namespace.
  345. * Besides its signature, this methods differs from {@link Namespace#lookup|lookup} in that it throws instead of returning `null`.
  346. * @param {string|string[]} path Path to look up
  347. * @returns {Enum} Looked up enum
  348. * @throws {Error} If `path` does not point to an enum
  349. */
  350. Namespace.prototype.lookupEnum = function lookupEnum(path) {
  351. var found = this.lookup(path, [ Enum ]);
  352. if (!found)
  353. throw Error("no such Enum '" + path + "' in " + this);
  354. return found;
  355. };
  356. /**
  357. * Looks up the {@link Type|type} or {@link Enum|enum} at the specified path, relative to this namespace.
  358. * Besides its signature, this methods differs from {@link Namespace#lookup|lookup} in that it throws instead of returning `null`.
  359. * @param {string|string[]} path Path to look up
  360. * @returns {Type} Looked up type or enum
  361. * @throws {Error} If `path` does not point to a type or enum
  362. */
  363. Namespace.prototype.lookupTypeOrEnum = function lookupTypeOrEnum(path) {
  364. var found = this.lookup(path, [ Type, Enum ]);
  365. if (!found)
  366. throw Error("no such Type or Enum '" + path + "' in " + this);
  367. return found;
  368. };
  369. /**
  370. * Looks up the {@link Service|service} at the specified path, relative to this namespace.
  371. * Besides its signature, this methods differs from {@link Namespace#lookup|lookup} in that it throws instead of returning `null`.
  372. * @param {string|string[]} path Path to look up
  373. * @returns {Service} Looked up service
  374. * @throws {Error} If `path` does not point to a service
  375. */
  376. Namespace.prototype.lookupService = function lookupService(path) {
  377. var found = this.lookup(path, [ Service ]);
  378. if (!found)
  379. throw Error("no such Service '" + path + "' in " + this);
  380. return found;
  381. };
  382. // Sets up cyclic dependencies (called in index-light)
  383. Namespace._configure = function(Type_, Service_, Enum_) {
  384. Type = Type_;
  385. Service = Service_;
  386. Enum = Enum_;
  387. };