var _ = require('../util').lodash, Property = require('./property').Property, E = '', ANY = 'any', NULL = 'null', STRING = 'string', Variable; /** * The object representation of a Variable consists the variable value and type. It also optionally includes the `id` * and a friendly `name` of the variable. The `id` and the `name` of a variable is usually managed and used when a * variable is made part of a {@link VariableList} instance. * * @typedef {Object} Variable.definition * @property {*=} [value] - The value of the variable that will be stored and will be typecast to the `type` * set in the variable or passed along in this parameter. * @property {String=} [type] - The type of this variable from the list of types defined at {@link Variable.types}. * * @example * { * "id": "my-var-1", * "name": "MyFirstVariable", * "value": "Hello World", * "type": "string" * } */ _.inherit(( /** * A variable inside a collection is similar to variables in any programming construct. The variable has an * identifier name (provided by its id) and a value. A variable is optionally accompanied by a variable type. One * or more variables can be associated with a collection and can be referred from anywhere else in the collection * using the double-brace {{variable-id}} format. Properties can then use the `.toObjectResolved` function to * procure an object representation of the property with all variable references replaced by corresponding values. * * @constructor * @extends {Property} * @param {Variable.definition=} [definition] - Specify the initial value and type of the variable. */ Variable = function PostmanVariable (definition) { // this constructor is intended to inherit and as such the super constructor is required to be executed Variable.super_.apply(this, arguments); // check what is the property name for indexing this variable var indexer = this.constructor._postman_propertyIndexKey; _.assign(this, /** @lends Variable.prototype */ { /** * @type {Variable.types} */ type: ANY, /** * @type {*} */ value: undefined }); if (!_.isNil(definition)) { /** * The name of the variable. This is used for referencing this variable from other locations and scripts * * @type {String} * @name key * @memberOf Variable.prototype */ _.has(definition, indexer) && (this[indexer] = definition[indexer]); this.update(definition); } }), Property); _.assign(Variable.prototype, /** @lends Variable.prototype */ { /** * Gets the value of the variable. * * @returns {Variable.types} */ get () { return _.isFunction(this.value) ? this.castOut(this.value()) : this.castOut(this.value); }, /** * Sets the value of the variable. * * @param {*} value - */ set (value) { // @todo - figure out how secure is this! this.value = _.isFunction(value) ? value : this.castIn(value); }, /** * An alias of this.get and this.set. * * @param {*=} [value] - * @returns {*} */ valueOf (value) { arguments.length && this.set(value); return this.get(); }, /** * Returns the stringified value of the variable. * * @returns {String} */ toString () { var value = this.valueOf(); // returns String representation of null as it's a valid JSON type // refer: https://github.com/postmanlabs/postman-app-support/issues/8493 if (value === null) { return NULL; } // returns empty string if the value is undefined or does not implement // the toString method return (!_.isNil(value) && _.isFunction(value.toString)) ? value.toString() : E; }, /** * Typecasts a value to the {@link Variable.types} of this {@link Variable}. Returns the value of the variable * converted to the type specified in {@link Variable#type}. * * @param {*} value - * @returns {*} */ cast (value) { return this.castOut(value); }, /** * Typecasts a value to the {@link Variable.types} of this {@link Variable}. Returns the value of the variable * converted to the type specified in {@link Variable#type}. * * @private * @param {*} value - * @returns {*} */ castIn (value) { var handler = Variable.types[this.type] || Variable.types.any; return _.isFunction(handler) ? handler(value) : handler.in(value); }, /** * Typecasts a value from the {@link Variable.types} of this {@link Variable}. Returns the value of the variable * converted to the type specified in {@link Variable#type}. * * @private * @param {*} value - * @returns {*} */ castOut (value) { var handler = Variable.types[this.type] || Variable.types.any; return _.isFunction(handler) ? handler(value) : handler.out(value); }, /** * Sets or gets the type of the value. * * @param {String} typeName - * @param {Boolean} _noCast - * @returns {String} - returns the current type of the variable from the list of {@link Variable.types} */ valueType (typeName, _noCast) { !_.isNil(typeName) && (typeName = typeName.toString().toLowerCase()); // sanitize if (!Variable.types[typeName]) { return this.type || ANY; // @todo: throw new Error('Invalid variable type.'); } // set type if it is valid this.type = typeName; // 1. get the current value // 2. set the new type if it is valid and cast the stored value // 3. then set the interstitial value var interstitialCastValue; // do not touch value functions if (!(_noCast || _.isFunction(this.value))) { interstitialCastValue = this.get(); this.set(interstitialCastValue); interstitialCastValue = null; // just a precaution } return this.type; }, /** * Updates the type and value of a variable from an object or JSON definition of the variable. * * @param {Variable.definition} options - */ update (options) { if (!_.isObject(options)) { return; } // set type and value. // @note that we cannot update the key, once created during construction _.has(options, 'type') && this.valueType(options.type, _.has(options, 'value')); _.has(options, 'value') && this.set(options.value); _.has(options, 'system') && (this.system = options.system); _.has(options, 'disabled') && (this.disabled = options.disabled); } }); _.assign(Variable, /** @lends Variable */ { /** * Defines the name of this property for internal use. * * @private * @readOnly * @type {String} */ _postman_propertyName: 'Variable', /** * Specify the key to be used while indexing this object * * @private * @readOnly * @type {String} */ _postman_propertyIndexKey: 'key', /** * The possible supported types of a variable is defined here. The keys defined here are the possible values of * {@link Variable#type}. * * Additional variable types can be supported by adding the type-casting function to this enumeration. * * @enum {Function} * @readonly */ types: { /** * When a variable's `type` is set to "string", it ensures that {@link Variable#get} converts the value of the * variable to a string before returning the data. */ string: String, /** * A boolean type of variable can either be set to `true` or `false`. Any other value set is converted to * Boolean when procured from {@link Variable#get}. */ boolean: Boolean, /** * A "number" type variable ensures that the value is always represented as a number. A non-number type value * is returned as `NaN`. */ number: Number, /** * A "array" type value stores Array data format */ array: { /** * @param {Array} val - * @returns {String} */ in (val) { var value; try { // @todo: should we check if `val` is a valid Array or Array string? value = typeof val === STRING ? val : JSON.stringify(val); } catch (e) { value = NULL; } return value; }, /** * A "array" type value stores Array data format * * @param {String} val - * @returns {Object} */ out (val) { var value; try { value = JSON.parse(val); } catch (e) { value = undefined; } return Array.isArray(value) ? value : undefined; } }, /** * A "object" type value stores Object data format */ object: { /** * @param {Object} val - * @returns {String} */ in (val) { var value; try { // @todo: should we check if `val` is a valid JSON string? value = typeof val === STRING ? val : JSON.stringify(val); } catch (e) { value = NULL; } return value; }, /** * A "object" type value stores Object data format * * @param {String} val - * @returns {Object} */ out (val) { var value; try { value = JSON.parse(val); } catch (e) { value = undefined; } return (value instanceof Object && !Array.isArray(value)) ? value : undefined; } }, /** * Free-form type of a value. This is the default for any variable, unless specified otherwise. It ensures that * the variable can store data in any type and no conversion is done while using {@link Variable#get}. */ any: { /** * @param {*} val - * @returns {*} */ in (val) { return val; // pass through }, /** * @param {*} val - * @returns {*} */ out (val) { return val; // pass through } } }, /** * @param {*} obj - * @returns {Boolean} */ isVariable: function (obj) { return Boolean(obj) && ((obj instanceof Variable) || _.inSuperChain(obj.constructor, '_postman_propertyName', Variable._postman_propertyName)); } }); module.exports = { Variable };