469 lines
16 KiB
JavaScript
469 lines
16 KiB
JavaScript
var _ = require('../util').lodash,
|
|
Property = require('./property').Property,
|
|
PropertyBase = require('./property-base').PropertyBase,
|
|
VariableList = require('./variable-list').VariableList,
|
|
MutationTracker = require('./mutation-tracker').MutationTracker,
|
|
|
|
/**
|
|
* Known variable mutation types.
|
|
*
|
|
* @private
|
|
* @constant
|
|
* @type {Object}
|
|
*/
|
|
MUTATIONS = {
|
|
SET: 'set',
|
|
UNSET: 'unset'
|
|
},
|
|
|
|
VariableScope;
|
|
|
|
/**
|
|
* Environment and Globals of postman is exported and imported in a specified data structure. This data structure can be
|
|
* passed on to the constructor parameter of {@link VariableScope} or {@link VariableList} to instantiate an instance of
|
|
* the same with pre-populated values from arguments.
|
|
*
|
|
* @typedef VariableScope.definition
|
|
* @property {String} [id] ID of the scope
|
|
* @property {String} [name] A name of the scope
|
|
* @property {Array.<Variable.definition>} [values] A list of variables defined in an array in form of `{name:String,
|
|
* value:String}`
|
|
*
|
|
* @example <caption>JSON definition of a VariableScope (environment, globals, etc)</caption>
|
|
* {
|
|
* "name": "globals",
|
|
* "values": [{
|
|
* "key": "var-1",
|
|
* "value": "value-1"
|
|
* }, {
|
|
* "key": "var-2",
|
|
* "value": "value-2"
|
|
* }]
|
|
* }
|
|
*/
|
|
_.inherit((
|
|
|
|
/**
|
|
* VariableScope is a representation of a list of variables in Postman, such as the environment variables or the
|
|
* globals. Using this object, it is easy to perform operations on this list of variables such as get a variable or
|
|
* set a variable.
|
|
*
|
|
* @constructor
|
|
* @extends {Property}
|
|
*
|
|
* @param {VariableScope.definition} definition The constructor accepts an initial set of values for initialising
|
|
* the scope
|
|
* @param {Array<VariableList>=} layers Additional parent scopes to search for and resolve variables
|
|
*
|
|
* @example <caption>Load a environment from file, modify and save back</caption>
|
|
* var fs = require('fs'), // assuming NodeJS
|
|
* env,
|
|
* sum;
|
|
*
|
|
* // load env from file assuming it has initial data
|
|
* env = new VariableScope(JSON.parse(fs.readFileSync('./my-postman-environment.postman_environment').toString()));
|
|
*
|
|
* // get two variables and add them
|
|
* sum = env.get('one-var') + env.get('another-var');
|
|
*
|
|
* // save it back in environment and write to file
|
|
* env.set('sum', sum, 'number');
|
|
* fs.writeFileSync('./sum-of-vars.postman_environment', JSON.stringify(env.toJSON()));
|
|
*/
|
|
VariableScope = function PostmanVariableScope (definition, layers) {
|
|
// in case the definition is an array (legacy format) or existing as list, we convert to actual format
|
|
if (_.isArray(definition) || VariableList.isVariableList(definition)) {
|
|
definition = { values: definition };
|
|
}
|
|
|
|
// we accept parent scopes to increase search area. Here we normalize the argument to be an array
|
|
// so we can easily loop though them and add them to the instance.
|
|
layers && !_.isArray(layers) && (layers = [layers]);
|
|
|
|
// this constructor is intended to inherit and as such the super constructor is required to be executed
|
|
VariableScope.super_.call(this, definition);
|
|
|
|
var values = definition && definition.values, // access the values (need this var to reuse access)
|
|
|
|
// enable mutation tracking if `mutations` are in definition (restore the state)
|
|
// or is enabled through options
|
|
mutations = definition && definition.mutations,
|
|
ii,
|
|
i;
|
|
|
|
/**
|
|
* @memberof VariableScope.prototype
|
|
* @type {VariableList}
|
|
*/
|
|
this.values = new VariableList(this, VariableList.isVariableList(values) ? values.toJSON() : values);
|
|
// in above line, we clone the values if it is already a list. there is no point directly using the instance of
|
|
// a variable list since one cannot be created with a parent reference to begin with.
|
|
|
|
if (layers) {
|
|
this._layers = [];
|
|
|
|
for (i = 0, ii = layers.length; i < ii; i++) {
|
|
VariableList.isVariableList(layers[i]) && this._layers.push(layers[i]);
|
|
}
|
|
}
|
|
|
|
// restore previously tracked mutations
|
|
if (mutations) {
|
|
this.mutations = new MutationTracker(mutations);
|
|
}
|
|
}), Property);
|
|
|
|
/**
|
|
* @note Handling disabled and duplicate variables:
|
|
* | method | single enabled | single disabled | with duplicates |
|
|
* |--------|-------------------|-----------------|------------------------------------------------------------------- |
|
|
* | has | true | false | true (if last enabled) OR false (if all disabled) |
|
|
* | get | {Variable} | undefined | last enabled {Variable} OR undefined (if all disabled) |
|
|
* | set | update {Variable} | new {Variable} | update last enabled {Variable} OR new {Variable} (if all disabled) |
|
|
* | unset | delete {Variable} | noop | delete all enabled {Variable} |
|
|
*
|
|
* @todo Expected behavior of `unset` with duplicates:
|
|
* delete last enabled {Variable} and update the reference with last enabled in rest of the list.
|
|
* This requires unique identifier in the variable list for mutations to work correctly.
|
|
*/
|
|
_.assign(VariableScope.prototype, /** @lends VariableScope.prototype */ {
|
|
/**
|
|
* Defines whether this property instances requires an id
|
|
*
|
|
* @private
|
|
* @readOnly
|
|
* @type {String}
|
|
*/
|
|
_postman_propertyRequiresId: true,
|
|
|
|
/**
|
|
* @private
|
|
* @deprecated discontinued in v4.0
|
|
*/
|
|
variables: function () {
|
|
// eslint-disable-next-line max-len
|
|
throw new Error('`VariableScope#variables` has been discontinued, use `VariableScope#syncVariablesTo` instead.');
|
|
},
|
|
|
|
/**
|
|
* Converts a list of Variables into an object where key is `_postman_propertyIndexKey` and value is determined
|
|
* by the `valueOf` function
|
|
*
|
|
* @param {Boolean} excludeDisabled -
|
|
* @param {Boolean} caseSensitive -
|
|
* @returns {Object}
|
|
*/
|
|
toObject: function (excludeDisabled, caseSensitive) {
|
|
// if the scope has no layers, we simply export the contents of primary store
|
|
if (!this._layers) {
|
|
return this.values.toObject(excludeDisabled, caseSensitive);
|
|
}
|
|
|
|
var mergedLayers = {};
|
|
|
|
_.forEachRight(this._layers, function (layer) {
|
|
_.assign(mergedLayers, layer.toObject(excludeDisabled, caseSensitive));
|
|
});
|
|
|
|
return _.assign(mergedLayers, this.values.toObject(excludeDisabled, caseSensitive));
|
|
},
|
|
|
|
/**
|
|
* Determines whether one particular variable is defined in this scope of variables.
|
|
*
|
|
* @param {String} key - The name of the variable to check
|
|
* @returns {Boolean} - Returns true if an enabled variable with given key is present in current or parent scopes,
|
|
* false otherwise
|
|
*/
|
|
has: function (key) {
|
|
var variable = this.values.oneNormalizedVariable(key),
|
|
i,
|
|
ii;
|
|
|
|
// if a variable is disabled or does not exist in local scope,
|
|
// we search all the layers and return the first occurrence.
|
|
if ((!variable || variable.disabled === true) && this._layers) {
|
|
for (i = 0, ii = this._layers.length; i < ii; i++) {
|
|
variable = this._layers[i].oneNormalizedVariable(key);
|
|
if (variable && variable.disabled !== true) { break; }
|
|
}
|
|
}
|
|
|
|
return Boolean(variable && variable.disabled !== true);
|
|
},
|
|
|
|
/**
|
|
* Fetches a variable from the current scope or from parent scopes if present.
|
|
*
|
|
* @param {String} key - The name of the variable to get.
|
|
* @returns {*} The value of the specified variable across scopes.
|
|
*/
|
|
get: function (key) {
|
|
var variable = this.values.oneNormalizedVariable(key),
|
|
i,
|
|
ii;
|
|
|
|
// if a variable does not exist in local scope, we search all the layers and return the first occurrence.
|
|
if ((!variable || variable.disabled === true) && this._layers) {
|
|
for (i = 0, ii = this._layers.length; i < ii; i++) {
|
|
variable = this._layers[i].oneNormalizedVariable(key);
|
|
if (variable && variable.disabled !== true) { break; }
|
|
}
|
|
}
|
|
|
|
return (variable && variable.disabled !== true) ? variable.valueOf() : undefined;
|
|
},
|
|
|
|
/**
|
|
* Creates a new variable, or updates an existing one.
|
|
*
|
|
* @param {String} key - The name of the variable to set.
|
|
* @param {*} value - The value of the variable to be set.
|
|
* @param {Variable.types} [type] - Optionally, the value of the variable can be set to a type
|
|
*/
|
|
set: function (key, value, type) {
|
|
var variable = this.values.oneNormalizedVariable(key),
|
|
|
|
// create an object that will be used as setter
|
|
update = { key, value };
|
|
|
|
_.isString(type) && (update.type = type);
|
|
|
|
// If a variable by the name key exists, update it's value and return.
|
|
// @note adds new variable if existing is disabled. Disabled variables are not updated.
|
|
if (variable && !variable.disabled) {
|
|
variable.update(update);
|
|
}
|
|
else {
|
|
this.values.add(update);
|
|
}
|
|
|
|
// track the change if mutation tracking is enabled
|
|
this._postman_enableTracking && this.mutations.track(MUTATIONS.SET, key, value);
|
|
},
|
|
|
|
/**
|
|
* Removes the variable with the specified name.
|
|
*
|
|
* @param {String} key -
|
|
*/
|
|
unset: function (key) {
|
|
var lastDisabledVariable;
|
|
|
|
this.values.remove(function (variable) {
|
|
// bail out if variable name didn't match
|
|
if (variable.key !== key) {
|
|
return false;
|
|
}
|
|
|
|
// don't delete disabled variables
|
|
if (variable.disabled) {
|
|
lastDisabledVariable = variable;
|
|
|
|
return false;
|
|
}
|
|
|
|
// delete all enabled variables
|
|
return true;
|
|
});
|
|
|
|
// restore the reference with the last disabled variable
|
|
if (lastDisabledVariable) {
|
|
this.values.reference[key] = lastDisabledVariable;
|
|
}
|
|
|
|
// track the change if mutation tracking is enabled
|
|
this._postman_enableTracking && this.mutations.track(MUTATIONS.UNSET, key);
|
|
},
|
|
|
|
/**
|
|
* Removes *all* variables from the current scope. This is a destructive action.
|
|
*/
|
|
clear: function () {
|
|
var mutations = this.mutations;
|
|
|
|
// track the change if mutation tracking is enabled
|
|
// do this before deleting the keys
|
|
if (this._postman_enableTracking) {
|
|
this.values.each(function (variable) {
|
|
mutations.track(MUTATIONS.UNSET, variable.key);
|
|
});
|
|
}
|
|
|
|
this.values.clear();
|
|
},
|
|
|
|
/**
|
|
* Replace all variable names with their values in the given template.
|
|
*
|
|
* @param {String|Object} template - A string or an object to replace variables in
|
|
* @returns {String|Object} The string or object with variables (if any) substituted with their values
|
|
*/
|
|
replaceIn: function (template) {
|
|
if (_.isString(template) || _.isArray(template)) {
|
|
// convert template to object because replaceSubstitutionsIn only accepts objects
|
|
var result = Property.replaceSubstitutionsIn({ template }, _.concat(this.values, this._layers));
|
|
|
|
return result.template;
|
|
}
|
|
|
|
if (_.isObject(template)) {
|
|
return Property.replaceSubstitutionsIn(template, _.concat(this.values, this._layers));
|
|
}
|
|
|
|
return template;
|
|
},
|
|
|
|
/**
|
|
* Enable mutation tracking.
|
|
*
|
|
* @note: Would do nothing if already enabled.
|
|
* @note: Any previously tracked mutations would be reset when starting a new tracking session.
|
|
*
|
|
* @param {MutationTracker.definition} [options] Options for Mutation Tracker. See {@link MutationTracker}
|
|
*/
|
|
enableTracking: function (options) {
|
|
// enabled already, do nothing
|
|
if (this._postman_enableTracking) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Controls if mutation tracking is enabled
|
|
*
|
|
* @memberof VariableScope.prototype
|
|
*
|
|
* @private
|
|
* @property {Boolean}
|
|
*/
|
|
this._postman_enableTracking = true;
|
|
|
|
// we don't want to add more mutations to existing mutations
|
|
// that will lead to mutations not capturing the correct state
|
|
// so we reset this with the new instance
|
|
this.mutations = new MutationTracker(options);
|
|
},
|
|
|
|
/**
|
|
* Disable mutation tracking.
|
|
*/
|
|
disableTracking: function () {
|
|
// disable further tracking but keep the tracked mutations
|
|
this._postman_enableTracking = false;
|
|
},
|
|
|
|
/**
|
|
* Apply a mutation instruction on this variable scope.
|
|
*
|
|
* @private
|
|
* @param {String} instruction Instruction identifying the type of the mutation, e.g. `set`, `unset`
|
|
* @param {String} key -
|
|
* @param {*} value -
|
|
*/
|
|
applyMutation: function (instruction, key, value) {
|
|
// we know that `set` and `unset` are the only supported instructions
|
|
// and we know the parameter signature of both is the same as the items in a mutation
|
|
/* istanbul ignore else */
|
|
if (this[instruction]) {
|
|
this[instruction](key, value);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Using this function, one can sync the values of this variable list from a reference object.
|
|
*
|
|
* @private
|
|
* @param {Object} obj -
|
|
* @param {Boolean=} [track] -
|
|
* @returns {Object}
|
|
*/
|
|
syncVariablesFrom: function (obj, track) {
|
|
return this.values.syncFromObject(obj, track);
|
|
},
|
|
|
|
/**
|
|
* Transfer the variables in this scope to an object
|
|
*
|
|
* @private
|
|
* @param {Object=} [obj] -
|
|
* @returns {Object}
|
|
*/
|
|
syncVariablesTo: function (obj) {
|
|
return this.values.syncToObject(obj);
|
|
},
|
|
|
|
/**
|
|
* Convert this variable scope into a JSON serialisable object. Useful to transport or store, environment and
|
|
* globals as a whole.
|
|
*
|
|
* @returns {Object}
|
|
*/
|
|
toJSON: function () {
|
|
var obj = PropertyBase.toJSON(this);
|
|
|
|
// @todo - remove this when pluralisation is complete
|
|
if (obj.value) {
|
|
obj.values = obj.value;
|
|
delete obj.value;
|
|
}
|
|
|
|
// ensure that the concept of layers is not exported as JSON. JSON cannot retain references and this will end up
|
|
// being a pointless object post JSONification.
|
|
if (obj._layers) {
|
|
delete obj._layers;
|
|
}
|
|
|
|
// ensure that tracking flag is not serialized
|
|
// otherwise, it is very easy to let tracking trickle to many instances leading to a snowball effect
|
|
if (obj._postman_enableTracking) {
|
|
delete obj._postman_enableTracking;
|
|
}
|
|
|
|
return obj;
|
|
},
|
|
|
|
/**
|
|
* Adds a variable list to the current instance in order to increase the surface area of variable resolution.
|
|
* This enables consumers to search across scopes (eg. environment and globals).
|
|
*
|
|
* @private
|
|
* @param {VariableList} [list] -
|
|
*/
|
|
addLayer: function (list) {
|
|
if (!VariableList.isVariableList(list)) {
|
|
return;
|
|
}
|
|
|
|
!this._layers && (this._layers = []); // lazily initialize layers
|
|
this._layers.push(list);
|
|
}
|
|
});
|
|
|
|
_.assign(VariableScope, /** @lends VariableScope */ {
|
|
/**
|
|
* Defines the name of this property for internal use.
|
|
*
|
|
* @private
|
|
* @readOnly
|
|
* @type {String}
|
|
*
|
|
* @note that this is directly accessed only in case of VariableScope from _.findValue lodash util mixin
|
|
*/
|
|
_postman_propertyName: 'VariableScope',
|
|
|
|
/**
|
|
* Check whether an object is an instance of {@link VariableScope}.
|
|
*
|
|
* @param {*} obj -
|
|
* @returns {Boolean}
|
|
*/
|
|
isVariableScope: function (obj) {
|
|
return Boolean(obj) && ((obj instanceof VariableScope) ||
|
|
_.inSuperChain(obj.constructor, '_postman_propertyName', VariableScope._postman_propertyName));
|
|
}
|
|
});
|
|
|
|
module.exports = {
|
|
VariableScope
|
|
};
|