property-base.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. "use strict";
  2. var _ = require('../../lodash'), __PARENT = '__parent', PropertyBase; // constructor
  3. /**
  4. * @typedef PropertyBase.definition
  5. * @property {String|Description} [description]
  6. */
  7. /**
  8. * Base of all properties in Postman Collection. It defines the root for all standalone properties for postman
  9. * collection.
  10. *
  11. * @constructor
  12. * @param {PropertyBase.definition} definition
  13. */
  14. PropertyBase = function PropertyBase(definition) {
  15. // In case definition object is missing, there is no point moving forward. Also if the definition is basic string
  16. // we do not need to do anything with it.
  17. if (!definition || typeof definition === 'string') {
  18. return;
  19. }
  20. // call the meta extraction functions to create the object where all keys that are prefixed with underscore can be
  21. // stored. more details on that can be retrieved from the propertyExtractMeta function itself.
  22. // @todo: make this a closed function to do getter and setter which is non enumerable
  23. var src = definition && definition.info || definition,
  24. // meta = _(src).pickBy(PropertyBase.propertyIsMeta).mapKeys(PropertyBase.propertyUnprefixMeta).value();
  25. meta = _.value(_.mapKeys(_.pickBy(src, PropertyBase.propertyIsMeta, PropertyBase.propertyUnprefixMeta)));
  26. if (_.keys(meta).length) {
  27. this._ = _.isObject(this._) ? _.mergeDefined(this._, meta) : meta;
  28. }
  29. };
  30. _.assign(PropertyBase.prototype, /** @lends PropertyBase.prototype */ {
  31. /**
  32. * Invokes the given iterator for every parent in the parent chain of the given element.
  33. *
  34. * @param {Object} options - A set of options for the parent chain traversal.
  35. * @param {?Boolean} [options.withRoot=false] - Set to true to include the collection object as well.
  36. * @param {Function} iterator - The function to call for every parent in the ancestry chain.
  37. * @todo Cache the results
  38. */
  39. forEachParent: function (options, iterator) {
  40. _.isFunction(options) && (iterator = options, options = {});
  41. if (!_.isFunction(iterator) || !_.isObject(options)) {
  42. return;
  43. }
  44. var parent = this.parent(), grandparent = parent && _.isFunction(parent.parent) && parent.parent();
  45. while (parent && (grandparent || options.withRoot)) {
  46. iterator(parent);
  47. parent = grandparent;
  48. grandparent = grandparent && _.isFunction(grandparent.parent) && grandparent.parent();
  49. }
  50. },
  51. /**
  52. * Tries to find the given property locally, and then proceeds to lookup in each parent,
  53. * going up the chain as necessary. Lookup will continue until `customizer` returns a truthy value. If used
  54. * without a customizer, the lookup will stop at the first parent that contains the property.
  55. *
  56. * @param {String} property
  57. * @param {Function} [customizer]
  58. *
  59. * @returns {*|undefined}
  60. */
  61. findInParents: function (property, customizer) {
  62. var owner = this.findParentContaining(property, customizer);
  63. return owner ? owner[property] : undefined;
  64. },
  65. /**
  66. * Looks up the closest parent which has a truthy value for the given property. Lookup will continue
  67. * until `customizer` returns a truthy value. If used without a customizer,
  68. * the lookup will stop at the first parent that contains the property.
  69. *
  70. * @param {String} property
  71. * @param {Function} [customizer]
  72. *
  73. * @returns {PropertyBase|undefined}
  74. * @private
  75. */
  76. findParentContaining: function (property, customizer) {
  77. var parent = this;
  78. // if customizer is present test with it
  79. if (customizer) {
  80. customizer = customizer.bind(this);
  81. do {
  82. // else check for existence
  83. if (customizer(parent)) {
  84. return parent;
  85. }
  86. parent = parent.__parent;
  87. } while (parent);
  88. }
  89. // else check for existence
  90. else {
  91. do {
  92. if (parent[property]) {
  93. return parent;
  94. }
  95. parent = parent.__parent;
  96. } while (parent);
  97. }
  98. },
  99. /**
  100. * Returns the JSON representation of a property, which conforms to the way it is defined in a collection.
  101. * You can use this method to get the instantaneous representation of any property, including a {@link Collection}.
  102. */
  103. toJSON: function () {
  104. return _.reduce(this, function (accumulator, value, key) {
  105. if (value === undefined) { // true/false/null need to be preserved.
  106. return accumulator;
  107. }
  108. // Handle plurality of PropertyLists in the SDK vs the exported JSON.
  109. // Basically, removes the trailing "s" from key if the value is a property list.
  110. // eslint-disable-next-line max-len
  111. if (value && value._postman_propertyIsList && !value._postman_proprtyIsSerialisedAsPlural && _.endsWith(key, 's')) {
  112. key = key.slice(0, -1);
  113. }
  114. // Handle 'PropertyBase's
  115. if (value && _.isFunction(value.toJSON)) {
  116. accumulator[key] = value.toJSON();
  117. return accumulator;
  118. }
  119. // Handle Strings
  120. if (_.isString(value)) {
  121. accumulator[key] = value;
  122. return accumulator;
  123. }
  124. // Everything else
  125. accumulator[key] = _.cloneElement(value);
  126. return accumulator;
  127. }, {});
  128. },
  129. /**
  130. * Returns the meta keys associated with the property
  131. *
  132. * @returns {*}
  133. */
  134. meta: function () {
  135. return arguments.length ? _.pick(this._, Array.prototype.slice.apply(arguments)) : _.cloneDeep(this._);
  136. },
  137. /**
  138. * Returns the parent of item
  139. *
  140. * @returns {*|undefined}
  141. */
  142. parent: function () {
  143. // @todo return grandparent only if it is a list
  144. return this && this.__parent && (this.__parent.__parent || this.__parent) || undefined;
  145. },
  146. /**
  147. * Accepts an object and sets it as the parent of the current property.
  148. *
  149. * @param {Object} parent The object to set as parent.
  150. * @private
  151. */
  152. setParent: function (parent) {
  153. _.assignHidden(this, __PARENT, parent);
  154. }
  155. });
  156. _.assign(PropertyBase, /** @lends PropertyBase */ {
  157. /**
  158. * Defines the name of this property for internal use.
  159. * @private
  160. * @readOnly
  161. * @type {String}
  162. */
  163. _postman_propertyName: 'PropertyBase',
  164. /**
  165. * Filter function to check whether a key starts with underscore or not. These usually are the meta properties. It
  166. * returns `true` if the criteria is matched.
  167. *
  168. * @param {*} value
  169. * @param {String} key
  170. *
  171. * @returns {boolean}
  172. */
  173. propertyIsMeta: function (value, key) {
  174. return _.startsWith(key, '_') && (key !== '_');
  175. },
  176. /**
  177. * Map function that removes the underscore prefix from an object key.
  178. *
  179. * @param {*} value
  180. * @param {String} key
  181. *
  182. * @returns {String}
  183. */
  184. propertyUnprefixMeta: function (value, key) {
  185. return _.trimStart(key, '_');
  186. },
  187. /**
  188. * Static function which allows calling toJSON() on any object.
  189. *
  190. * @param {Object} obj
  191. * @returns {*}
  192. */
  193. toJSON: function (obj) {
  194. return PropertyBase.prototype.toJSON.call(obj);
  195. }
  196. });
  197. module.exports = {
  198. PropertyBase: PropertyBase
  199. };