feat: Created a mini nodeJS server with NewMan for testing without PostMan GUI.

This will mimic a run in a CD/CI environment or docker container.
This commit is contained in:
Simon Priet
2021-09-08 14:01:19 +02:00
parent 5fbd7c88fa
commit e69a613a37
5610 changed files with 740417 additions and 3 deletions

View File

@@ -0,0 +1,84 @@
var _ = require('../util').lodash,
PropertyList = require('./property-list').PropertyList,
Url = require('./url').Url,
Certificate = require('./certificate').Certificate,
CertificateList;
_.inherit((
/**
* @constructor
* @extends {PropertyList}
*
* @param {Object} parent -
* @param {Array} list - The list of certificate representations
*
* @example <caption>Create a new CertificateList</caption>
* var CertificateList = require('postman-collection').CertificateList,
* certificateList = new CertificateList({}, [
* {
* name: 'my certificate for example.com',
* matches: ['https://example.com/*'],
* key: { src: '/path/to/key/file' },
* cert: { src: '/path/to/certificate/file' }
* },
* {
* name: 'my certificate for example2.com',
* matches: ['https://example2.com/*'],
* key: { src: '/path/to/key/file' },
* cert: { src: '/path/to/key/file' }
* }
* ]);
*/
CertificateList = function (parent, list) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
CertificateList.super_.call(this, Certificate, parent, list);
}), PropertyList);
_.assign(CertificateList.prototype, /** @lends CertificateList.prototype */ {
/**
* Matches the given url against the member certificates' allowed matches
* and returns the certificate that can be used for the url.
*
* @param {String} url The url to find the certificate for
* @returns {Certificate.definition=} The matched certificate
*/
resolveOne (url) {
// url must be either string or an instance of url.
if (!_.isString(url) && !Url.isUrl(url)) {
return;
}
// find a certificate that can be applied to the url
return this.find(function (certificate) {
return certificate.canApplyTo(url);
});
}
});
_.assign(CertificateList, /** @lends CertificateList */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'CertificateList',
/**
* Checks if the given object is a CertificateList
*
* @param {*} obj -
* @returns {Boolean}
*/
isCertificateList: function (obj) {
return Boolean(obj) && ((obj instanceof CertificateList) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', CertificateList._postman_propertyName));
}
});
module.exports = {
CertificateList
};

View File

@@ -0,0 +1,208 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
PropertyBase = require('./property-base').PropertyBase,
Url = require('./url').Url,
UrlMatchPatternList = require('../url-pattern/url-match-pattern-list').UrlMatchPatternList,
STRING = 'string',
HTTPS = 'https',
Certificate;
/**
* The following is the object representation accepted as param for the Certificate constructor.
* Also the type of the object returned when {@link Property#toJSON} or {@link Property#toObjectResolved} is called on a
* Certificate instance.
*
* @typedef Certificate.definition
* @property {String} [name] A name for the certificate
* @property {Array} [matches] A list of match patterns
* @property {{ src: (String) }} [key] Object with path on the file system for private key file, as src
* @property {{ src: (String) }} [cert] Object with path on the file system for certificate file, as src
* @property {String} [passphrase] The passphrase for the certificate key
*
* @example <caption>JSON definition of an example certificate object</caption>
* {
* "name": "My certificate for example.com",
* "matches": ["https://example.com/*"],
* "key": { "src": "/path/to/key" },
* "cert": { "src": "/User/path/to/certificate" },
* "passphrase": "iampassphrase"
* }
*/
_.inherit((
/**
* A Certificate definition that represents the ssl certificate
* to be used for an url.
* Properties can then use the `.toObjectResolved` function to procure an object representation of the property with
* all the variable references replaced by corresponding values.
*
* @constructor
* @extends {Property}
*
* @param {Certificate.definition=} [options] Object with matches, key, cert and passphrase
*
* @example <caption> Create a new Certificate</caption>
*
* var Certificate = require('postman-collection').Certificate,
* certificate = new Certificate({
* name: 'Certificate for example.com',
* matches: ['example.com'],
* key: { src: '/User/path/to/certificate/key' },
* cert: { src: '/User/path/to/certificate' },
* passphrase: 'iampassphrase'
* });
*/
Certificate = function Certificate (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Certificate.super_.apply(this, arguments);
this.update(options);
}), Property);
_.assign(Certificate.prototype, /** @lends Certificate.prototype */ {
/**
* Ensure all object have id
*
* @private
*/
_postman_propertyRequiresId: true,
/**
* Updates the certificate with the given properties.
*
* @param {Certificate.definition=} [options] Object with matches, key, cert and passphrase
*/
update: function (options) {
// return early if options is empty or invalid
if (!_.isObject(options)) {
return;
}
_.mergeDefined(this, /** @lends Certificate.prototype */ {
/**
* Unique identifier
*
* @type {String}
*/
id: options.id,
/**
* Name for user reference
*
* @type {String}
*/
name: options.name,
/**
* List of match pattern
*
* @type {UrlMatchPatternList}
*/
matches: options.matches && new UrlMatchPatternList({}, options.matches),
/**
* Private Key
*
* @type {{ src: (string) }}
*/
key: _.isObject(options.key) ? options.key : { src: options.key },
/**
* Certificate
*
* @type {{ src: (string) }}
*/
cert: _.isObject(options.cert) ? options.cert : { src: options.cert },
/**
* PFX or PKCS12 Certificate
*
* @type {{ src: (string) }}
*/
pfx: _.isObject(options.pfx) ? options.pfx : { src: options.pfx },
/**
* passphrase
*
* @type {Object}
*/
passphrase: options.passphrase
});
},
/**
* Checks if the certificate can be applied to a given url
*
* @param {String|Url} url The url string for which the certificate is checked for match.
*/
canApplyTo: function (url) {
if (_.isEmpty(url)) {
return false;
}
// convert url strings to Url
(typeof url === STRING) && (url = new Url(url));
// this ensures we don't proceed any further for any protocol
// that is not https
if (url.protocol !== HTTPS) {
return false;
}
// test the input url against allowed matches
return this.matches.test(url);
},
/**
* Allows the serialization of a {@link Certificate}
*
* This is overridden, in order to ensure that certificate contents are not accidentally serialized,
* which can be a security risk.
*/
toJSON: function () {
var obj = PropertyBase.toJSON(this);
_.unset(obj, 'key.value');
_.unset(obj, 'cert.value');
_.unset(obj, 'pfx.value');
return obj;
}
});
_.assign(Certificate, /** @lends Certificate */ {
/**
* Defines the name of this property for internal use
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Certificate',
/**
* Specify the key to be used while indexing this object
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyIndexKey: 'id',
/**
* Checks if the given object is a Certificate
*
* @param {*} obj -
* @returns {Boolean}
*/
isCertificate: function (obj) {
return Boolean(obj) && ((obj instanceof Certificate) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Certificate._postman_propertyName));
}
});
module.exports = {
Certificate
};

View File

@@ -0,0 +1,242 @@
var _ = require('../util').lodash,
ItemGroup = require('./item-group').ItemGroup,
VariableList = require('./variable-list').VariableList,
Version = require('./version').Version,
Collection, // constructor
SCHEMA_URL = 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json';
/**
* The following is the object structure accepted as constructor parameter while calling `new Collection(...)`. It is
* also the structure exported when {@link Property#toJSON} or {@link Property#toObjectResolved} is called on a
* collection instance.
*
* @typedef Collection.definition
*
* @property {Object=} [info] The meta information regarding the collection is provided as the `info` object.
* @property {String=} [info.id] Every collection is identified by the unique value of this property. It is recommended
* that you maintain the same id since changing the id usually implies that is a different collection than it was
* originally.
* @property {String=} [info.name] A collection's friendly name is defined by this property. You would want to set this
* field to a value that would allow you to easily identify this collection among a bunch of other collections.
* @property {String=} [info.version] Postman allows you to version your collections as they grow, and this field holds
* the version number. While optional, it is recommended that you use this field to its fullest extent.
* @property {Array<(Item.definition|ItemGroup.definition)>} [item] Items are the basic unit for a Postman collection.
* You can think of them as corresponding to a single API endpoint. Each Item has one request and may have multiple API
* responses associated with it.
* @property {Variable.definition=} [variable] Collection variables allow you to define a set of variables,
* that are a *part of the collection*, as opposed to environments, which are separate entities.
* @property {RequestAuth.definition=} [auth] Collection auth allows you to define an authentication,
* that *applies to all items* in the collection.
* @property {Array<Event.definition>=} [event] Postman allows you to configure scripts to run when specific events
* occur.
* @property {String|Version.definition=} [version] Version of the collection expressed in [semver](http://semver.org/)
* format.
*
* @see {ItemGroup.definition} - Since `Collection` inherits {@link ItemGroup}, the properties supported by ItemGroup
* are applicable as well.
*
* @example <caption>JSON definition of an example collection</caption>
* {
* "info": {
* "name": "My Postman Collection",
* "version": "1.0.0"
* }
* "item": [{
* "request": "{{base-url}}/get"
* }],
* "variables": [{
* "id": "base-url",
* "value": "https://postman-echo.com"
* }]
* }
*/
_.inherit((
/**
* Create or load an instance of [Postman Collection](https://www.getpostman.com/docs/collections) as a JavaScript
* object that can be manipulated easily.
*
* A collection lets you group individual requests together. These requests can be further organized into folders to
* accurately mirror your API. Requests can also store sample responses when saved in a collection. You can add
* metadata like name and description too so that all the information that a developer needs to use your API is
* available easily.
*
* @constructor
* @extends {ItemGroup}
*
* @param {Collection.definition=} [definition] - Pass the initial definition of the collection (name, id, etc) as
* the `definition` parameter. The definition object is structured exactly as the collection format as defined in
* [https://www.schema.getpostman.com/](https://www.schema.getpostman.com/). This parameter is optional. That
* implies that you can create an empty instance of collection and add requests and other properties in order to
* build a new collection.
* @param {Array<Object>=} [environments] - The collection instance constructor accepts the second parameter as an
* array of environment objects. Environments objects store variable definitions that are inherited by
* {@link Collection#variables}. These environment variables are usually the ones that are exported from the Postman
* App to use them with different collections. Refer to Postman
* [documentation on environment variables](https://www.getpostman.com/docs/environments).
*
* @example <caption>Load a Collection JSON file from disk</caption>
* var fs = require('fs'), // needed to read JSON file from disk
* pretty = function (obj) { // function to neatly log the collection object to console
* return require('util').inspect(obj, {colors: true});
* },
* Collection = require('postman-collection').Collection,
* myCollection;
*
* // Load a collection to memory from a JSON file on disk (say, sample-collection.json)
* myCollection = new Collection(JSON.stringify(fs.readFileSync('sample-collection.json').toString()));
*
* // log items at root level of the collection
* console.log(pretty(myCollection));
*
* @example <caption>Create a blank collection and write to file</caption>
* var fs = require('fs'),
* Collection = require('postman-collection').Collection,
* mycollection;
*
* myCollection = new Collection({
* info: {
* name: "my Collection"
* }
* });
*
* // log the collection to console to see its contents
* fs.writeFileSync('myCollection.postman_collection', JSON.stringify(myCollection, null, 2));
*/
Collection = function PostmanCollection (definition, environments) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Collection.super_.call(this, definition);
_.assign(this, /** @lends Collection.prototype */ {
/**
* The `variables` property holds a list of variables that are associated with a Collection. These variables
* are stored within a collection so that they can be re-used and replaced in rest of the collection. For
* example, if one has a variable named `port` with value `8080`, then one can write a request {@link Url}
* as `http://localhost:{{port}}/my/endpoint` and that will be replaced to form
* `http://localhost:8080/my/endpoint`. **Collection Variables** are like
* [environment variables](https://www.getpostman.com/docs/environments), but stored locally within a
* collection.
*
* @type {VariableList}
*
* @example <caption>Creating a collection with variables</caption>
* var fs = require('fs'),
* Collection = require('postman-collection').Collection,
* mycollection;
*
* // Create a new empty collection.
* myCollection = new Collection();
*
* // Add a variable to the collection
* myCollection.variables.add({
* id: 'apiBaseUrl',
* value: 'http://timeapi.org',
* type: 'string'
* });
*
* //Add a request that uses the variable that we just added.
* myCollection.items.add({
* id: 'utc-time-now',
* name: 'Get the current time in UTC',
* request: '{{apiBaseUrl}}/utc/now'
* });
*/
variables: new VariableList(this, definition && definition.variable, environments),
/**
* The `version` key in collection is used to express the version of the collection. It is useful in either
* tracking development iteration of an API server or the version of an API itself. It can also be used to
* represent the number of iterations of the collection as it is updated through its lifetime.
*
* Version is expressed in [semver](http://semver.org/) format.
*
* @type {Version}
* @optional
*
* @see {@link http://semver.org/}
*/
version: (definition && definition.info && definition.info.version) ?
new Version(definition.info.version) : undefined
});
}), ItemGroup);
_.assign(Collection.prototype, /** @lends Collection.prototype */ {
/**
* Using this function, one can sync the values of collection variables from a reference object.
*
* @param {Object} obj -
* @param {Boolean=} [track] -
*
* @returns {Object}
*/
syncVariablesFrom (obj, track) {
return this.variables.syncFromObject(obj, track);
},
/**
* Transfer the variables in this scope to an object
*
* @param {Object=} [obj] -
*
* @returns {Object}
*/
syncVariablesTo (obj) {
return this.variables.syncToObject(obj);
},
/**
* Convert the collection to JSON compatible plain object
*
* @returns {Object}
*/
toJSON () {
var json = ItemGroup.prototype.toJSON.apply(this);
// move ids and stuff from root level to `info` object
json.info = {
_postman_id: this.id,
name: this.name,
version: this.version,
schema: SCHEMA_URL
};
delete json.id;
delete json.name;
delete json.version;
if (_.has(json, 'description')) {
json.info.description = this.description;
delete json.description;
}
return json;
}
});
_.assign(Collection, /** @lends Collection */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Collection',
/**
* Check whether an object is an instance of {@link ItemGroup}.
*
* @param {*} obj -
* @returns {Boolean}
*/
isCollection: function (obj) {
return Boolean(obj) && ((obj instanceof Collection) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Collection._postman_propertyName));
}
});
module.exports = {
Collection
};

View File

@@ -0,0 +1,49 @@
var _ = require('../util').lodash,
PropertyList = require('./property-list').PropertyList,
Cookie = require('./cookie').Cookie,
CookieList;
_.inherit((
/**
* Contains a list of header elements
*
* @constructor
* @param {Object} parent -
* @param {Object[]} cookies -
* @extends {PropertyList}
*/
CookieList = function (parent, cookies) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
CookieList.super_.call(this, Cookie, parent, cookies);
}), PropertyList);
// _.assign(CookieList.prototype, /** @lends CookieList.prototype */ {
// });
_.assign(CookieList, /** @lends CookieList */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'CookieList',
/**
* Checks if the given object is a CookieList
*
* @param {*} obj -
* @returns {Boolean}
*/
isCookieList: function (obj) {
return Boolean(obj) && ((obj instanceof CookieList) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', CookieList._postman_propertyName));
}
});
module.exports = {
CookieList
};

View File

@@ -0,0 +1,426 @@
var _ = require('../util').lodash,
PropertyBase = require('./property-base').PropertyBase,
PropertyList = require('./property-list').PropertyList,
E = '',
EQ = '=',
PAIR_SPLIT_REGEX = /; */,
COOKIES_SEPARATOR = '; ',
/**
* Enum for all the Cookie attributes.
*
* @private
* @readonly
* @enum {string} CookieAttributes
*/
cookieAttributes = {
httponly: 'httpOnly',
secure: 'secure',
domain: 'domain',
path: 'path',
'max-age': 'maxAge',
session: 'session',
expires: 'expires'
},
Cookie;
/**
* The following is the object structure accepted as constructor parameter while calling `new Cookie(...)`. It is
* also the structure exported when {@link Property#toJSON} or {@link Property#toObjectResolved} is called on a
* Cookie instance.
*
* @typedef Cookie.definition
*
* @property {String=} [key] The name of the cookie. Some call it the "name".
* @property {String=} [value] The value stored in the Cookie
* @property {String=} [expires] Expires sets an expiry date for when a cookie gets deleted. It should either be a
* date object or timestamp string of date.
* @property {Number=} [maxAge] Max-age sets the time in seconds for when a cookie will be deleted.
* @property {String=} [domain] Indicates the domain(s) for which the cookie should be sent.
* @property {String=} [path] Limits the scope of the cookie to a specified path, e.g: "/accounts"
* @property {Boolean=} [secure] A secure cookie will only be sent to the server when a request is made using SSL and
* the HTTPS protocol.
* The idea that the contents of the cookie are of high value and could be potentially damaging to transmit
* as clear text.
* @property {Boolean=} [httpOnly] The idea behind HTTP-only cookies is to instruct a browser that a cookie should never
* be accessible via JavaScript through the document.cookie property. This feature was designed as a security measure
* to help prevent cross-site scripting (XSS) attacks perpetrated by stealing cookies via JavaScript.
* @property {Boolean=} [hostOnly] Indicates that this cookie is only valid for the given domain (and not its parent or
* child domains.)
* @property {Boolean=} [session] Indicates whether this is a Session Cookie. (A transient cookie, which is deleted at
* the end of an HTTP session.)
* @property {Array=} [extensions] Any extra attributes that are extensions to the original Cookie specification can be
* specified here.
* @property {String} [extensions[].key] Name of the extension.
* @property {String} [extensions[].value] Value of the extension
*
* @example <caption>JSON definition of an example cookie</caption>
* {
* "key": "my-cookie-name",
* "expires": "1464769543832",
* // UNIX timestamp, in *milliseconds*
* "maxAge": "300",
* // In seconds. In this case, the Cookie is valid for 5 minutes
* "domain": "something.example.com",
* "path": "/",
* "secure": false,
* "httpOnly": true,
* "session": false,
* "value": "my-cookie-value",
* "extensions": [{
* "key": "Priority",
* "value": "HIGH"
* }]
* }
*/
_.inherit((
/**
* A Postman Cookie definition that comprehensively represents an HTTP Cookie.
*
* @constructor
* @extends {PropertyBase}
*
* @param {Cookie.definition} [options] Pass the initial definition of the Cookie.
* @example <caption>Create a new Cookie</caption>
* var Cookie = require('postman-collection').Cookie,
* myCookie = new Cookie({
* name: 'my-cookie-name',
* expires: '1464769543832', // UNIX timestamp, in *milliseconds*
* maxAge: '300', // In seconds. In this case, the Cookie is valid for 5 minutes
* domain: 'something.example.com',
* path: '/',
* secure: false,
* httpOnly: true,
* session: false,
* value: 'my-cookie-value',
* extensions: [{
* key: 'Priority',
* value: 'HIGH'
* }]
* });
*
* @example <caption>Parse a Cookie Header</caption>
* var Cookie = require('postman-collection').Cookie,
* rawHeader = 'myCookie=myValue;Path=/;Expires=Sun, 04-Feb-2018 14:18:27 GMT;Secure;HttpOnly;Priority=HIGH'
* myCookie = new Cookie(rawHeader);
*
* console.log(myCookie.toJSON());
*/
Cookie = function PostmanCookie (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Cookie.super_.call(this, options);
_.isString(options) && (options = Cookie.parse(options));
options && this.update(options);
}), PropertyBase);
_.assign(Cookie.prototype, /** @lends Cookie.prototype */ {
update (options) {
_.mergeDefined(this, /** @lends Cookie.prototype */ {
/**
* The name of the cookie.
*
* @type {String}
*/
name: _.choose(options.name, options.key),
/**
* Expires sets an expiry date for when a cookie gets deleted. It should either be a date object or
* timestamp string of date.
*
* @type {Date|String}
*
* @note
* The value for this option is a date in the format Wdy, DD-Mon-YYYY HH:MM:SS GMT such as
* "Sat, 02 May 2009 23:38:25 GMT". Without the expires option, a cookie has a lifespan of a single session.
* A session is defined as finished when the browser is shut down, so session cookies exist only while the
* browser remains open. If the expires option is set to a date that appears in the past, then the cookie is
* immediately deleted in browser.
*
* @todo Accept date object and convert stringified date (timestamp only) to date object
* @todo Consider using Infinity as a default
*/
expires: _.isString(options.expires) ? new Date(options.expires) : options.expires,
/**
* Max-age sets the time in seconds for when a cookie will be deleted.
*
* @type {Number}
*/
maxAge: _.has(options, 'maxAge') ? Number(options.maxAge) : undefined,
/**
* Indicates the domain(s) for which the cookie should be sent.
*
* @type {String}
*
* @note
* By default, domain is set to the host name of the page setting the cookie, so the cookie value is sent
* whenever a request is made to the same host name. The value set for the domain option must be part of the
* host name that is sending the Set-Cookie header. The SDK does not perform this check, but the underlying
* client that actually sends the request could do it automatically.
*/
domain: options.domain,
/**
* @type {String}
*
* @note
* On server, the default value for the path option is the path of the URL that sent the Set-Cookie header.
*/
path: options.path,
/**
* A secure cookie will only be sent to the server when a request is made using SSL and the HTTPS protocol.
* The idea that the contents of the cookie are of high value and could be potentially damaging to transmit
* as clear text.
*
* @type {Boolean}
*/
secure: _.has(options, 'secure') ? Boolean(options.secure) : undefined,
/**
* The idea behind HTTP-only cookies is to instruct a browser that a cookie should never be accessible via
* JavaScript through the document.cookie property. This feature was designed as a security measure to help
* prevent cross-site scripting (XSS) attacks perpetrated by stealing cookies via JavaScript.
*
* @type {Boolean}
*/
httpOnly: _.has(options, 'httpOnly') ? Boolean(options.httpOnly) : undefined,
/**
* @type {Boolean}
*/
hostOnly: _.has(options, 'hostOnly') ? Boolean(options.hostOnly) : undefined,
/**
* Indicates whether this is a Session Cookie.
*
* @type {Boolean}
*/
session: _.has(options, 'session') ? Boolean(options.session) : undefined,
/**
* @note The commonly held belief is that cookie values must be URL-encoded, but this is a fallacy even
* though it is the de facto implementation. The original specification indicates that only three types of
* characters must be encoded: semicolon, comma, and white space. The specification indicates that URL
* encoding may be used but stops short of requiring it. The RFC makes no mention of encoding whatsoever.
* Still, almost all implementations perform some sort of URL encoding on cookie values.
* @type {String}
*/
value: options.value ? _.ensureEncoded(options.value) : undefined,
/**
* Any extra parameters that are not strictly a part of the Cookie spec go here.
*
* @type {Array}
*/
extensions: options.extensions || undefined
});
},
/**
* Get the value of this cookie.
*
* @returns {String}
*/
valueOf () {
try {
return decodeURIComponent(this.value);
}
// handle malformed URI sequence
// refer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Malformed_URI
catch (error) {
/* istanbul ignore next */
return this.value;
}
},
/**
* Converts the Cookie to a single Set-Cookie header string.
*
* @returns {String}
*/
toString () {
var str = Cookie.unparseSingle(this);
if (this.expires && this.expires instanceof Date) {
// check for valid date
if (!Number.isNaN(this.expires.getTime())) {
str += '; Expires=' + this.expires.toUTCString();
}
}
else if (this.expires) {
str += '; Expires=' + this.expires;
}
if (this.maxAge && this.maxAge !== Infinity) {
str += '; Max-Age=' + this.maxAge;
}
if (this.domain && !this.hostOnly) {
str += '; Domain=' + this.domain;
}
if (this.path) {
str += '; Path=' + this.path;
}
if (this.secure) {
str += '; Secure';
}
if (this.httpOnly) {
str += '; HttpOnly';
}
if (this.extensions) {
this.extensions.forEach(({ key, value }) => {
str += `; ${key}`;
str += value === true ? '' : `=${value}`;
});
}
return str;
}
});
_.assign(Cookie, /** @lends Cookie */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Cookie',
// define behaviour of this object when put in list
_postman_propertyIndexKey: 'name',
_postman_propertyIndexCaseInsensitive: true,
_postman_propertyAllowsMultipleValues: true,
/**
* Check whether an object is an instance of PostmanCookie.
*
* @param {*} obj -
* @returns {Boolean}
*/
isCookie: function (obj) {
return Boolean(obj) && ((obj instanceof Cookie) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Cookie._postman_propertyName));
},
/**
* Stringifies an Array or {@link PropertyList} of Cookies into a single string.
*
* @param {Cookie[]} cookies - List of cookie definition object
* @returns {String}
*/
unparse: function (cookies) {
if (!_.isArray(cookies) && !PropertyList.isPropertyList(cookies)) {
return E;
}
return cookies.map(Cookie.unparseSingle).join(COOKIES_SEPARATOR);
},
/**
* Unparses a single Cookie.
*
* @param {Cookie} cookie - Cookie definition object
* @returns {String}
*/
unparseSingle: function (cookie) {
if (!_.isObject(cookie)) { return E; }
var value = _.isNil(cookie.value) ? E : cookie.value;
// for the empty name, return just the value to match the browser behavior
if (!cookie.name) {
return value;
}
return cookie.name + EQ + value;
},
/**
* Cookie header parser
*
* @param {String} str -
* @returns {*} A plain cookie options object, use it to create a new Cookie
*/
parse: function (str) {
if (!_.isString(str)) {
return str;
}
var obj = {},
pairs = str.split(PAIR_SPLIT_REGEX),
nameval;
nameval = Cookie.splitParam(pairs.shift()); // The first kvp is the name and value
obj.key = nameval.key;
obj.value = nameval.value;
pairs.forEach(function (pair) {
var keyval = Cookie.splitParam(pair),
value = keyval.value,
keyLower = keyval.key.toLowerCase();
if (cookieAttributes[keyLower]) {
obj[cookieAttributes[keyLower]] = value;
}
else {
obj.extensions = obj.extensions || [];
obj.extensions.push(keyval);
}
});
// Handle the hostOnly flag
if (!obj.domain) {
obj.hostOnly = true;
}
return obj;
},
/**
* Converts the Cookie to a single Set-Cookie header string.
*
* @param {Cookie} cookie - Cookie definition object
* @returns {String}
*/
stringify: function (cookie) {
return Cookie.prototype.toString.call(cookie);
},
/**
* Splits a Cookie parameter into a key and a value
*
* @private
* @param {String} param -
* @returns {{key: *, value: (Boolean|*)}}
*/
splitParam: function (param) {
var split = param.split('='),
key, value;
key = split[0].trim();
value = _.isString(split[1]) ? split[1].trim() : true;
if (_.isString(value) && value[0] === '"') {
value = value.slice(1, -1);
}
return { key, value };
}
});
module.exports = {
Cookie
};

View File

@@ -0,0 +1,126 @@
var _ = require('../util').lodash,
E = '',
DEFAULT_MIMETYPE = 'text/plain',
Description;
/**
* @typedef Description.definition
* @property {String} content
* @property {String} type
*/
/**
* This is one of the properties that are (if provided) processed by all other properties. Any property can have an
* instance of `Description` property assigned to it with the key name `description` and it should be treated as
* something that "describes" the property within which it belongs. Usually this property is used to generate
* documentation and other contextual information for a property in a Collection.
*
* @constructor
*
* @param {Description.definition|String} [definition] The content of the description can be passed as a string when it
* is in `text/plain` format or otherwise be sent as part of an object adhering to the {@link Description.definition}
* structure having `content` and `type`.
*
* @example <caption>Add a description to an instance of Collection</caption>
* var SDK = require('postman-collection'),
* Collection = SDK.Collection,
* Description = SDK.Description,
* mycollection;
*
* // create a blank collection
* myCollection = new Collection();
* myCollection.description = new Description({
* content: '&lt;h1&gt;Hello World&lt;/h1&gt;&lt;p&gt;I am a Collection&lt;/p&gt;',
* type: 'text/html'
* });
*
* // alternatively, you could also use the `.describe` method of any property to set or update the description of the
* // property.
* myCollection.describe('Hey! This is a cool collection.');
*/
Description = function PostmanPropertyDescription (definition) {
// if the definition is a string, it implies that this is a get of URL
_.isString(definition) && (definition = {
content: definition,
type: DEFAULT_MIMETYPE
});
// populate the description
definition && this.update(definition);
};
_.assign(Description.prototype, /** @lends Description.prototype */ {
/**
* Updates the content of this description property.
*
* @param {String|Description.definition} content -
* @param {String=} [type] -
* @todo parse version of description
*/
update (content, type) {
_.isObject(content) && ((type = content.type), (content = content.content));
_.assign(this, /** @lends Description.prototype */ {
/**
* The raw content of the description
*
* @type {String}
*/
content: content,
/**
* The mime-type of the description.
*
* @type {String}
*/
type: type || DEFAULT_MIMETYPE
});
},
/**
* Returns stringified Description.
*
* @returns {String}
*/
toString () {
return this.content || E;
},
/**
* Creates a JSON representation of the Description (as a plain Javascript object).
*
* @returns {{content: *, type: *, version: (String|*)}}
*/
toJSON () {
return {
content: this.content,
type: this.type
};
}
});
_.assign(Description, /** @lends Description */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Description',
/**
* Checks whether a property is an instance of Description object.
*
* @param {*} obj -
* @returns {Boolean}
*/
isDescription: function (obj) {
return Boolean(obj) && ((obj instanceof Description) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Description._postman_propertyName));
}
});
module.exports = {
Description
};

View File

@@ -0,0 +1,94 @@
var _ = require('../util').lodash,
PropertyList = require('./property-list').PropertyList,
Event = require('./event').Event,
EventList;
_.inherit((
/**
* A type of {@link PropertyList}, EventList handles resolving events from parents. If an {@link ItemGroup} contains
* a set of events, each {@link Item} in that group will inherit those events from its parent, and so on.
*
* @constructor
* @param {Object} parent -
* @param {Object[]} populate -
* @extends {PropertyList}
*
* This is useful when we need to have a common test across all requests.
*/
EventList = function PostmanEventList (parent, populate) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
EventList.super_.call(this, Event, parent, populate);
}), PropertyList);
_.assign(EventList.prototype, /** @lends EventList.prototype */ {
/**
* Returns an array of listeners filtered by the listener name
*
* @note
* If one needs to access disabled events, use {@link PropertyList#all} or
* any other similar {@link PropertyList} method.
*
* @param {String} name -
* @returns {Array<Event>}
*/
listeners (name) {
var all;
// we first procure all matching events from this list
all = this.listenersOwn(name);
this.eachParent(function (parent) {
var parentEvents;
// we check that the parent is not immediate mother. then we check whether the non immediate mother has a
// valid `events` store and only if this store has events with specified listener, we push them to the
// array we are compiling for return
(parent !== this.__parent) && EventList.isEventList(parent.events) &&
(parentEvents = parent.events.listenersOwn(name)) && parentEvents.length &&
all.unshift.apply(all, parentEvents); // eslint-disable-line prefer-spread
}, this);
return all;
},
/**
* Returns all events with specific listeners only within this list. Refer to {@link EventList#listeners} for
* procuring all inherited events
*
* @param {string} name -
* @returns {Array<Event>}
*/
listenersOwn (name) {
return this.filter(function (event) {
return (!event.disabled && event.listen === name);
});
}
});
_.assign(EventList, /** @lends EventList */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'EventList',
/**
* Checks if the given object is an EventList.
*
* @param {*} obj -
* @returns {Boolean}
*/
isEventList: function (obj) {
return Boolean(obj) && ((obj instanceof EventList) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', EventList._postman_propertyName));
}
});
module.exports = {
EventList
};

View File

@@ -0,0 +1,95 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
Script = require('./script').Script,
Event;
/**
* @typedef Event.definition
* @property {String} listen The event-name that this script will be called for. Usually either "test" or "prerequest"
* @property {Script|String} script A {@link Script} instance that will be executed on this event. In case of a
* string, a new {@link Script} is created.
* @example <caption>Constructing an event</caption>
* var Event = require('postman-collection').Event,
* rawEvent = {
* listen: 'test',
* script: 'tests["response code is 401"] = responseCode.code === 401'
* },
* myEvent;
* myEvent = new Event(rawEvent);
*/
_.inherit((
/**
* A Postman event definition that refers to an event to be listened to and a script reference or definition to be
* executed.
*
* @constructor
* @extends {Property}
*
* @param {Event.definition} definition Pass the initial definition of the event as the options parameter.
*/
Event = function PostmanEvent (definition) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Event.super_.call(this, definition);
// set initial values of this event
definition && this.update(definition);
}), Property);
_.assign(Event.prototype, /** @lends Event.prototype */ {
/**
* Update an event.
*
* @param {Event.definition} definition -
*/
update (definition) {
if (!definition) {
return;
}
var result,
script = definition.script;
if (Script.isScript(script)) {
result = script;
}
else if (_.isArray(script) || _.isString(script)) {
result = new Script({ exec: script });
}
else if (_.isObject(script)) {
result = new Script(script);
}
_.mergeDefined(this, /** @lends Event.prototype */ {
/**
* Name of the event that this instance is intended to listen to.
*
* @type {String}
*/
listen: _.isString(definition.listen) ? definition.listen.toLowerCase() : undefined,
/**
* The script that is to be executed when this event is triggered.
*
* @type {Script}
*/
script: result
});
}
});
_.assign(Event, /** @lends Event */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Event'
});
module.exports = {
Event
};

View File

@@ -0,0 +1,103 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
PropertyBase = require('./property-base').PropertyBase,
FormParam;
/**
* @typedef FormParam.definition
* @property {String} key The name ("key") of the form data parameter.
* @property {String} value The value of the parameter.
*/
_.inherit((
/**
* Represents a Form Data parameter, which can exist in request body.
*
* @constructor
* @param {FormParam.definition} options Pass the initial definition of the form data parameter.
*/
FormParam = function PostmanFormParam (options = {}) {
FormParam.super_.apply(this, arguments);
this.key = options.key || '';
this.value = options.value || '';
this.type = options.type;
this.src = options.src;
this.contentType = options.contentType;
}), Property);
_.assign(FormParam.prototype, /** @lends FormParam.prototype */ {
/**
* Converts the FormParameter to a single param string.
*
* @returns {String}
*/
toString () {
return this.key + '=' + this.value;
},
/**
* Returns the value of the form parameter (if any).
*
* @returns {*|String}
*/
valueOf () {
return this.value; // can be multiple types, so just return whatever we have instead of being too clever
},
/**
* Convert the form-param to JSON compatible plain object.
*
* @returns {Object}
*/
toJSON () {
var obj = PropertyBase.toJSON(this);
// remove value from file param because it is non-serializable ReadStream
if (obj.type === 'file') {
_.unset(obj, 'value');
}
return obj;
}
});
_.assign(FormParam, /** @lends FormParam */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'FormParam',
/**
* Declare the list index key, so that property lists of form parameters work correctly
*
* @type {String}
*/
_postman_propertyIndexKey: 'key',
/**
* Form params can have multiple values, so set this to true.
*
* @type {Boolean}
*/
_postman_propertyAllowsMultipleValues: true,
/**
* Parse a form data string into an array of objects, where each object contains a key and a value.
*
* @todo implement this, not implemented yet.
* @param formdata {String}
* @returns {Array}
*/
parse: _.noop
});
module.exports = {
FormParam
};

View File

@@ -0,0 +1,61 @@
var _ = require('../util').lodash,
PropertyList = require('./property-list').PropertyList,
Header = require('./header').Header,
PROP_NAME = '_postman_propertyName',
HeaderList;
_.inherit((
/**
* Contains a list of header elements
*
* @constructor
* @param {Object} parent -
* @param {Header[]} headers -
* @extends {PropertyList}
*/
HeaderList = function (parent, headers) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
HeaderList.super_.call(this, Header, parent, headers);
}), PropertyList);
_.assign(HeaderList.prototype, /** @lends HeaderList.prototype */ {
/**
* Gets size of a list of headers excluding standard header prefix.
*
* @returns {Number}
*/
contentSize () {
if (!this.count()) { return 0; }
return Header.unparse(this).length;
}
});
_.assign(HeaderList, /** @lends HeaderList */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'HeaderList',
/**
* Checks if the given object is a HeaderList
*
* @param {*} obj -
* @returns {Boolean}
*/
isHeaderList: function (obj) {
return Boolean(obj) && ((obj instanceof HeaderList) ||
_.inSuperChain(obj.constructor, PROP_NAME, HeaderList._postman_propertyName));
}
});
module.exports = {
HeaderList
};

View File

@@ -0,0 +1,295 @@
var util = require('../util'),
_ = util.lodash,
E = '',
SPC = ' ',
CRLF = '\r\n',
HEADER_KV_SEPARATOR = ':',
Property = require('./property').Property,
PropertyList = require('./property-list').PropertyList,
Header;
/**
* @typedef Header.definition
* @property {String} key The Header name (e.g: 'Content-Type')
* @property {String} value The value of the header.
*
* @example <caption>Create a header</caption>
* var Header = require('postman-collection').Header,
* header = new Header({
* key: 'Content-Type',
* value: 'application/xml'
* });
*
* console.log(header.toString()) // prints the string representation of the Header.
*/
_.inherit((
/**
* Represents an HTTP header, for requests or for responses.
*
* @constructor
* @extends {Property}
*
* @param {Header.definition|String} options - Pass the header definition as an object or the value of the header.
* If the value is passed as a string, it should either be in `name:value` format or the second "name" parameter
* should be used to pass the name as string
* @param {String} [name] - optional override the header name or use when the first parameter is the header value as
* string.
*
* @example <caption>Parse a string of headers into an array of Header objects</caption>
* var Header = require('postman-collection').Header,
* headerString = 'Content-Type: application/json\nUser-Agent: MyClientLibrary/2.0\n';
*
* var rawHeaders = Header.parse(headerString);
* console.log(rawHeaders); // [{ 'Content-Type': 'application/json', 'User-Agent': 'MyClientLibrary/2.0' }]
*
* var headers = rawHeaders.map(function (h) {
* return new Header(h);
* });
*
* function assert(condition, message) {
* if (!condition) {
* message = message || "Assertion failed";
* if (typeof Error !== "undefined") {
* throw new Error(message);
* }
* throw message; //fallback
* }
* else {
* console.log("Assertion passed");
* }
* }
*
* assert(headerString.trim() === Header.unparse(headers).trim());
*/
Header = function PostmanHeader (options, name) {
if (_.isString(options)) {
options = _.isString(name) ? { key: name, value: options } : Header.parseSingle(options);
}
// this constructor is intended to inherit and as such the super constructor is required to be executed
Header.super_.apply(this, arguments);
this.update(options);
}), Property);
_.assign(Header.prototype, /** @lends Header.prototype */ {
/**
* Converts the header to a single header string.
*
* @returns {String}
*/
toString () {
return this.key + ': ' + this.value;
},
/**
* Return the value of this header.
*
* @returns {String}
*/
valueOf () {
return this.value;
},
/**
* Assigns the given properties to the Header
*
* @param {Object} options -
* @todo check for allowed characters in header key-value or store encoded.
*/
update (options) {
/**
* The header Key
*
* @type {String}
* @todo avoid headers with falsy key.
*/
this.key = _.get(options, 'key') || E;
/**
* The header value
*
* @type {String}
*/
this.value = _.get(options, 'value', E);
/**
* Indicates whether the header was added by internal SDK operations, such as authorizing a request.
*
* @type {*|boolean}
*/
_.has(options, 'system') && (this.system = options.system);
/**
* Indicates whether the header should be .
*
* @type {*|boolean}
* @todo figure out whether this should be in property.js
*/
_.has(options, 'disabled') && (this.disabled = options.disabled);
}
});
_.assign(Header, /** @lends Header */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Header',
/**
* Specify the key to be used while indexing this object
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyIndexKey: 'key',
/**
* Specifies whether the index lookup of this property, when in a list is case insensitive or not
*
* @private
* @readOnly
* @type {boolean}
*/
_postman_propertyIndexCaseInsensitive: true,
/**
* Since each header may have multiple possible values, this is set to true.
*
* @private
* @readOnly
* @type {Boolean}
*/
_postman_propertyAllowsMultipleValues: true,
/**
* Parses a multi line header string into an array of {@link Header.definition}.
*
* @param {String} headerString -
* @returns {Array}
*/
parse: function (headerString) {
var headers = [],
regexes = {
header: /^(\S+):(.*)$/gm,
fold: /\r\n([ \t])/g,
trim: /^\s*(.*\S)?\s*$/ // eslint-disable-line security/detect-unsafe-regex
},
match = regexes.header.exec(headerString);
headerString = headerString.toString().replace(regexes.fold, '$1');
while (match) {
headers.push({
key: match[1],
value: match[2].replace(regexes.trim, '$1')
});
match = regexes.header.exec(headerString);
}
return headers;
},
/**
* Parses a single Header.
*
* @param {String} header -
* @returns {{key: String, value: String}}
*/
parseSingle: function (header) {
if (!_.isString(header)) { return { key: E, value: E }; }
var index = header.indexOf(HEADER_KV_SEPARATOR),
key,
value;
(index < 0) && (index = header.length);
key = header.substr(0, index);
value = header.substr(index + 1);
return {
key: _.trim(key),
value: _.trim(value)
};
},
/**
* Stringifies an Array or {@link PropertyList} of Headers into a single string.
*
* @note Disabled headers are excluded.
*
* @param {Array|PropertyList<Header>} headers -
* @param {String=} [separator='\r\n'] - Specify a string for separating each header
* @returns {String}
*/
unparse: function (headers, separator = CRLF) {
if (!_.isArray(headers) && !PropertyList.isPropertyList(headers)) {
return E;
}
return headers.reduce(function (acc, header) {
if (header && !header.disabled) {
acc += Header.unparseSingle(header) + separator;
}
return acc;
}, E);
},
/**
* Unparses a single Header.
*
* @param {String} header -
* @returns {String}
*/
unparseSingle: function (header) {
if (!_.isObject(header)) { return E; }
return header.key + HEADER_KV_SEPARATOR + SPC + header.value;
},
/**
* Check whether an object is an instance of PostmanHeader.
*
* @param {*} obj -
* @returns {Boolean}
*/
isHeader: function (obj) {
return Boolean(obj) && ((obj instanceof Header) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Header._postman_propertyName));
},
/* eslint-disable jsdoc/check-param-names */
/**
* Create a new header instance
*
* @param {Header.definition|String} [value] - Pass the header definition as an object or the value of the header.
* If the value is passed as a string, it should either be in `name:value` format or the second "name" parameter
* should be used to pass the name as string
* @param {String} [name] - optional override the header name or use when the first parameter is the header value as
* string.
* @returns {Header}
*/
create: function () {
var args = Array.prototype.slice.call(arguments);
args.unshift(Header);
return new (Header.bind.apply(Header, args))(); // eslint-disable-line prefer-spread
}
/* eslint-enable jsdoc/check-param-names */
});
module.exports = {
Header
};

View File

@@ -0,0 +1,341 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
PropertyList = require('./property-list').PropertyList,
EventList = require('./event-list').EventList,
Item = require('./item').Item,
Request = require('./request').Request,
RequestAuth = require('./request-auth').RequestAuth,
ItemGroup,
/**
* @private
* @type {String}
*/
OBJECT = 'object';
/**
* The following defines the object (or JSON) structure that one can pass to the ItemGroup while creating a new
* ItemGroup instance. This is also the object structure returned when `.toJSON()` is called on an ItemGroup instance.
*
* @typedef ItemGroup.definition
* @property {Array<ItemGroup.definition|Item.definition>=} [item]
* @property {RequestAuth.definition=} [auth]
* @property {Array<Event.definition>=} [event]
*
* @example
* {
* "name": "Echo Get Requests",
* "id": "echo-get-requests",
* "item": [{
* "request": "https://postman-echo.com/get"
* }, {
* "request": "https://postman-echo.com/headers"
* }],
* "auth": {
* "type": "basic",
* "basic": {
* "username": "jean",
* "password": "{{somethingsecret}}"
* }
* },
* "event": [{
* "listen": "prerequest",
* "script": {
* "type": "text/javascript",
* "exec": "console.log(new Date())"
* }
* }]
* }
*/
_.inherit((
/**
* An ItemGroup represents a composite list of {@link Item} or ItemGroup. In terms of Postman App, ItemGroup
* represents a "Folder". This allows one to group Items into subsets that can have their own meaning. An
* ItemGroup also allows one to define a subset of common properties to be applied to each Item within it. For
* example, a `test` event defined on an ItemGroup is executed while testing any Item that belongs to that group.
* Similarly, ItemGroups can have a common {@RequestAuth} defined so that every {@link Request}, when processed,
* requires to be authenticated using the `auth` defined in the group.
*
* Essentially, {@link Collection} too is a special type of ItemGroup ;-).
*
* @constructor
* @extends {Property}
*
* @param {ItemGroup.definition=} [definition] While creating a new instance of ItemGroup, one can provide the
* initial configuration of the item group with the requests it contains, the authentication applied to all
* requests, events that the requests responds to, etc.
*
* @example <caption>Add a new ItemGroup to a collection instance</caption>
* var Collection = require('postman-collection').Collection,
* ItemGroup = require('postman-collection').ItemGroup,
* myCollection;
*
* myCollection = new Collection(); // create an empty collection
* myCollection.items.add(new ItemGroup({ // add a folder called "blank folder"
* "name": "This is a blank folder"
* }));
*/
ItemGroup = function PostmanItemGroup (definition) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
ItemGroup.super_.apply(this, arguments);
_.mergeDefined(this, /** @lends ItemGroup.prototype */ {
/**
* This is a {@link PropertyList} that holds the list of {@link Item}s or {@link ItemGroup}s belonging to a
* {@link Collection} or to an {@link ItemGroup}. Operation on an individual item in this list can be
* performed using various functions available to a {@link PropertyList}.
*
* @type {PropertyList<(Item|ItemGroup)>}
*
* @example <caption>Fetch empty ItemGroups in a list loaded from a file</caption>
* var fs = require('fs'), // needed to read JSON file from disk
* Collection = require('postman-collection').Collection,
* myCollection,
* emptyGroups;
* // Load a collection to memory from a JSON file on disk (say, sample-collection.json)
* myCollection = new Collection(JSON.stringify(fs.readFileSync('sample-collection.json').toString()));
*
* // Filter items in Collection root that is an empty ItemGroup
* emptyGroups = myCollection.items.filter(function (item) {
* return item && item.items && (item.items.count() === 0);
* });
*
* // Log the emptyGroups array to check it's contents
* console.log(emptyGroups);
*/
items: new PropertyList(ItemGroup._createNewGroupOrItem, this, definition && definition.item),
/**
* One can define the default authentication method required for every item that belongs to this list.
* Individual {@link Request}s can override this in their own definitions. More on how to define an
* authentication method is outlined in the {@link RequestAuth} property.
*
* @type {RequestAuth}
*
* @example <caption>Define an entire ItemGroup (folder) or Collection to follow Basic Auth</caption>
* var fs = require('fs'),
* Collection = require('postman-collection').Collection,
* RequestAuth = require('postman-collection').RequestAuth,
* mycollection;
*
* // Create a collection having two requests
* myCollection = new Collection();
* myCollection.items.add([
* { name: 'GET Request', request: 'https://postman-echo.com/get?auth=basic' },
* { name: 'PUT Request', request: 'https://postman-echo.com/put?auth=basic' }
* ]);
*
* // Add basic auth to the Collection, to be applied on all requests.
* myCollection.auth = new RequestAuth({
* type: 'basic',
* username: 'postman',
* password: 'password'
* });
*/
// auth is a special case, empty RequestAuth should not be created for falsy values
// to allow inheritance from parent
auth: definition && definition.auth ? new RequestAuth(definition.auth) : undefined,
/**
* In this list, one can define the {@link Script}s to be executed when an event is triggered. Events are
* triggered before certain actions are taken on a Collection, Request, etc. For example, executing a
* request causes the `prerequest` and the `test` events to be triggered.
*
* @type {EventList}
* @memberOf Collection.prototype
*
* @example <caption>Executing a common test script for all requests in a collection</caption>
* var fs = require('fs'), // needed to read JSON file from disk
* Collection = require('postman-collection').Collection,
* myCollection;
*
* // Load a collection to memory from a JSON file on disk (say, sample-collection.json)
* myCollection = new Collection(JSON.stringify(fs.readFileSync('sample-collection.json').toString()));
*
* // Add an event listener to the collection that listens to the `test` event.
* myCollection.events.add({
* listen: 'test',
* script: {
* exec: 'tests["Status code is 200"] = (responseCode.code === 200)'
* }
* });
*/
events: new EventList(this, definition && definition.event),
/**
* Set of configurations used to alter the usual behavior of sending the request.
*
* @type {Object}
* @property {Boolean} disableBodyPruning Disable body pruning for request methods like GET, HEAD etc.
*/
protocolProfileBehavior: definition && typeof definition.protocolProfileBehavior === OBJECT ?
definition.protocolProfileBehavior : undefined
});
}), Property);
_.assign(ItemGroup.prototype, /** @lends ItemGroup.prototype */ {
/**
* Defines that this property requires an ID field
*
* @private
* @readonly
*/
_postman_propertyRequiresId: true,
/**
* Calls the callback for each item belonging to itself. If any ItemGroups are encountered,
* they will call the callback on their own Items.
*
* @private
* @param {Function} callback -
*/
forEachItem: function forEachItem (callback) {
this.items.each(function (item) {
return ItemGroup.isItemGroup(item) ? item.forEachItem(callback) : callback(item, this);
}, this);
},
/**
* Calls the callback for each itemgroup belonging to itself. All ItemGroups encountered will also,
* call the callback on their own ItemGroups
*
* @private
* @param {Function} callback -
*/
forEachItemGroup: function forEachItemGroup (callback) {
this.items.each(function (item) {
if (ItemGroup.isItemGroup(item)) {
item.forEachItemGroup(callback);
callback(item, this); // eslint-disable-line callback-return
}
}, this);
},
/**
* Finds the first item with the given name or id in the current ItemGroup.
*
* @param {String} idOrName -
*/
oneDeep: function (idOrName) {
if (!_.isString(idOrName)) { return; }
var item;
this.items.each(function (eachItem) {
if (eachItem.id === idOrName || eachItem.name === idOrName) {
item = eachItem;
return false; // we found something, so bail out of the for loop.
}
if (ItemGroup.isItemGroup(eachItem)) {
item = eachItem.oneDeep(idOrName);
return !item; // bail out of the for loop if we found anything
}
});
return item;
},
/**
* Fetches protocol profile behavior for the current ItemGroup
*
* @private
* @returns {Object}
*
* @note This will not inherit protocol profile behaviors from parent,
* use `getProtocolProfileBehaviorResolved` to achieve that behavior.
*/
getProtocolProfileBehavior: Item.prototype.getProtocolProfileBehavior,
/**
* Fetches protocol profile behavior applicable for the current ItemGroup,
* inherited from parent ItemGroups(s).
*
* @private
* @returns {Object}
*/
getProtocolProfileBehaviorResolved: Item.prototype.getProtocolProfileBehaviorResolved,
/**
* Set or update protocol profile behavior for the current ItemGroup.
*
* @example <caption> Set or update protocol profile behavior </caption>
* itemGroup.setProtocolProfileBehavior('strictSSL', false);
*
* @private
* @param {String} key - protocol profile behavior name
* @param {*} value - protocol profile behavior value
* @returns {ItemGroup}
*/
setProtocolProfileBehavior: Item.prototype.setProtocolProfileBehavior,
/**
* Unset or delete protocol profile behavior for the current ItemGroup.
*
* @example <caption> Unset protocol profile behavior </caption>
* itemGroup.unsetProtocolProfileBehavior('strictSSL');
*
* @private
* @param {String} key - protocol profile behavior name to unset
* @returns {ItemGroup}
*/
unsetProtocolProfileBehavior: Item.prototype.unsetProtocolProfileBehavior,
/**
* Sets authentication method for all the items within this group
*
* @param {?String|RequestAuth.definition} type
* @param {VariableList=} [options]
*
* @note This function was previously (in v2 of SDK) used to clone request and populate headers. Now it is used to
* only set auth information to request
*/
authorizeRequestsUsing: Request.prototype.authorizeUsing
});
_.assign(ItemGroup, /** @lends ItemGroup */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'ItemGroup',
/**
* Iterator function to update an itemgroup's item array with appropriate objects from definition.
*
* @private
* @this {ItemGroup}
* @param {Object} item - the definition of an item or group
* @returns {ItemGroup|Item}
* @note
* This function is intended to be used in scope of an instance of a {@link ItemGroup).
*/
_createNewGroupOrItem: function (item) {
if (Item.isItem(item) || ItemGroup.isItemGroup(item)) { return item; }
return item && item.item ? new ItemGroup(item) : new Item(item);
},
/**
* Check whether an object is an instance of {@link ItemGroup}.
*
* @param {*} obj -
* @returns {Boolean}
*/
isItemGroup: function (obj) {
return Boolean(obj) && ((obj instanceof ItemGroup) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', ItemGroup._postman_propertyName));
}
});
module.exports = {
ItemGroup
};

359
node_modules/postman-collection/lib/collection/item.js generated vendored Normal file
View File

@@ -0,0 +1,359 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
PropertyList = require('./property-list').PropertyList,
EventList = require('./event-list').EventList,
Request = require('./request').Request,
RequestAuth = require('./request-auth').RequestAuth,
Response = require('./response').Response,
Item,
/**
* @private
* @type {String}
*/
OBJECT = 'object',
/**
* @private
* @type {String}
*/
STRING = 'string',
/**
* Extracts `auth` from an entity. Checks if `auth` is present and it is not falsy type.
*
* @private
*
* @param {Object} [entity] -
*/
extractAuth = function (entity) {
var auth;
return (entity && (auth = entity.auth) && RequestAuth.isValidType(auth.type)) ? auth : undefined;
},
/**
* Extracts `protocolProfileBehavior` from an entity.
*
* @private
*
* @param {Item|ItemGroup} entity -
* @returns {Object}
*/
extractProtocolProfileBehavior = function (entity) {
var protocolProfileBehavior = entity && entity.protocolProfileBehavior;
return typeof protocolProfileBehavior === OBJECT ? protocolProfileBehavior : {};
};
/**
* The following defines the object (or JSON) structure that one can pass to the Item while creating a new Item
* instance. This is also the object structure returned when `.toJSON()` is called on an Item instance.
*
* @typedef Item.definition
*
* @property {Request.definition=} [request] A request represents an HTTP request. If a string, the string is assumed to
* be the request URL and the method is assumed to be 'GET'.
* @property {Array<Response.definition>=} [responses] Sample responses for this request can be stored along with the
* item definition.
* @property {Array<Event.definition>=} [events] Postman allows you to configure scripts to run when specific events
* occur. These scripts are stored here, and can be referenced in the collection by their id.
*
* @example
* {
* "name": "Get Headers from Echo",
* "id": "my-request-1",
* "description": "Makes a GET call to echo service and returns the client headers that were sent",
*
* "request": {
* "url": "https://postman-echo.com/headers",
* "method": "GET"
* }
* }
*
* @todo add response and event to example
*/
_.inherit((
/**
* A Postman Collection Item that holds your request definition, responses and other stuff. An Item essentially is
* a HTTP request definition along with the sample responses and test scripts clubbed together. One or more of these
* items can be grouped together and placed in an {@link ItemGroup} and as such forms a {@link Collection} of
* requests.
*
* @constructor
* @extends {Property}
*
* @param {Item.definition=} [definition] While creating a new instance of Item, one can provide the initial
* configuration of the item with the the request it sends, the expected sample responses, tests, etc
*
* @example <caption>Add a new Item to a folder in a collection instance</caption>
* var Collection = require('postman-collection').Collection,
* Item = require('postman-collection').Item,
* myCollection;
*
* myCollection = new Collection({
* "item": [{
* "id": "my-folder-1",
* "name": "The solo folder in this collection",
* "item": [] // blank array indicates this is a folder
* }]
* }); // create a collection with an empty folder
* // add a request to "my-folder-1" that sends a GET request
* myCollection.items.one("my-folder-1").items.add(new Item({
* "name": "Send a GET request",
* "id": "my-get-request",
* "request": {
* "url": "https://postman-echo.com/get",
* "method": "GET"
* }
* }));
*/
Item = function PostmanItem (definition) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Item.super_.apply(this, arguments);
_.mergeDefined(this, /** @lends Item.prototype */ {
/**
* The instance of the {@link Request} object inside an Item defines the HTTP request that is supposed to be
* sent. It further contains the request method, url, request body, etc.
*
* @type {Request}
*/
request: definition && (new Request(definition.request)),
/**
* An Item also contains a list of sample responses that is expected when the request defined in the item is
* executed. The sample responses are useful in elaborating API usage and is also useful for other
* integrations that use the sample responses to do something - say a mock service.
*
* @type {PropertyList<Response>}
*/
responses: new PropertyList(Response, this, definition && definition.response),
/**
* Events are a set of of {@link Script}s that are executed when certain activities are triggered on an
* Item. For example, on defining an event that listens to the "test" event, would cause the associated
* script of the event to be executed when the test runs.
*
* @type {EventList}
*
* @example <caption>Add a script to be executed on "prerequest" event</caption>
* var Collection = require('postman-collection').Collection,
* Item = require('postman-collection').Item,
* myCollection;
*
* myCollection = new Collection({
* "item": [{
* "name": "Send a GET request",
* "id": "my-get-request",
* "request": {
* "url": "https://postman-echo.com/get",
* "method": "GET"
* }
* }]
* }); // create a collection with one request
*
* // add a pre-request script to the event list
* myCollection.items.one('my-get-request').events.add({
* "listen": "prerequest",
* "script": {
* "type": "text/javascript",
* "exec": "console.log(new Date())"
* }
* });
*/
events: new EventList(this, definition && definition.event),
/**
* Set of configurations used to alter the usual behavior of sending the request.
*
* @type {Object}
*/
protocolProfileBehavior: definition && typeof definition.protocolProfileBehavior === OBJECT ?
definition.protocolProfileBehavior : undefined
});
}), Property);
_.assign(Item.prototype, /** @lends Item.prototype */ {
/**
* Defines whether this property instances requires an id
*
* @private
* @readOnly
* @type {Boolean}
*/
_postman_propertyRequiresId: true,
/**
* Fetches applicable AuthType from the current item.
*
* @returns {RequestAuth}
*
* @note Since v3.0 release, this returns the entire auth RequestAuth, instead of just the parameters
*
* @todo Deprecate this and use getAuthResolved instead
*/
getAuth: function () {
var requestAuth;
// find auth on request, if not found or empty auth, lookup in the parents
// eslint-disable-next-line no-cond-assign
return (requestAuth = extractAuth(this.request)) ? requestAuth : this.findInParents('auth', extractAuth);
},
/**
* Fetches protocol profile behavior for the current Item
*
* @private
* @returns {Object}
*
* @note This will not inherit protocol profile behaviors from parent,
* use `getProtocolProfileBehaviorResolved` to achieve that behavior.
*/
getProtocolProfileBehavior: function () {
return extractProtocolProfileBehavior(this);
},
/**
* Fetches protocol profile behavior applicable for the current Item,
* inherited from parent ItemGroup(s).
*
* @private
* @returns {Object}
*/
getProtocolProfileBehaviorResolved: function () {
var protocolProfileBehavior = extractProtocolProfileBehavior(this);
// inherit protocolProfileBehavior from ItemGroup(s)
this.forEachParent({ withRoot: true }, function (entity) {
protocolProfileBehavior = {
...extractProtocolProfileBehavior(entity),
...protocolProfileBehavior
};
});
return protocolProfileBehavior;
},
/**
* Set or update protocol profile behavior for the current Item.
*
* @example <caption> Set or update protocol profile behavior </caption>
* item.setProtocolProfileBehavior('strictSSL', false);
*
* @private
* @param {String} key - protocol profile behavior name
* @param {*} value - protocol profile behavior value
* @returns {Item}
*/
setProtocolProfileBehavior: function (key, value) {
// bail out if key is non-string
if (typeof key !== STRING) { return this; }
!this.protocolProfileBehavior && (this.protocolProfileBehavior = {});
this.protocolProfileBehavior[key] = value;
return this;
},
/**
* Unset or delete protocol profile behavior for the current Item.
*
* @example <caption> Unset protocol profile behavior </caption>
* item.unsetProtocolProfileBehavior('strictSSL');
*
* @private
* @param {String} key - protocol profile behavior name to unset
* @returns {Item}
*/
unsetProtocolProfileBehavior: function (key) {
// bail out if property protocolProfileBehavior is not set or key is non-string
if (!(typeof this.protocolProfileBehavior === OBJECT && typeof key === STRING)) {
return this;
}
if (_.has(this.protocolProfileBehavior, key)) {
delete this.protocolProfileBehavior[key];
}
return this;
},
/**
* Returns {@link Event}s corresponding to a particular event name. If no name is given, returns all events. This
* is useful when you want to trigger all associated scripts for an event.
*
* @param {String} name - one of the available event types such as `test`, `prerequest`, `postrequest`, etc.
* @returns {Array<Event>}
*
* @example <caption>Get all events for an item and evaluate their scripts</caption>
* var fs = require('fs'), // needed to read JSON file from disk
* Collection = require('postman-collection').Collection,
* myCollection;
*
* // Load a collection to memory from a JSON file on disk (say, sample-collection.json)
* myCollection = new Collection(JSON.stringify(fs.readFileSync('sample-collection.json').toString()));
*
* // assuming the collection has a request called "my-request-1" in root, we get it's test events
* myCollection.items.one("my-request-1").getEvents("test").forEach(function (event) {
* event.script && eval(event.script.toSource());
* });
*
* @todo decide appropriate verb names based on the fact that it gets events for a specific listener name
* @draft
*/
getEvents: function (name) {
if (!name) {
return this.events.all(); // return all events if name is not provided.
}
return this.events.filter(function (ev) {
return ev.listen === name;
});
},
/**
* Sets authentication method for the request within this item
*
* @param {?String|RequestAuth.definition} type -
* @param {VariableList=} [options] -
*
* @note This function was previously (in v2 of SDK) used to clone request and populate headers. Now it is used to
* only set auth information to request
*/
authorizeRequestUsing: function (type, options) {
if (!this.request) { this.request = new Request(); } // worst case
return this.request.authorizeUsing(type, options);
}
});
_.assign(Item, /** @lends Item */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Item',
/**
* Check whether an object is an instance of PostmanItem.
*
* @param {*} obj -
* @returns {Boolean}
*/
isItem: function (obj) {
return Boolean(obj) && ((obj instanceof Item) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Item._postman_propertyName));
}
});
module.exports = {
Item
};

View File

@@ -0,0 +1,243 @@
var _ = require('../util').lodash,
PropertyBase = require('./property-base').PropertyBase,
/**
* Primitive mutation types.
*
* @private
* @constant
* @type {Object}
*/
PRIMITIVE_MUTATIONS = {
SET: 'set',
UNSET: 'unset'
},
/**
* Detects if the mutation is a primitive mutation type. A primitive mutation is the simplified mutation structure.
*
* @private
* @param {MutationTracker.mutation} mutation -
* @returns {Boolean}
*/
isPrimitiveMutation = function (mutation) {
return mutation && mutation.length <= 2;
},
/**
* Applies a single mutation on a target.
*
* @private
* @param {*} target -
* @param {MutationTracker.mutation} mutation -
*/
applyMutation = function applyMutation (target, mutation) {
// only `set` and `unset` instructions are supported
// for non primitive mutations, the instruction would have to be extracted from mutation
/* istanbul ignore if */
if (!isPrimitiveMutation(mutation)) {
return;
}
// extract instruction from the mutation
var operation = mutation.length > 1 ? PRIMITIVE_MUTATIONS.SET : PRIMITIVE_MUTATIONS.UNSET;
// now hand over applying mutation to the target
target.applyMutation(operation, ...mutation);
},
MutationTracker;
/**
* A JSON representation of a mutation on an object. Here objects mean instances of postman-collection classes.
* This captures the instruction and the parameters of the instruction so that it can be replayed on a different object.
* Mutations can be any change on an object. For example setting a key or unsetting a key.
*
* For example, the mutation to set `name` on an object to 'Bruce Wayne' would look like ['name', 'Bruce Wayne']. Where
* the first item is the key path and second item is the value. To add a property `punchLine` to the object it would be
* the same as updating the property i.e. ['punchLine', 'I\'m Batman']. To remove a property `age` the mutation would
* look like ['age'].
*
* This format of representing changes is derived from
* {@link http://json-delta.readthedocs.io/en/latest/philosophy.html}.
*
* The `set` and `unset` are primitive instructions and can be derived from the mutation without explicitly stating the
* instruction. For more complex mutation the instruction would have to be explicitly stated.
*
* @typedef {Array} MutationTracker.mutation
*/
/**
* A JSON representation of the MutationTracker.
*
* @typedef MutationTracker.definition
*
* @property {Array} stream contains the stream mutations tracked
* @property {Object} compacted contains a compacted version of the mutations
* @property {Boolean} [autoCompact=false] when set to true, all new mutations would be compacted immediately
*/
_.inherit((
/**
* A MutationTracker allows to record mutations on any of object and store them. This stored mutations can be
* transported for reporting or to replay on similar objects.
*
* @constructor
* @extends {PropertyBase}
*
* @param {MutationTracker.definition} definition serialized mutation tracker
*/
MutationTracker = function MutationTracker (definition) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
MutationTracker.super_.call(this, definition);
definition = definition || {};
// initialize options
this.autoCompact = Boolean(definition.autoCompact);
// restore mutations
this.stream = Array.isArray(definition.stream) ? definition.stream : [];
this.compacted = _.isPlainObject(definition.compacted) ? definition.compacted : {};
}), PropertyBase);
_.assign(MutationTracker.prototype, /** @lends MutationTracker.prototype */ {
/**
* Records a new mutation.
*
* @private
* @param {MutationTracker.mutation} mutation -
*/
addMutation (mutation) {
// bail out for empty or unsupported mutations
if (!(mutation && isPrimitiveMutation(mutation))) {
return;
}
// if autoCompact is set, we need to compact while adding
if (this.autoCompact) {
this.addAndCompact(mutation);
return;
}
// otherwise just push to the stream of mutations
this.stream.push(mutation);
},
/**
* Records a mutation compacting existing mutations for the same key path.
*
* @private
* @param {MutationTracker.mutation} mutation -
*/
addAndCompact (mutation) {
// for `set` and `unset` mutations the key to compact with is the `keyPath`
var key = mutation[0];
// convert `keyPath` to a string
key = Array.isArray(key) ? key.join('.') : key;
this.compacted[key] = mutation;
},
/**
* Track a mutation.
*
* @param {String} instruction the type of mutation
* @param {...*} payload mutation parameters
*/
track (instruction, ...payload) {
// invalid call
if (!(instruction && payload)) {
return;
}
// unknown instruction
if (!(instruction === PRIMITIVE_MUTATIONS.SET || instruction === PRIMITIVE_MUTATIONS.UNSET)) {
return;
}
// for primitive mutations the arguments form the mutation object
// if there is more complex mutation, we have to use a processor to create a mutation for the instruction
this.addMutation(payload);
},
/**
* Compacts the recorded mutations removing duplicate mutations that apply on the same key path.
*/
compact () {
// for each of the mutation, add to compacted list
this.stream.forEach(this.addAndCompact.bind(this));
// reset the `stream`, all the mutations are now recorded in the `compacted` storage
this.stream = [];
},
/**
* Returns the number of mutations tracked so far.
*
* @returns {Number}
*/
count () {
// the total count of mutations is the sum of
// mutations in the stream
var mutationCount = this.stream.length;
// and the compacted mutations
mutationCount += Object.keys(this.compacted).length;
return mutationCount;
},
/**
* Applies all the recorded mutations on a target object.
*
* @param {*} target Target to apply mutations. Must implement `applyMutation`.
*/
applyOn (target) {
if (!(target && target.applyMutation)) {
return;
}
var applyIndividualMutation = function applyIndividualMutation (mutation) {
applyMutation(target, mutation);
};
// mutations move from `stream` to `compacted`, so we apply the compacted mutations first
// to ensure FIFO of mutations
// apply the compacted mutations first
_.forEach(this.compacted, applyIndividualMutation);
// apply the mutations in the stream
_.forEach(this.stream, applyIndividualMutation);
}
});
_.assign(MutationTracker, /** @lends MutationTracker */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'MutationTracker',
/**
* Check whether an object is an instance of {@link MutationTracker}.
*
* @param {*} obj -
* @returns {Boolean}
*/
isMutationTracker: function (obj) {
return Boolean(obj) && ((obj instanceof MutationTracker) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', MutationTracker._postman_propertyName));
}
});
module.exports = {
MutationTracker
};

View File

@@ -0,0 +1,231 @@
var _ = require('../util').lodash,
__PARENT = '__parent',
PropertyBase; // constructor
/**
* @typedef PropertyBase.definition
* @property {String|Description} [description]
*/
/**
* Base of all properties in Postman Collection. It defines the root for all standalone properties for postman
* collection.
*
* @constructor
* @param {PropertyBase.definition} definition -
*/
PropertyBase = function PropertyBase (definition) {
// In case definition object is missing, there is no point moving forward. Also if the definition is basic string
// we do not need to do anything with it.
if (!definition || typeof definition === 'string') { return; }
// call the meta extraction functions to create the object where all keys that are prefixed with underscore can be
// stored. more details on that can be retrieved from the propertyExtractMeta function itself.
// @todo: make this a closed function to do getter and setter which is non enumerable
var src = definition && definition.info || definition,
meta = _(src).pickBy(PropertyBase.propertyIsMeta).mapKeys(PropertyBase.propertyUnprefixMeta).value();
if (_.keys(meta).length) {
this._ = _.isObject(this._) ?
/* istanbul ignore next */
_.mergeDefined(this._, meta) :
meta;
}
};
_.assign(PropertyBase.prototype, /** @lends PropertyBase.prototype */ {
/**
* Invokes the given iterator for every parent in the parent chain of the given element.
*
* @param {Object} options - A set of options for the parent chain traversal.
* @param {?Boolean} [options.withRoot=false] - Set to true to include the collection object as well.
* @param {Function} iterator - The function to call for every parent in the ancestry chain.
* @todo Cache the results
*/
forEachParent (options, iterator) {
_.isFunction(options) && (iterator = options, options = {});
if (!_.isFunction(iterator) || !_.isObject(options)) { return; }
var parent = this.parent(),
grandparent = parent && _.isFunction(parent.parent) && parent.parent();
while (parent && (grandparent || options.withRoot)) {
iterator(parent);
parent = grandparent;
grandparent = grandparent && _.isFunction(grandparent.parent) && grandparent.parent();
}
},
/**
* Tries to find the given property locally, and then proceeds to lookup in each parent,
* going up the chain as necessary. Lookup will continue until `customizer` returns a truthy value. If used
* without a customizer, the lookup will stop at the first parent that contains the property.
*
* @param {String} property -
* @param {Function} [customizer] -
* @returns {*|undefined}
*/
findInParents (property, customizer) {
var owner = this.findParentContaining(property, customizer);
return owner ? owner[property] : undefined;
},
/**
* Looks up the closest parent which has a truthy value for the given property. Lookup will continue
* until `customizer` returns a truthy value. If used without a customizer,
* the lookup will stop at the first parent that contains the property.
*
* @private
* @param {String} property -
* @param {Function} [customizer] -
* @returns {PropertyBase|undefined}
*/
findParentContaining (property, customizer) {
var parent = this;
// if customizer is present test with it
if (customizer) {
customizer = customizer.bind(this);
do {
// else check for existence
if (customizer(parent)) {
return parent;
}
parent = parent.__parent;
} while (parent);
}
// else check for existence
else {
do {
if (parent[property]) {
return parent;
}
parent = parent.__parent;
} while (parent);
}
},
/**
* Returns the JSON representation of a property, which conforms to the way it is defined in a collection.
* You can use this method to get the instantaneous representation of any property, including a {@link Collection}.
*/
toJSON () {
return _.reduce(this, function (accumulator, value, key) {
if (value === undefined) { // true/false/null need to be preserved.
return accumulator;
}
// Handle plurality of PropertyLists in the SDK vs the exported JSON.
// Basically, removes the trailing "s" from key if the value is a property list.
// eslint-disable-next-line max-len
if (value && value._postman_propertyIsList && !value._postman_proprtyIsSerialisedAsPlural && _.endsWith(key, 's')) {
key = key.slice(0, -1);
}
// Handle 'PropertyBase's
if (value && _.isFunction(value.toJSON)) {
accumulator[key] = value.toJSON();
return accumulator;
}
// Handle Strings
if (_.isString(value)) {
accumulator[key] = value;
return accumulator;
}
// Everything else
accumulator[key] = _.cloneElement(value);
return accumulator;
}, {});
},
/**
* Returns the meta keys associated with the property
*
* @returns {*}
*/
meta () {
return arguments.length ? _.pick(this._, Array.prototype.slice.apply(arguments)) : _.cloneDeep(this._);
},
/**
* Returns the parent of item
*
* @returns {*|undefined}
*/
parent () {
// @todo return grandparent only if it is a list
return this && this.__parent && (this.__parent.__parent || this.__parent) || undefined;
},
/**
* Accepts an object and sets it as the parent of the current property.
*
* @param {Object} parent The object to set as parent.
* @private
*/
setParent (parent) {
_.assignHidden(this, __PARENT, parent);
}
});
_.assign(PropertyBase, /** @lends PropertyBase */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'PropertyBase',
/**
* Filter function to check whether a key starts with underscore or not. These usually are the meta properties. It
* returns `true` if the criteria is matched.
*
* @param {*} value -
* @param {String} key -
*
* @returns {boolean}
*/
propertyIsMeta: function (value, key) {
return _.startsWith(key, '_') && (key !== '_');
},
/**
* Map function that removes the underscore prefix from an object key.
*
* @param {*} value -
* @param {String} key -
* @returns {String}
*/
propertyUnprefixMeta: function (value, key) {
return _.trimStart(key, '_');
},
/**
* Static function which allows calling toJSON() on any object.
*
* @param {Object} obj -
* @returns {*}
*/
toJSON: function (obj) {
return PropertyBase.prototype.toJSON.call(obj);
}
});
module.exports = {
PropertyBase
};

View File

@@ -0,0 +1,708 @@
var _ = require('../util').lodash,
PropertyBase = require('./property-base').PropertyBase,
__PARENT = '__parent',
DEFAULT_INDEX_ATTR = 'id',
DEFAULT_INDEXCASE_ATTR = false,
DEFAULT_INDEXMULTI_ATTR = false,
PropertyList;
/**
* An item constructed of PropertyList.Type.
*
* @typedef {Object} PropertyList.Type
*/
_.inherit((
/**
* @constructor
* @param {Function} type -
* @param {Object} parent -
* @param {Array} populate -
*/
PropertyList = function PostmanPropertyList (type, parent, populate) {
// @todo add this test sometime later
// if (!type) {
// throw new Error('postman-collection: cannot initialise a list without a type parameter');
// }
PropertyList.super_.call(this); // call super with appropriate options
this.setParent(parent); // save reference to parent
_.assign(this, /** @lends PropertyList.prototype */ {
/**
* @private
* @type {Array}
*/
members: this.members || [],
/**
* @private
* @type {Object}
* @note This should not be used, and it's not guaranteed to be in sync with the actual list of members.
*/
reference: this.reference || {},
/**
* @private
* @type {Function}
*/
Type: type
});
// if the type this list holds has its own index key, then use the same
_.getOwn(type, '_postman_propertyIndexKey') && (this._postman_listIndexKey = type._postman_propertyIndexKey);
// if the type has case sensitivity flags, set the same
_.getOwn(type, '_postman_propertyIndexCaseInsensitive') && (this._postman_listIndexCaseInsensitive =
type._postman_propertyIndexCaseInsensitive);
// if the type allows multiple values, set the flag
_.getOwn(type, '_postman_propertyAllowsMultipleValues') && (this._postman_listAllowsMultipleValues =
type._postman_propertyAllowsMultipleValues);
// prepopulate
populate && this.populate(populate);
}), PropertyBase);
_.assign(PropertyList.prototype, /** @lends PropertyList.prototype */ {
/**
* Indicates that this element contains a number of other elements.
*
* @private
*/
_postman_propertyIsList: true,
/**
* Holds the attribute to index this PropertyList by. Default: 'id'
*
* @private
* @type {String}
*/
_postman_listIndexKey: DEFAULT_INDEX_ATTR,
/**
* Holds the attribute whether indexing of this list is case sensitive or not
*
* @private
* @type {String}
*/
_postman_listIndexCaseInsensitive: DEFAULT_INDEXCASE_ATTR,
/**
* Holds the attribute whether exporting the index retains duplicate index items
*
* @private
* @type {String}
*/
_postman_listAllowsMultipleValues: DEFAULT_INDEXMULTI_ATTR,
/**
* Insert an element at the end of this list. When a reference member specified via second parameter is found, the
* member is inserted at an index before the reference member.
*
* @param {PropertyList.Type} item -
* @param {PropertyList.Type|String} [before] -
*/
insert: function (item, before) {
if (!_.isObject(item)) { return; } // do not proceed on empty param
var duplicate = this.indexOf(item),
index;
// remove from previous list
PropertyList.isPropertyList(item[__PARENT]) && (item[__PARENT] !== this) && item[__PARENT].remove(item);
// inject the parent reference
_.assignHidden(item, __PARENT, this);
// ensure that we do not double insert things into member array
(duplicate > -1) && this.members.splice(duplicate, 1);
// find the position of the reference element
before && (before = this.indexOf(before));
// inject to the members array ata position or at the end in case no item is there for reference
(before > -1) ? this.members.splice(before, 0, item) : this.members.push(item);
// store reference by id, so create the index string. we first ensure that the index value is truthy and then
// recheck that the string conversion of the same is truthy as well.
if ((index = item[this._postman_listIndexKey]) && (index = String(index))) {
// desensitise case, if the property needs it to be
this._postman_listIndexCaseInsensitive && (index = index.toLowerCase());
// if multiple values are allowed, the reference may contain an array of items, mapped to an index.
if (this._postman_listAllowsMultipleValues && Object.hasOwnProperty.call(this.reference, index)) {
// if the value is not an array, convert it to an array.
!_.isArray(this.reference[index]) && (this.reference[index] = [this.reference[index]]);
// add the item to the array of items corresponding to this index
this.reference[index].push(item);
}
else {
this.reference[index] = item;
}
}
},
/**
* Insert an element at the end of this list. When a reference member specified via second parameter is found, the
* member is inserted at an index after the reference member.
*
* @param {PropertyList.Type} item -
* @param {PropertyList.Type|String} [after] -
*/
insertAfter: function (item, after) {
// convert item to positional reference
return this.insert(item, this.idx(this.indexOf(after) + 1));
},
/**
* Adds or moves an item to the end of this list.
*
* @param {PropertyList.Type} item -
*/
append: function (item) {
return this.insert(item);
},
/**
* Adds or moves an item to the beginning of this list.
*
* @param {PropertyList.Type} item -
*/
prepend: function (item) {
return this.insert(item, this.idx(0));
},
/**
* Add an item or item definition to this list.
*
* @param {Object|PropertyList.Type} item -
* @todo
* - remove item from original parent if already it has a parent
* - validate that the original parent's constructor matches this parent's constructor
*/
add: function (item) {
// do not proceed on empty param, but empty strings are in fact valid.
// eslint-disable-next-line lodash/prefer-is-nil
if (_.isNull(item) || _.isUndefined(item) || _.isNaN(item)) { return; }
// create new instance of the item based on the type specified if it is not already
this.insert((item.constructor === this.Type) ? item :
// if the property has a create static function, use it.
// eslint-disable-next-line prefer-spread
(_.has(this.Type, 'create') ? this.Type.create.apply(this.Type, arguments) : new this.Type(item)));
},
/**
* Add an item or update an existing item
*
* @param {PropertyList.Type} item -
* @returns {?Boolean}
*/
upsert: function (item) {
// do not proceed on empty param, but empty strings are in fact valid.
if (_.isNil(item) || _.isNaN(item)) { return null; }
var indexer = this._postman_listIndexKey,
existing = this.one(item[indexer]);
if (existing) {
if (!_.isFunction(existing.update)) {
throw new Error('collection: unable to upsert into a list of Type that does not support .update()');
}
existing.update(item);
return false;
}
// since there is no existing item, just add a new one
this.add(item);
return true; // indicate added
},
/**
* Removes all elements from the PropertyList for which the predicate returns truthy.
*
* @param {Function|String|PropertyList.Type} predicate -
* @param {Object} context Optional context to bind the predicate to.
*/
remove: function (predicate, context) {
var match; // to be used if predicate is an ID
!context && (context = this);
if (_.isString(predicate)) {
// if predicate is id, then create a function to remove that
// need to take care of case sensitivity as well :/
match = this._postman_listIndexCaseInsensitive ? predicate.toLowerCase() : predicate;
predicate = function (item) {
var id = item[this._postman_listIndexKey];
this._postman_listIndexCaseInsensitive && (id = id.toLowerCase());
return id === match;
}.bind(this);
}
else if (predicate instanceof this.Type) {
// in case an object reference is sent, prepare it for removal using direct reference comparison
match = predicate;
predicate = function (item) {
return (item === match);
};
}
_.isFunction(predicate) && _.remove(this.members, function (item) {
var index;
if (predicate.apply(context, arguments)) {
if ((index = item[this._postman_listIndexKey]) && (index = String(index))) {
this._postman_listIndexCaseInsensitive && (index = index.toLowerCase());
if (this._postman_listAllowsMultipleValues && _.isArray(this.reference[index])) {
// since we have an array of multiple values, remove only the value for which the
// predicate returned truthy. If the array becomes empty, just delete it.
_.remove(this.reference[index], function (each) {
return each === item;
});
// If the array becomes empty, remove it
/* istanbul ignore next */
(this.reference[index].length === 0) && (delete this.reference[index]);
// If the array contains only one element, remove the array, and assign the element
// as the reference value
(this.reference[index].length === 1) && (this.reference[index] = this.reference[index][0]);
}
else {
delete this.reference[index];
}
}
delete item[__PARENT]; // unlink from its parent
return true;
}
}.bind(this));
},
/**
* Removes all items in the list
*/
clear: function () {
// we unlink every member from it's parent (assuming this is their parent)
this.all().forEach(PropertyList._unlinkItemFromParent);
this.members.length = 0; // remove all items from list
// now we remove all items from index reference
Object.keys(this.reference).forEach(function (key) {
delete this.reference[key];
}.bind(this));
},
/**
* Load one or more items
*
* @param {Object|Array} items -
*/
populate: function (items) {
// if Type supports parsing of string headers then do it before adding it.
_.isString(items) && _.isFunction(this.Type.parse) && (items = this.Type.parse(items));
// add a single item or an array of items.
_.forEach(_.isArray(items) ? items :
// if population is not an array, we send this as single item in an array or send each property separately
// if the core Type supports Type.create
((_.isPlainObject(items) && _.has(this.Type, 'create')) ? items : [items]), this.add.bind(this));
},
/**
* Clears the list and adds new items.
*
* @param {Object|Array} items -
*/
repopulate: function (items) {
this.clear();
this.populate(items);
},
/**
* Add or update values from a source list.
*
* @param {PropertyList|Array} source -
* @param {Boolean} [prune=false] Setting this to `true` will cause the extra items from the list to be deleted
*/
assimilate: function (source, prune) {
var members = PropertyList.isPropertyList(source) ? source.members : source,
list = this,
indexer = list._postman_listIndexKey,
sourceKeys = {}; // keeps track of added / updated keys for later exclusion
if (!_.isArray(members)) {
return;
}
members.forEach(function (item) {
/* istanbul ignore if */
if (!(item && _.has(item, indexer))) { return; }
list.upsert(item);
sourceKeys[item[indexer]] = true;
});
// now remove any variable that is not in source object
// @note - using direct `this.reference` list of keys here so that we can mutate the list while iterating
// on it
if (prune) {
_.forEach(list.reference, function (value, key) {
if (_.has(sourceKeys, key)) { return; } // de not delete if source obj has this variable
list.remove(key); // use PropertyList functions to remove so that the .members array is cleared too
});
}
},
/**
* Returns a map of all items.
*
* @returns {Object}
*/
all: function () {
return _.clone(this.members);
},
/**
* Get Item in this list by `ID` reference. If multiple values are allowed, the last value is returned.
*
* @param {String} id -
* @returns {PropertyList.Type}
*/
one: function (id) {
var val = this.reference[this._postman_listIndexCaseInsensitive ? String(id).toLowerCase() : id];
if (this._postman_listAllowsMultipleValues && Array.isArray(val)) {
return val.length ? val[val.length - 1] :
/* istanbul ignore next */
undefined;
}
return val;
},
/**
* Get the value of an item in this list. This is similar to {@link PropertyList.one} barring the fact that it
* returns the value of the underlying type of the list content instead of the item itself.
*
* @param {String|Function} key -
* @returns {PropertyList.Type|*}
*/
get: function (key) {
var member = this.one(key);
if (!member) { return; } // eslint-disable-line getter-return
return member.valueOf();
},
/**
* Iterate on each item of this list.
*
* @param {Function} iterator -
* @param {Object} context -
*/
each: function (iterator, context) {
_.forEach(this.members, _.isFunction(iterator) ? iterator.bind(context || this.__parent) : iterator);
},
/**
* @param {Function} rule -
* @param {Object} context -
*/
filter: function (rule, context) {
return _.filter(this.members, _.isFunction(rule) && _.isObject(context) ? rule.bind(context) : rule);
},
/**
* Find an item within the item group
*
* @param {Function} rule -
* @param {Object} [context] -
* @returns {Item|ItemGroup}
*/
find: function (rule, context) {
return _.find(this.members, _.isFunction(rule) && _.isObject(context) ? rule.bind(context) : rule);
},
/**
* Iterates over the property list.
*
* @param {Function} iterator Function to call on each item.
* @param {Object} context Optional context, defaults to the PropertyList itself.
*/
map: function (iterator, context) {
return _.map(this.members, _.isFunction(iterator) ? iterator.bind(context || this) : iterator);
},
/**
* Iterates over the property list and accumulates the result.
*
* @param {Function} iterator Function to call on each item.
* @param {*} accumulator Accumulator initial value
* @param {Object} context Optional context, defaults to the PropertyList itself.
*/
reduce: function (iterator, accumulator, context) {
return _.reduce(this.members, _.isFunction(iterator) ?
iterator.bind(context || this) :
/* istanbul ignore next */
iterator
, accumulator);
},
/**
* Returns the length of the PropertyList
*
* @returns {Number}
*/
count: function () {
return this.members.length;
},
/**
* Get a member of this list by it's index
*
* @param {Number} index -
* @returns {PropertyList.Type}
*/
idx: function (index) {
return this.members[index];
},
/**
* Find the index of an item in this list
*
* @param {String|Object} item -
* @returns {Number}
*/
indexOf: function (item) {
return this.members.indexOf(_.isString(item) ? (item = this.one(item)) : item);
},
/**
* Check whether an item exists in this list
*
* @param {String|PropertyList.Type} item -
* @param {*=} value -
* @returns {Boolean}
*/
has: function (item, value) {
var match,
val,
i;
match = _.isString(item) ?
this.reference[this._postman_listIndexCaseInsensitive ? item.toLowerCase() : item] :
this.filter(function (member) {
return member === item;
});
// If we don't have a match, there's nothing to do
if (!match) { return false; }
// if no value is provided, just check if item exists
if (arguments.length === 1) {
return Boolean(_.isArray(match) ? match.length : match);
}
// If this property allows multiple values and we get an array, we need to iterate through it and see
// if any element matches.
if (this._postman_listAllowsMultipleValues && _.isArray(match)) {
for (i = 0; i < match.length; i++) {
// use the value of the current element
val = _.isFunction(match[i].valueOf) ? match[i].valueOf() :
/* istanbul ignore next */
match[i];
if (val === value) { return true; }
}
// no matches were found, so return false here.
return false;
}
// We didn't have an array, so just check if the matched value equals the provided value.
_.isFunction(match.valueOf) && (match = match.valueOf());
return match === value;
},
/**
* Iterates over all parents of the property list
*
* @param {Function} iterator -
* @param {Object=} [context] -
*/
eachParent: function (iterator, context) {
// validate parameters
if (!_.isFunction(iterator)) { return; }
!context && (context = this);
var parent = this.__parent,
prev;
// iterate till there is no parent
while (parent) {
// call iterator with the parent and previous parent
iterator.call(context, parent, prev);
// update references
prev = parent;
parent = parent.__parent;
}
},
/**
* Converts a list of Properties into an object where key is `_postman_propertyIndexKey` and value is determined
* by the `valueOf` function
*
* @param {?Boolean} [excludeDisabled=false] - When set to true, disabled properties are excluded from the resultant
* object.
* @param {?Boolean} [caseSensitive] - When set to true, properties are treated strictly as per their original
* case. The default value for this property also depends on the case insensitivity definition of the current
* property.
* @param {?Boolean} [multiValue=false] - When set to true, only the first value of a multi valued property is
* returned.
* @param {Boolean} [sanitizeKeys=false] - When set to true, properties with falsy keys are removed.
* @todo Change the function signature to an object of options instead of the current structure.
* @returns {Object}
*/
toObject: function (excludeDisabled, caseSensitive, multiValue, sanitizeKeys) {
var obj = {}, // create transformation data accumulator
// gather all the switches of the list
key = this._postman_listIndexKey,
sanitiseKeys = this._postman_sanitizeKeys || sanitizeKeys,
sensitive = !this._postman_listIndexCaseInsensitive || caseSensitive,
multivalue = this._postman_listAllowsMultipleValues || multiValue;
// iterate on each member to create the transformation object
this.each(function (member) {
// Bail out for the current member if ANY of the conditions below is true:
// 1. The member is falsy.
// 2. The member does not have the specified property list index key.
// 3. The member is disabled and disabled properties have to be ignored.
// 4. The member has a falsy key, and sanitize is true.
if (!member || !_.has(member, key) || (excludeDisabled && member.disabled) ||
(sanitiseKeys && !member[key])) {
return;
}
// based on case sensitivity settings, we get the property name of the item
var prop = sensitive ? member[key] : String(member[key]).toLowerCase();
// now, if transformation object already has a member with same property name, we either overwrite it or
// append to an array of values based on multi-value support
if (multivalue && _.has(obj, prop)) {
(!Array.isArray(obj[prop])) && (obj[prop] = [obj[prop]]);
obj[prop].push(member.valueOf());
}
else {
obj[prop] = member.valueOf();
}
});
return obj;
},
/**
* Adds ability to convert a list to a string provided it's underlying format has unparse function defined.
*
* @returns {String}
*/
toString: function () {
if (this.Type.unparse) {
return this.Type.unparse(this.members);
}
return this.constructor ? this.constructor.prototype.toString.call(this) : '';
},
toJSON: function () {
if (!this.count()) {
return [];
}
return _.map(this.members, function (member) {
// use member.toJSON if it exists
if (!_.isEmpty(member) && _.isFunction(member.toJSON)) {
return member.toJSON();
}
return _.reduce(member, function (accumulator, value, key) {
if (value === undefined) { // true/false/null need to be preserved.
return accumulator;
}
// Handle plurality of PropertyLists in the SDK vs the exported JSON.
// Basically, removes the trailing "s" from key if the value is a property list.
if (value && value._postman_propertyIsList && !value._postman_proprtyIsSerialisedAsPlural &&
_.endsWith(key, 's')) {
key = key.slice(0, -1);
}
// Handle 'PropertyBase's
if (value && _.isFunction(value.toJSON)) {
accumulator[key] = value.toJSON();
return accumulator;
}
// Handle Strings
if (_.isString(value)) {
accumulator[key] = value;
return accumulator;
}
// Everything else
accumulator[key] = _.cloneElement(value);
return accumulator;
}, {});
});
}
});
_.assign(PropertyList, /** @lends PropertyList */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'PropertyList',
/**
* Removes child-parent links for the provided PropertyList member.
*
* @param {Property} item - The property for which to perform parent de-linking.
* @private
*/
_unlinkItemFromParent: function (item) {
item.__parent && (delete item.__parent); // prevents V8 from making unnecessary look-ups if there is no __parent
},
/**
* Checks whether an object is a PropertyList
*
* @param {*} obj -
* @returns {Boolean}
*/
isPropertyList: function (obj) {
return Boolean(obj) && ((obj instanceof PropertyList) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', PropertyList._postman_propertyName));
}
});
module.exports = {
PropertyList
};

View File

@@ -0,0 +1,345 @@
var _ = require('../util').lodash,
uuid = require('uuid'),
PropertyBase = require('./property-base').PropertyBase,
Description = require('./description').Description,
Substitutor = require('../superstring').Substitutor,
DISABLED = 'disabled',
DESCRIPTION = 'description',
REGEX_EXTRACT_VARS = Substitutor.REGEX_EXTRACT_VARS,
Property; // constructor
/**
* Recursively traverses a variable and detects all instances of variable
* replacements within the string of the object.
*
* @private
* @param {*} value Any JS variable within which we are trying to discover {{variables}}
* @param {[Object]} seen Set of objects traversed before to avoid infinite recursion
* @param {[Object]} result Set of variables to accumulate result in the recursive call
* @returns {Object} Set of variables
*/
function _findSubstitutions (value, seen = new Set(), result = new Set()) {
if (!value || seen.has(value)) {
return result;
}
if (Array.isArray(value)) {
seen.add(value);
for (let i = 0, ii = value.length; i < ii; i++) {
_findSubstitutions(value[i], seen, result);
}
}
else if (typeof value === 'object') {
seen.add(value);
for (const key in value) {
if (Object.hasOwnProperty.call(value, key)) {
_findSubstitutions(value[key], seen, result);
}
}
}
else if (typeof value === 'string') {
let match;
while ((match = REGEX_EXTRACT_VARS.exec(value)) !== null) {
result.add(match[0].slice(2, -2));
}
}
return result;
}
/**
* @typedef Property.definition
* @property {String=} [id] A unique string that identifies the property.
* @property {String=} [name] A distinctive and human-readable name of the property.
* @property {Boolean=} [disabled] Denotes whether the property is disabled or not.
* @property {Object=} [info] The meta information regarding the Property is provided as the `info` object.
* @property {String=} [info.id] If set, this is used instead of the definition root's id.
* @property {String=} [info.name] If set, this is used instead of the definition root's name.
*/
_.inherit((
/**
* The Property class forms the base of all postman collection SDK elements. This is to be used only for SDK
* development or to extend the SDK with additional functionalities. All SDK classes (constructors) that are
* supposed to be identifyable (i.e. ones that can have a `name` and `id`) are derived from this class.
*
* For more information on what is the structure of the `definition` the function parameter, have a look at
* {@link Property.definition}.
*
* > This is intended to be a private class except for those who want to extend the SDK itself and add more
* > functionalities.
*
* @constructor
* @extends {PropertyBase}
*
* @param {Property.definition=} [definition] Every constructor inherited from `Property` is required to call the
* super constructor function. This implies that construction parameters of every inherited member is propagated
* to be sent up to this point.
*
* @see Property.definition
*/
Property = function PostmanProperty (definition) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Property.super_.apply(this, arguments);
// The definition can have an `info` object that stores the identification of this property. If that is present,
// we use it instead of the definition root.
var src = definition && definition.info || definition,
id;
// first we extract id from all possible sources
// we also check if this property is marked to require an ID, we generate one if not found.
id = (src && src.id) || this.id || (this._ && this._.postman_id) || (this._postman_propertyRequiresId &&
uuid.v4());
/**
* The `id` of the property is a unique string that identifies this property and can be used to refer to
* this property from relevant other places. It is a good practice to define the id or let the system
* auto generate a UUID if one is not defined for properties that require an `id`.
*
* @name id
* @type {String}
* @memberOf Property.prototype
*
* @note The property can also be present in the `postman_id` meta in case it is not specified in the
* object. An auto-generated property is used wherever one is not specified
*/
id && (this.id = id);
/**
* A property can have a distinctive and human-readable name. This is to be used to display the name of the
* property within Postman, Newman or other runtimes that consume collection. In certain cases, the absence
* of name might cause the runtime to use the `id` as a fallback.
*
* @name name
* @memberOf Property.prototype
* @type {String}
*/
src && src.name && (this.name = src.name);
/**
* This (optional) flag denotes whether this property is disabled or not. Usually, this is helpful when a
* property is part of a {@link PropertyList}. For example, in a PropertyList of {@link Header}s, the ones
* that are disabled can be filtered out and not processed.
*
* @type {Boolean}
* @optional
* @name disabled
*
* @memberOf Property.prototype
*/
definition && _.has(definition, DISABLED) && (this.disabled = Boolean(definition.disabled));
/**
* The `description` property holds the detailed documentation of any property.
* It is recommended that this property be updated using the [describe](#describe) function.
*
* @type {Description}
* @see Property#describe
*
* @example <caption>Accessing descriptions of all root items in a collection</caption>
* var fs = require('fs'), // needed to read JSON file from disk
* Collection = require('postman-collection').Collection,
* myCollection;
*
* // Load a collection to memory from a JSON file on disk (say, sample-collection.json)
* myCollection = new Collection(JSON.stringify(fs.readFileSync('sample-collection.json').toString()));
*
* // Log the description of all root items
* myCollection.item.all().forEach(function (item) {
* console.log(item.name || 'Untitled Item');
* item.description && console.log(item.description.toString());
* });
*/
// eslint-disable-next-line max-len
_.has(src, DESCRIPTION) && (this.description = _.createDefined(src, DESCRIPTION, Description, this.description));
}), PropertyBase);
_.assign(Property.prototype, /** @lends Property.prototype */ {
/**
* This function allows to describe the property for the purpose of detailed identification or documentation
* generation. This function sets or updates the `description` child-property of this property.
*
* @param {String} content The content of the description can be provided here as a string. Note that it is expected
* that if the content is formatted in any other way than simple text, it should be specified in the subsequent
* `type` parameter.
* @param {String=} [type="text/plain"] The type of the content.
*
* @example <caption>Add a description to an instance of Collection</caption>
* var Collection = require('postman-collection').Collection,
* mycollection;
*
* // create a blank collection
* myCollection = new Collection();
* myCollection.describe('Hey! This is a cool collection.');
*
* console.log(myCollection.description.toString()); // read the description
*/
describe (content, type) {
(Description.isDescription(this.description) ? this.description : (this.description = new Description()))
.update(content, type);
},
/**
* Returns an object representation of the Property with its variable references substituted.
*
* @example <caption>Resolve an object using variable definitions from itself and its parents</caption>
* property.toObjectResolved();
*
* @example <caption>Resolve an object using variable definitions on a different object</caption>
* property.toObjectResolved(item);
*
* @example <caption>Resolve an object using variables definitions as a flat list of variables</caption>
* property.toObjectResolved(null, [variablesDefinition1, variablesDefinition1], {ignoreOwnVariables: true});
*
* @private
* @draft
* @param {?Item|ItemGroup=} [scope] - One can specifically provide an item or group with `.variables`. In
* the event one is not provided, the variables are taken from this object or one from the parent tree.
* @param {Array<Object>} overrides - additional objects to lookup for variable values
* @param {Object} [options] -
* @param {Boolean} [options.ignoreOwnVariables] - if set to true, `.variables` on self(or scope)
* will not be used for variable resolution. Only variables in `overrides` will be used for resolution.
* @returns {Object|undefined}
* @throws {Error} If `variables` cannot be resolved up the parent chain.
*/
toObjectResolved (scope, overrides, options) {
var ignoreOwnVariables = options && options.ignoreOwnVariables,
variableSourceObj,
variables,
reference;
// ensure you do not substitute variables itself!
reference = this.toJSON();
_.isArray(reference.variable) && (delete reference.variable);
// if `ignoreScopeVariables` is turned on, ignore `.variables` and resolve with only `overrides`
// otherwise find `.variables` on current object or `scope`
if (ignoreOwnVariables) {
return Property.replaceSubstitutionsIn(reference, overrides);
}
// 1. if variables is passed as params, use it or fall back to oneself
// 2. for a source from point (1), and look for `.variables`
// 3. if `.variables` is not found, then rise up the parent to find first .variables
variableSourceObj = scope || this;
do {
variables = variableSourceObj.variables;
variableSourceObj = variableSourceObj.__parent;
} while (!variables && variableSourceObj);
if (!variables) { // worst case = no variable param and none detected in tree or object
throw Error('Unable to resolve variables. Require a List type property for variable auto resolution.');
}
return variables.substitute(reference, overrides);
},
/**
* Returns all the substitutions (variables) that are needed (or referenced) in this property (recursively).
*
* @returns {String[]}
*
* @example
* // returns ['host', 'path1']
* prop.findSubstitutions({request: 'https://{{host}}/{{path1}}-action/'});
*
* @see {Property.findSubstitutions}
*/
findSubstitutions () {
return Property.findSubstitutions(this.toJSON());
}
});
_.assign(Property, /** @lends Property */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Property',
/**
* This function accepts a string followed by a number of variable sources as arguments. One or more variable
* sources can be provided and it will use the one that has the value in left-to-right order.
*
* @param {String} str -
* @param {VariableList|Object|Array.<VariableList|Object>} variables -
* @returns {String}
*/
// @todo: improve algorithm via variable replacement caching
replaceSubstitutions: function (str, variables) {
// if there is nothing to replace, we move on
if (!(str && _.isString(str))) { return str; }
// if variables object is not an instance of substitutor then ensure that it is an array so that it becomes
// compatible with the constructor arguments for a substitutor
!Substitutor.isInstance(variables) && !_.isArray(variables) && (variables = _.tail(arguments));
return Substitutor.box(variables, Substitutor.DEFAULT_VARS).parse(str).toString();
},
/**
* This function accepts an object followed by a number of variable sources as arguments. One or more variable
* sources can be provided and it will use the one that has the value in left-to-right order.
*
* @param {Object} obj -
* @param {Array.<VariableList|Object>} variables -
* @returns {Object}
*/
replaceSubstitutionsIn: function (obj, variables) {
// if there is nothing to replace, we move on
if (!(obj && _.isObject(obj))) {
return obj;
}
// convert the variables to a substitutor object (will not reconvert if already substitutor)
variables = Substitutor.box(variables, Substitutor.DEFAULT_VARS);
var customizer = function (objectValue, sourceValue) {
objectValue = objectValue || {};
if (!_.isString(sourceValue)) {
_.forOwn(sourceValue, function (value, key) {
sourceValue[key] = customizer(objectValue[key], value);
});
return sourceValue;
}
return this.replaceSubstitutions(sourceValue, variables);
}.bind(this);
return _.mergeWith({}, obj, customizer);
},
/**
* This function recursively traverses a variable and detects all instances of variable replacements
* within the string of the object
*
* @param {*} obj Any JS variable within which we are trying to discover {{variables}}
* @returns {String[]}
*
* @example
* // returns ['host', 'path1']
* Property.findSubstitutions({request: 'https://{{host}}/{{path1}}-action/'});
*
* @todo
* - tonne of scope for performance optimizations
* - accept a reference variable scope so that substitutions can be applied to find recursive
* replacements (e.g. {{hello-{{hi}}-var}})
*/
findSubstitutions: function (obj) {
return Array.from(_findSubstitutions(obj));
}
});
module.exports = {
Property
};

View File

@@ -0,0 +1,75 @@
var _ = require('../util').lodash,
PropertyList = require('./property-list').PropertyList,
ProxyConfig = require('./proxy-config').ProxyConfig,
Url = require('./url').Url,
ProxyConfigList;
_.inherit((
/**
* @constructor
* @extends {PropertyList}
*
* @param {Object} parent -
* @param {Array} populate The list of proxy objects
*
* @example <caption>Create a new ProxyConfigList</caption>
* var ProxyConfigList = require('postman-collection').ProxyConfigList,
* myProxyConfig = new ProxyConfigList({}, [
* {match: 'https://example.com/*', host: 'proxy.com', port: 8080, tunnel: true},
* {match: 'http+https://example2.com/*', host: 'proxy2.com'},
* ]);
*/
ProxyConfigList = function PostmanProxyConfigList (parent, populate) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
ProxyConfigList.super_.call(this, ProxyConfig, parent, populate);
}), PropertyList);
_.assign(ProxyConfigList.prototype, /** @lends ProxyConfigList.prototype */ {
/**
* Matches and gets the proxy config for the particular url.
*
* @returns {ProxyConfig.definition} The matched proxyConfig object
* @param {URL=} [url] The url for which the proxy config needs to be fetched
*/
resolve (url) {
// url must be either string or an instance of url.
if (!_.isString(url) && !Url.isUrl(url)) {
return;
}
// @todo - use a fixed-length cacheing of regexes in future
return this.find(function (proxyConfig) {
return !proxyConfig.disabled && proxyConfig.test(url);
});
}
});
_.assign(ProxyConfigList, /** @lends ProxyConfigList */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*
* @note that this is directly accessed only in case of ProxyConfigList from _.findValue lodash util mixin
*/
_postman_propertyName: 'ProxyConfigList',
/**
* Checks whether an object is a ProxyConfigList
*
* @param {*} obj -
* @returns {Boolean}
*/
isProxyConfigList: function (obj) {
return Boolean(obj) && ((obj instanceof ProxyConfigList) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', ProxyConfigList._postman_propertyName));
}
});
module.exports = {
ProxyConfigList
};

View File

@@ -0,0 +1,301 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
PropertyList = require('./property-list').PropertyList,
Url = require('./url').Url,
UrlMatchPattern = require('../url-pattern/url-match-pattern').UrlMatchPattern,
UrlMatchPatternList = require('../url-pattern/url-match-pattern-list').UrlMatchPatternList,
ProxyConfig,
PROTOCOL_DELIMITER = UrlMatchPattern.PROTOCOL_DELIMITER,
E = '',
COLON = ':',
DEFAULT_PORT = 8080,
PROTOCOL_HOST_SEPARATOR = '://',
MATCH_ALL_HOST_AND_PATH = '*:*/*',
AUTH_CREDENTIALS_SEPARATOR = '@',
DEFAULT_PROTOCOL = 'http',
ALLOWED_PROTOCOLS = ['http', 'https'],
// 'http+https://*:*/*'
DEFAULT_PATTERN = ALLOWED_PROTOCOLS.join(PROTOCOL_DELIMITER) + PROTOCOL_HOST_SEPARATOR + MATCH_ALL_HOST_AND_PATH;
/**
* The following is the object structure accepted as constructor parameter while calling `new ProxyConfig(...)`. It is
* also the structure exported when {@link Property#toJSON} or {@link Property#toObjectResolved} is called on a
* Proxy instance.
*
* @typedef ProxyConfig.definition
*
* @property {String=} [match = 'http+https://*\/*'] The match for which the proxy needs to be configured.
* @property {String=} [host = ''] The proxy server url.
* @property {Number=} [port = 8080] The proxy server port number.
* @property {Boolean=} [tunnel = false] The tunneling option for the proxy request.
* @property {Boolean=} [disabled = false] To override the proxy for the particular url, you need to provide true.
* @property {Boolean=} [authenticate = false] To enable authentication for the proxy, you need to provide true.
* @property {String=} [username] The proxy authentication username
* @property {String=} [password] The proxy authentication password
*
* @example <caption>JSON definition of an example proxy object</caption>
* {
* "match": "http+https://example.com/*",
* "host": "proxy.com",
* "port": "8080",
* "tunnel": true,
* "disabled": false,
* "authenticate": true,
* "username": "proxy_username",
* "password": "proxy_password"
* }
*/
_.inherit((
/**
* A ProxyConfig definition that represents the proxy configuration for an url match.
* Properties can then use the `.toObjectResolved` function to procure an object representation of the property with
* all the variable references replaced by corresponding values.
*
* @constructor
* @extends {Property}
* @param {ProxyConfig.definition=} [options] - Specifies object with props matches, server and tunnel.
*
* @example <caption>Create a new ProxyConfig</caption>
* var ProxyConfig = require('postman-collection').ProxyConfig,
* myProxyConfig = new ProxyConfig({
* host: 'proxy.com',
* match: 'http+https://example.com/*',
* port: 8080,
* tunnel: true,
* disabled: false,
* authenticate: true,
* username: 'proxy_username',
* password: 'proxy_password'
* });
*/
ProxyConfig = function ProxyConfig (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
ProxyConfig.super_.call(this, options);
// Assign defaults before proceeding
_.assign(this, /** @lends ProxyConfig */ {
/**
* The proxy server host or ip
*
* @type {String}
*/
host: E,
/**
* The url mach for which the proxy has been associated with.
*
* @type {String}
*/
match: new UrlMatchPattern(DEFAULT_PATTERN),
/**
* The proxy server port number
*
* @type {Number}
*/
port: DEFAULT_PORT,
/**
* This represents whether the tunneling needs to done while proxying this request.
*
* @type Boolean
*/
tunnel: false,
/**
* Proxy bypass list
*
* @type {UrlMatchPatternList}
*/
bypass: undefined,
/**
* Enable proxy authentication
*
* @type {Boolean}
*/
authenticate: false,
/**
* Proxy auth username
*
* @type {String}
*/
username: undefined,
/**
* Proxy auth password
*
* @type {String}
*/
password: undefined
});
this.update(options);
}), Property);
_.assign(ProxyConfig.prototype, /** @lends ProxyConfig.prototype */ {
/**
* Defines whether this property instances requires an id
*
* @private
* @readOnly
* @type {Boolean}
*/
_postman_propertyRequiresId: true,
/**
* Updates the properties of the proxy object based on the options provided.
*
* @param {ProxyConfig.definition} options The proxy object structure.
*/
update: function (options) {
if (!_.isObject(options)) {
return;
}
var parsedUrl,
port = _.get(options, 'port') >> 0;
if (_.isString(options.host)) {
// strip the protocol from given host
parsedUrl = new Url(options.host);
this.host = parsedUrl.getHost();
}
_.isString(options.match) && (this.match = new UrlMatchPattern(options.match));
_.isString(_.get(options, 'match.pattern')) && (this.match = new UrlMatchPattern(options.match.pattern));
port && (this.port = port);
_.isBoolean(options.tunnel) && (this.tunnel = options.tunnel);
// todo: Add update method in parent class Property and call that here
_.isBoolean(options.disabled) && (this.disabled = options.disabled);
_.isBoolean(options.authenticate) && (this.authenticate = options.authenticate);
_.isString(options.username) && (this.username = options.username);
_.isString(options.password) && (this.password = options.password);
// init bypass list from the given array
if (Array.isArray(options.bypass)) {
this.bypass = new UrlMatchPatternList(null, options.bypass);
}
// or, convert existing PropertyList or UrlMatchPatternList
else if (PropertyList.isPropertyList(options.bypass)) {
this.bypass = new UrlMatchPatternList(null, options.bypass.all());
}
},
/**
* Updates the protocols in the match pattern
*
* @param {Array.<String>} protocols The array of protocols
*/
updateProtocols: function (protocols) {
if (!protocols) {
return;
}
var updatedProtocols,
hostAndPath = _.split(this.match.pattern, PROTOCOL_HOST_SEPARATOR)[1];
if (!hostAndPath) {
return;
}
updatedProtocols = _.intersection(ALLOWED_PROTOCOLS, _.castArray(protocols));
_.isEmpty(updatedProtocols) && (updatedProtocols = ALLOWED_PROTOCOLS);
this.match.update({
pattern: updatedProtocols.join(PROTOCOL_DELIMITER) + PROTOCOL_HOST_SEPARATOR + hostAndPath
});
},
/**
* Tests the url string with the match provided.
* Follows the https://developer.chrome.com/extensions/match_patterns pattern for pattern validation and matching
*
* @param {String=} [urlStr] The url string for which the proxy match needs to be done.
*/
test: function (urlStr) {
var protocol = Url.isUrl(urlStr) ? urlStr.protocol : (Url.parse(urlStr || E).protocol || E);
// to allow target URLs without any protocol. e.g.: 'foo.com/bar'
if (_.isEmpty(protocol)) {
protocol = DEFAULT_PROTOCOL;
urlStr = protocol + PROTOCOL_HOST_SEPARATOR + urlStr;
}
// this ensures we don't proceed any further for any non-supported protocol
if (!_.includes(ALLOWED_PROTOCOLS, protocol)) {
return false;
}
// don't proceed if the given URL should skip use of a proxy all together
if (this.bypass && this.bypass.test(urlStr)) {
return false;
}
return this.match.test(urlStr);
},
/**
* Returns the proxy server url.
*
* @returns {String}
*/
getProxyUrl: function () {
var auth = E;
// Add authentication method to URL if the same is requested. We do it this way because
// this is how `postman-request` library accepts auth credentials in its proxy configuration.
if (this.authenticate) {
auth = encodeURIComponent(this.username || E);
if (this.password) {
auth += COLON + encodeURIComponent(this.password);
}
if (auth) {
auth += AUTH_CREDENTIALS_SEPARATOR;
}
}
return DEFAULT_PROTOCOL + PROTOCOL_HOST_SEPARATOR + auth + this.host + COLON + this.port;
},
/**
* Returns the protocols supported.
*
* @returns {Array.<String>}
*/
getProtocols: function () {
return this.match.getProtocols();
}
});
_.assign(ProxyConfig, /** @lends ProxyConfig */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'ProxyConfig',
/**
* Check whether an object is an instance of PostmanItem.
*
* @param {*} obj -
* @returns {Boolean}
*/
isProxyConfig: function (obj) {
return Boolean(obj) && ((obj instanceof ProxyConfig) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', ProxyConfig._postman_propertyName));
}
});
module.exports = {
ProxyConfig,
ALLOWED_PROTOCOLS,
DEFAULT_PATTERN
};

View File

@@ -0,0 +1,291 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
PropertyList = require('./property-list').PropertyList,
E = '',
AMPERSAND = '&',
STRING = 'string',
EQUALS = '=',
EMPTY = '',
HASH = '#',
REGEX_HASH = /#/g,
REGEX_EQUALS = /=/g, // eslint-disable-line no-div-regex
REGEX_AMPERSAND = /&/g,
REGEX_EXTRACT_VARS = /{{[^{}]*[&#=][^{}]*}}/g,
QueryParam,
/**
* Percent encode reserved chars (&, = and #) in the given string.
*
* @private
* @param {String} str -
* @param {Boolean} encodeEquals -
* @returns {String}
*/
encodeReservedChars = function (str, encodeEquals) {
if (!str) {
return str;
}
// eslint-disable-next-line lodash/prefer-includes
str.indexOf(AMPERSAND) !== -1 && (str = str.replace(REGEX_AMPERSAND, '%26'));
// eslint-disable-next-line lodash/prefer-includes
str.indexOf(HASH) !== -1 && (str = str.replace(REGEX_HASH, '%23'));
// eslint-disable-next-line lodash/prefer-includes
encodeEquals && str.indexOf(EQUALS) !== -1 && (str = str.replace(REGEX_EQUALS, '%3D'));
return str;
},
/**
* Normalize the given param string by percent-encoding the reserved chars
* such that it won't affect the re-parsing.
*
* @note `&`, `=` and `#` needs to be percent-encoded otherwise re-parsing
* the same URL string will generate different output
*
* @private
* @param {String} str -
* @param {Boolean} encodeEquals -
* @returns {String}
*/
normalizeParam = function (str, encodeEquals) {
// bail out if the given sting is null or empty
if (!(str && typeof str === STRING)) {
return str;
}
// bail out if the given string does not include reserved chars
// eslint-disable-next-line lodash/prefer-includes
if (str.indexOf(AMPERSAND) === -1 && str.indexOf(HASH) === -1) {
// eslint-disable-next-line lodash/prefer-includes
if (!(encodeEquals && str.indexOf(EQUALS) !== -1)) {
return str;
}
}
var normalizedString = '',
pointer = 0,
variable,
match,
index;
// find all the instances of {{<variable>}} which includes reserved chars
while ((match = REGEX_EXTRACT_VARS.exec(str)) !== null) {
variable = match[0];
index = match.index;
// [pointer, index) string is normalized + the matched variable
normalizedString += encodeReservedChars(str.slice(pointer, index), encodeEquals) + variable;
// update the pointer
pointer = index + variable.length;
}
// whatever left in the string is normalized as well
if (pointer < str.length) {
normalizedString += encodeReservedChars(str.slice(pointer), encodeEquals);
}
return normalizedString;
};
/**
* @typedef QueryParam.definition
* @property {String} key The name ("key") of the query parameter.
* @property {String} value The value of the parameter.
*/
_.inherit((
/**
* Represents a URL query parameter, which can exist in request URL or POST data.
*
* @constructor
* @extends {Property}
* @param {FormParam.definition|String} options Pass the initial definition of the query parameter. In case of
* string, the query parameter is parsed using {@link QueryParam.parseSingle}.
*/
QueryParam = function PostmanQueryParam (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
QueryParam.super_.apply(this, arguments);
this.update(options);
}), Property);
_.assign(QueryParam.prototype, /** @lends QueryParam.prototype */ {
/**
* Converts the QueryParameter to a single param string.
*
* @returns {String}
*/
toString () {
return QueryParam.unparseSingle(this);
},
/**
* Updates the key and value of the query parameter
*
* @param {String|Object} param -
* @param {String} param.key -
* @param {String=} [param.value] -
*/
update (param) {
_.assign(this, /** @lends QueryParam.prototype */ _.isString(param) ? QueryParam.parseSingle(param) : {
key: _.get(param, 'key'), // we do not replace falsey with blank string since null has a meaning
value: _.get(param, 'value')
});
_.has(param, 'system') && (this.system = param.system);
},
valueOf () {
return _.isString(this.value) ? this.value : EMPTY;
}
});
_.assign(QueryParam, /** @lends QueryParam */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'QueryParam',
/**
* Declare the list index key, so that property lists of query parameters work correctly
*
* @type {String}
*/
_postman_propertyIndexKey: 'key',
/**
* Query params can have multiple values, so set this to true.
*
* @type {Boolean}
*/
_postman_propertyAllowsMultipleValues: true,
/**
* Parse a query string into an array of objects, where each object contains a key and a value.
*
* @param {String} query -
* @returns {Array}
*/
parse: function (query) {
return _.isString(query) ? query.split(AMPERSAND).map(QueryParam.parseSingle) : [];
},
/**
* Parses a single query parameter.
*
* @param {String} param -
* @param {Number} idx -
* @param {String[]} all - array of all params, in case this is being called while parsing multiple params.
* @returns {{key: String|null, value: String|null}}
*/
parseSingle: function (param, idx, all) {
// helps handle weird edge cases such as "/get?a=b&&"
if (param === EMPTY && // if param is empty
_.isNumber(idx) && // this and the next condition ensures that this is part of a map call
_.isArray(all) &&
idx !== (all && (all.length - 1))) { // not last parameter in the array
return { key: null, value: null };
}
var index = (typeof param === STRING) ? param.indexOf(EQUALS) : -1,
paramObj = {};
// this means that there was no value for this key (not even blank, so we store this info) and the value is set
// to null
if (index < 0) {
paramObj.key = param.substr(0, param.length);
paramObj.value = null;
}
else {
paramObj.key = param.substr(0, index);
paramObj.value = param.substr(index + 1);
}
return paramObj;
},
/**
* Create a query string from array of parameters (or object of key-values).
*
* @note Disabled parameters are excluded.
*
* @param {Array|Object} params -
* @returns {String}
*/
unparse: function (params) {
if (!params) { return EMPTY; }
var str,
firstEnabledParam = true;
// Convert hash maps to an array of params
if (!_.isArray(params) && !PropertyList.isPropertyList(params)) {
return _.reduce(params, function (result, value, key) {
result && (result += AMPERSAND);
return result + QueryParam.unparseSingle({ key, value });
}, EMPTY);
}
// construct a query parameter string from the list, with considerations for disabled values
str = params.reduce(function (result, param) {
// bail out if param is disabled
if (param.disabled === true) { return result; }
// don't add '&' for the very first enabled param
if (firstEnabledParam) {
firstEnabledParam = false;
}
// add '&' before concatenating param
else {
result += AMPERSAND;
}
return result + QueryParam.unparseSingle(param);
}, EMPTY);
return str;
},
/**
* Takes a query param and converts to string
*
* @param {Object} obj -
* @returns {String}
*/
unparseSingle: function (obj) {
if (!obj) { return EMPTY; }
var key = obj.key,
value = obj.value,
result;
if (typeof key === STRING) {
result = normalizeParam(key, true);
}
else {
result = E;
}
if (typeof value === STRING) {
result += EQUALS + normalizeParam(value);
}
return result;
}
});
module.exports = {
QueryParam
};

View File

@@ -0,0 +1,188 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
VariableList = require('./variable-list').VariableList,
RequestAuth;
/**
* This defines the definition of the authentication method to be used.
*
* @typedef RequestAuth.definition
* @property {String=} type The Auth type to use. Check the names in {@link AuthTypes}
*
* @example <caption>Sample auth definition for Basic Auth</caption>
* {
* "type": "basic",
* "basic": [
* { "key": "username", "value": "postman" },
* { "key": "password", "value": "secrets" }
* ]
* }
*/
_.inherit((
/**
* A Postman Auth definition that comprehensively represents different types of auth mechanisms available.
*
* @constructor
* @extends {Property}
*
* @param {RequestAuth.definition} options Pass the initial definition of the Auth.
* @param {Property|PropertyList=} [parent] optionally pass the parent of this auth. aides variable resolution.
*
* @example <caption>Creating a request with two auth data and one selected</caption>
* var auth = new RequestAuth({
* type: 'digest',
*
* basic: [
* { key: "username", value: "postman" },
* { key: "password", value: "secrets" }
* ],
* digest: [
* { key: "nonce", value: "aef54cde" },
* { key: "realm", value: "items.x" }
* ]
* });
*
* // change the selected auth
* auth.use('basic');
*/
RequestAuth = function PostmanRequestAuth (options, parent) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
RequestAuth.super_.call(this, options);
// set the parent
parent && this.setParent(parent);
// set the type, if passed via options
if (_.has(options, 'type')) {
this.use(options.type);
}
// load all possible auth parameters from options
_.forEach(_.omit(options, 'type'), this.update.bind(this));
}), Property);
_.assign(RequestAuth.prototype, /** @lends RequestAuth.prototype */ {
/**
* Update the parameters of a specific authentication type. If none is provided then it uses the one marked as to be
* used.
*
* @param {VariableList|Array|Object} options -
* @param {String=} [type=this.type] -
*/
update (options, type) {
// update must have options
if (!_.isObject(options)) { return; }
// choose default from existing type if not provided
if (!type) { type = this.type; }
// validate type parameter and return in case type is not valid.
if (!RequestAuth.isValidType(type)) { return; }
var parameters = this[type];
// in case the type holder is not created, we create one and send the population variables
if (!VariableList.isVariableList(parameters)) {
// @todo optimise the handling of legacy object type auth parameters
parameters = this[type] = new VariableList(this);
parameters._postman_requestAuthType = type;
}
// we simply assimilate the new options either it is an array or an object
if (_.isArray(options) || VariableList.isVariableList(options)) {
parameters.assimilate(options);
}
else {
parameters.syncFromObject(options, false, false); // params: no need to track and no need to prune
}
},
/**
* Sets the authentication type to be used by this item.
*
* @param {String} type -
* @param {VariableList|Array|Object} options - note that options set here would replace all existing
* options for the particular auth
*/
use (type, options) {
if (!RequestAuth.isValidType(type)) { return; }
this.type = type; // set the type
var parameters = this[type];
if (!VariableList.isVariableList(parameters)) {
parameters = this[type] = new VariableList(this);
}
// we simply assimilate the new options either it is an array or an object
if (_.isArray(options) || VariableList.isVariableList(options)) {
parameters.assimilate(options);
}
else {
parameters.syncFromObject(options, false, false); // params: no need to track and no need to prune
}
},
/**
* @private
* @deprecated discontinued in v4.0
*/
current () {
throw new Error('`Request#current` has been discontinued, use `Request#parameters` instead.');
},
/**
* Returns the parameters of the selected auth type
*
* @returns {VariableList}
*/
parameters () {
return this[this.type];
},
/**
* Clears the definition of an auth type.
*
* @param {String} type -
*/
clear (type) {
if (!(RequestAuth.isValidType(type) && VariableList.isVariableList(this[type]))) {
return;
}
// clear the variable list
this[type].clear();
// if it is not a currently selected auth type, do not delete the variable list, but simply delete it
if (type !== this.type) {
delete this[type];
}
}
});
_.assign(RequestAuth, /** @lends RequestAuth */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'RequestAuth',
/**
* Determines whether an authentication type name is valid or not
*
* @param {String} type -
* @returns {Boolean}
*/
isValidType: function (type) {
// no auth name can be "type", else will have namespace collision with type selector
return _.isString(type) && (type !== 'type');
}
});
module.exports = {
RequestAuth
};

View File

@@ -0,0 +1,248 @@
var _ = require('../util').lodash,
PropertyBase = require('./property-base').PropertyBase,
PropertyList = require('./property-list').PropertyList,
QueryParam = require('./query-param').QueryParam,
FormParam = require('./form-param').FormParam,
EMPTY = '',
RequestBody;
/**
* @typedef RequestBody.definition
* @property {String} mode
* @property {String} raw
* @property {String} file
* @property {Object} graphql
* @property {Object[]} formdata
* @property {Object[]|String} urlencoded
*/
_.inherit((
/**
* RequestBody holds data related to the request body. By default, it provides a nice wrapper for url-encoded,
* form-data, and raw types of request bodies.
*
* @constructor
* @extends {PropertyBase}
*
* @param {Object} options -
*/
RequestBody = function PostmanRequestBody (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
RequestBody.super_.apply(this, arguments);
if (!options) { return; } // in case definition object is missing, there is no point moving forward
this.update(options);
}), PropertyBase);
_.assign(RequestBody.prototype, /** @lends RequestBody.prototype */ {
/**
* Set the content of this request data
*
* @param {Object} options -
*/
update (options) {
_.isString(options) && (options = { mode: 'raw', raw: options });
if (!options.mode) { return; } // need a valid mode @todo raise error?
var mode = RequestBody.MODES[options.mode.toString().toLowerCase()] || RequestBody.MODES.raw,
urlencoded = options.urlencoded,
formdata = options.formdata,
graphql = options.graphql,
file = options.file,
raw = options.raw;
// Handle URL Encoded data
if (options.urlencoded) {
_.isString(options.urlencoded) && (urlencoded = QueryParam.parse(options.urlencoded));
urlencoded = new PropertyList(QueryParam, this, urlencoded);
}
// Handle Form data
if (options.formdata) {
formdata = new PropertyList(FormParam, this, options.formdata);
}
// Handle GraphQL data
if (options.graphql) {
graphql = {
query: graphql.query,
operationName: graphql.operationName,
variables: graphql.variables
};
}
_.isString(options.file) && (file = { src: file });
// If mode is raw but options does not give raw content, set it to empty string
(mode === RequestBody.MODES.raw && !raw) && (raw = '');
// If mode is urlencoded but options does not provide any content, set it to an empty property list
(mode === RequestBody.MODES.urlencoded && !urlencoded) && (urlencoded = new PropertyList(QueryParam, this, []));
// If mode is formdata but options does not provide any content, set it to an empty property list
(mode === RequestBody.MODES.formdata && !formdata) && (formdata = new PropertyList(FormParam, this, []));
// If mode is graphql but options does not provide any content, set empty query
(mode === RequestBody.MODES.graphql && !graphql) && (graphql = {});
_.assign(this, /** @lends RequestBody.prototype */ {
/**
* Indicates the type of request data to use.
*
* @type {String}
*/
mode: mode,
/**
* If the request has raw body data associated with it, the data is held in this field.
*
* @type {String}
*/
raw: raw,
/**
* Any URL encoded body params go here.
*
* @type {PropertyList<QueryParam>}
*/
urlencoded: urlencoded,
/**
* Form data parameters for this request are held in this field.
*
* @type {PropertyList<FormParam>}
*/
formdata: formdata,
/**
* Holds a reference to a file which should be read as the RequestBody. It can be a file path (when used
* with Node) or a unique ID (when used with the browser).
*
* @note The reference stored here should be resolved by a resolver function (which should be provided to
* the Postman Runtime).
*/
file: file,
/**
* If the request has raw graphql data associated with it, the data is held in this field.
*
* @type {Object}
*/
graphql: graphql,
/**
* If the request has body Options associated with it, the data is held in this field.
*
* @type {Object}
*/
options: _.isObject(options.options) ? options.options : undefined,
/**
* Indicates whether to include body in request or not.
*
* @type {Boolean}
*/
disabled: options.disabled
});
},
/**
* Stringifies and returns the request body.
*
* @note FormData is not supported yet.
* @returns {*}
*/
toString () {
// Formdata. Goodluck.
if (this.mode === RequestBody.MODES.formdata || this.mode === RequestBody.MODES.file) {
// @todo: implement this, check if we need to return undefined or something.
return EMPTY;
}
if (this.mode === RequestBody.MODES.urlencoded) {
return PropertyList.isPropertyList(this.urlencoded) ? QueryParam.unparse(this.urlencoded.all()) :
((this.urlencoded && _.isFunction(this.urlencoded.toString)) ? this.urlencoded.toString() : EMPTY);
}
if (this.mode === RequestBody.MODES.raw) {
return (this.raw && _.isFunction(this.raw.toString)) ? this.raw.toString() : EMPTY;
}
return EMPTY;
},
/**
* If the request body is set to a mode, but does not contain data, then we should not be sending it.
*
* @returns {Boolean}
*/
isEmpty () {
var mode = this.mode,
data = mode && this[mode];
// bail out if there's no data for the selected mode
if (!data) {
return true;
}
// Handle file mode
// @note this is a legacy exception. ideally every individual data mode
// in future would declare its "empty state".
if (mode === RequestBody.MODES.file) {
return !(data.src || data.content);
}
if (_.isString(data)) {
return (data.length === 0);
}
if (_.isFunction(data.count)) { // handle for property lists
return (data.count() === 0);
}
return _.isEmpty(data); // catch all for remaining data modes
},
/**
* Convert the request body to JSON compatible plain object
*
* @returns {Object}
*/
toJSON () {
var obj = PropertyBase.toJSON(this);
// make sure that file content is removed because it is non-serializable ReadStream
_.unset(obj, 'file.content');
return obj;
}
});
_.assign(RequestBody, /** @lends RequestBody **/{
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'RequestBody',
/**
* @enum {string} MODES
*/
MODES: {
file: 'file',
formdata: 'formdata',
graphql: 'graphql',
raw: 'raw',
urlencoded: 'urlencoded'
}
});
module.exports = {
RequestBody
};

View File

@@ -0,0 +1,418 @@
var util = require('../util'),
_ = util.lodash,
PropertyBase = require('./property-base').PropertyBase,
Property = require('./property').Property,
Url = require('./url').Url,
ProxyConfig = require('./proxy-config').ProxyConfig,
Certificate = require('./certificate').Certificate,
HeaderList = require('./header-list').HeaderList,
RequestBody = require('./request-body').RequestBody,
RequestAuth = require('./request-auth').RequestAuth,
Request,
/**
* Default request method
*
* @private
* @const
* @type {String}
*/
DEFAULT_REQ_METHOD = 'GET',
/**
* Content length header name
*
* @private
* @const
* @type {String}
*/
CONTENT_LENGTH = 'Content-Length',
/**
* Single space
*
* @private
* @const
* @type {String}
*/
SP = ' ',
/**
* Carriage return + line feed
*
* @private
* @const
* @type {String}
*/
CRLF = '\r\n',
/**
* HTTP version
*
* @private
* @const
* @type {String}
*/
HTTP_X_X = 'HTTP/X.X',
/**
* @private
* @type {Boolean}
*/
supportsBuffer = (typeof Buffer !== undefined) && _.isFunction(Buffer.byteLength),
/**
* Source of request body size calculation.
* Either computed from body or used Content-Length header value.
*
* @private
* @const
* @type {Object}
*/
SIZE_SOURCE = {
computed: 'COMPUTED',
contentLength: 'CONTENT-LENGTH'
};
/**
* @typedef Request.definition
* @property {String|Url} url The URL of the request. This can be a {@link Url.definition} or a string.
* @property {String} method The request method, e.g: "GET" or "POST".
* @property {Array<Header.definition>} header The headers that should be sent as a part of this request.
* @property {RequestBody.definition} body The request body definition.
* @property {RequestAuth.definition} auth The authentication/signing information for this request.
* @property {ProxyConfig.definition} proxy The proxy information for this request.
* @property {Certificate.definition} certificate The certificate information for this request.
*/
_.inherit((
/**
* A Postman HTTP request object.
*
* @constructor
* @extends {Property}
* @param {Request.definition} options -
*/
Request = function PostmanRequest (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Request.super_.apply(this, arguments);
// if the definition is a string, it implies that this is a get of URL
(typeof options === 'string') && (options = {
url: options
});
// Create the default properties
_.assign(this, /** @lends Request.prototype */ {
/**
* @type {Url}
*/
url: new Url(),
/**
* @type {HeaderList}
*/
headers: new HeaderList(this, options && options.header),
// Although a similar check is being done in the .update call below, this handles falsy options as well.
/**
* @type {String}
* @todo: Clean this up
*/
// the negated condition is required to keep DEFAULT_REQ_METHOD as a fallback
method: _.has(options, 'method') && !_.isNil(options.method) ?
String(options.method).toUpperCase() : DEFAULT_REQ_METHOD
});
this.update(options);
}), Property);
_.assign(Request.prototype, /** @lends Request.prototype */ {
/**
* Updates the different properties of the request.
*
* @param {Request.definition} options -
*/
update: function (options) {
// Nothing to do
if (!options) { return; }
// The existing url is updated.
_.has(options, 'url') && this.url.update(options.url);
// The existing list of headers must be cleared before adding the given headers to it.
options.header && this.headers.repopulate(options.header);
// Only update the method if one is provided.
_.has(options, 'method') && (this.method = _.isNil(options.method) ?
DEFAULT_REQ_METHOD : String(options.method).toUpperCase());
// The rest of the properties are not assumed to exist so we merge in the defined ones.
_.mergeDefined(this, /** @lends Request.prototype */ {
/**
* @type {RequestBody|undefined}
*/
body: _.createDefined(options, 'body', RequestBody),
// auth is a special case, empty RequestAuth should not be created for falsy values
// to allow inheritance from parent
/**
* @type {RequestAuth}
*/
auth: options.auth ? new RequestAuth(options.auth) : undefined,
/**
* @type {ProxyConfig}
*/
proxy: options.proxy && new ProxyConfig(options.proxy),
/**
* @type {Certificate|undefined}
*/
certificate: options.certificate && new Certificate(options.certificate)
});
},
/**
* Sets authentication method for the request
*
* @param {?String|RequestAuth.definition} type -
* @param {VariableList=} [options] -
*
* @note This function was previously (in v2 of SDK) used to clone request and populate headers. Now it is used to
* only set auth information to request
*
* @note that ItemGroup#authorizeUsing depends on this function
*/
authorizeUsing: function (type, options) {
if (_.isObject(type) && _.isNil(options)) {
options = _.omit(type, 'type');
type = type.type;
}
// null = delete request
if (type === null) {
_.has(this, 'auth') && (delete this.auth);
return;
}
if (!RequestAuth.isValidType(type)) {
return;
}
// create a new authentication data
if (!this.auth) {
this.auth = new RequestAuth(null, this);
}
else {
this.auth.clear(type);
}
this.auth.use(type, options);
},
/**
* Returns an object where the key is a header name and value is the header value.
*
* @param {Object=} options -
* @param {Boolean} options.ignoreCase When set to "true", will ensure that all the header keys are lower case.
* @param {Boolean} options.enabled Only get the enabled headers
* @param {Boolean} options.multiValue When set to "true", duplicate header values will be stored in an array
* @param {Boolean} options.sanitizeKeys When set to "true", headers with falsy keys are removed
* @returns {Object}
* @note If multiple headers are present in the same collection with same name, but different case
* (E.g "x-forward-port" and "X-Forward-Port", and `options.ignoreCase` is set to true,
* the values will be stored in an array.
*/
getHeaders: function getHeaders (options) {
!options && (options = {});
// @note: options.multiValue will not be respected since, Header._postman_propertyAllowsMultipleValues
// gets higher precedence in PropertyLists.toObject.
// @todo: sanitizeKeys for headers by default.
return this.headers.toObject(options.enabled, !options.ignoreCase, options.multiValue, options.sanitizeKeys);
},
/**
* Calls the given callback on each Header object contained within the request.
*
* @param {Function} callback -
*/
forEachHeader: function forEachHeader (callback) {
this.headers.all().forEach(function (header) {
return callback(header, this);
}, this);
},
/**
* Adds a header to the PropertyList of headers.
*
* @param {Header| {key: String, value: String}} header Can be a {Header} object, or a raw header object.
*/
addHeader: function (header) {
this.headers.add(header);
},
/**
* Removes a header from the request.
*
* @param {String|Header} toRemove A header object to remove, or a string containing the header key.
* @param {Object} options -
* @param {Boolean} options.ignoreCase If set to true, ignores case while removing the header.
*/
removeHeader: function (toRemove, options) {
toRemove = _.isString(toRemove) ? toRemove : toRemove.key;
options = options || {};
if (!toRemove) { // Nothing to remove :(
return;
}
options.ignoreCase && (toRemove = toRemove.toLowerCase());
this.headers.remove(function (header) {
var key = options.ignoreCase ? header.key.toLowerCase() : header.key;
return key === toRemove;
});
},
/**
* Updates or inserts the given header.
*
* @param {Object} header -
*/
upsertHeader: function (header) {
if (!(header && header.key)) { return; } // if no valid header is provided, do nothing
var existing = this.headers.find({ key: header.key });
if (!existing) {
return this.headers.add(header);
}
existing.value = header.value;
},
/**
* Add query parameters to the request.
*
* @todo: Rename this?
* @param {Array<QueryParam>|String} params -
*/
addQueryParams: function (params) {
this.url.addQueryParams(params);
},
/**
* Removes parameters passed in params.
*
* @param {String|Array} params -
*/
removeQueryParams: function (params) {
this.url.removeQueryParams(params);
},
/**
* Get the request size by computing the headers and body or using the
* actual content length header once the request is sent.
*
* @returns {Object}
*/
size: function () {
var contentLength = this.headers.get(CONTENT_LENGTH),
requestTarget = this.url.getPathWithQuery(),
bodyString,
sizeInfo = {
body: 0,
header: 0,
total: 0,
source: SIZE_SOURCE.computed
};
// if 'Content-Length' header is present, we take body as declared by
// the client(postman-request or user-defined). else we need to compute the same.
if (contentLength && util.isNumeric(contentLength)) {
sizeInfo.body = parseInt(contentLength, 10);
sizeInfo.source = SIZE_SOURCE.contentLength;
}
// otherwise, if body is defined, we calculate the length of the body
else if (this.body) {
// @note body.toString() returns E for formdata or file mode
bodyString = this.body.toString();
sizeInfo.body = supportsBuffer ? Buffer.byteLength(bodyString) :
/* istanbul ignore next */
bodyString.length;
}
// https://tools.ietf.org/html/rfc7230#section-3
// HTTP-message = start-line (request-line / status-line)
// *( header-field CRLF )
// CRLF
// [ message-body ]
// request-line = method SP request-target SP HTTP-version CRLF
sizeInfo.header = (this.method + SP + requestTarget + SP + HTTP_X_X + CRLF + CRLF).length +
this.headers.contentSize();
// compute the approximate total body size by adding size of header and body
sizeInfo.total = (sizeInfo.body || 0) + (sizeInfo.header);
return sizeInfo;
},
/**
* Converts the Request to a plain JavaScript object, which is also how the request is
* represented in a collection file.
*
* @returns {{url: (*|String), method: *, header: (undefined|*), body: *, auth: *, certificate: *}}
*/
toJSON: function () {
var obj = PropertyBase.toJSON(this);
// remove header array if blank
if (_.isArray(obj.header) && !obj.header.length) {
delete obj.header;
}
return obj;
},
/**
* Creates a clone of this request
*
* @returns {Request}
*/
clone: function () {
return new Request(this.toJSON());
}
});
_.assign(Request, /** @lends Request */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Request',
/**
* Check whether an object is an instance of {@link ItemGroup}.
*
* @param {*} obj -
* @returns {Boolean}
*/
isRequest: function (obj) {
return Boolean(obj) && ((obj instanceof Request) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Request._postman_propertyName));
}
});
module.exports = {
Request
};

View File

@@ -0,0 +1,613 @@
var util = require('../util'),
_ = util.lodash,
httpReasons = require('http-reasons'),
LJSON = require('liquid-json'),
Property = require('./property').Property,
PropertyBase = require('./property-base').PropertyBase,
Request = require('./request').Request,
CookieList = require('./cookie-list').CookieList,
HeaderList = require('./header-list').HeaderList,
contentInfo = require('../content-info').contentInfo,
/**
* @private
* @const
* @type {string}
*/
E = '',
/**
* @private
* @const
* @type {String}
*/
HEADER = 'header',
/**
* @private
* @const
* @type {String}
*/
BODY = 'body',
/**
* @private
* @const
* @type {String}
*/
GZIP = 'gzip',
/**
* @private
* @const
* @type {String}
*/
CONTENT_ENCODING = 'Content-Encoding',
/**
* @private
* @const
* @type {String}
*/
CONTENT_LENGTH = 'Content-Length',
/**
* @private
* @const
* @type {string}
*/
BASE64 = 'base64',
/**
* @private
* @const
* @type {string}
*/
STREAM_TYPE_BUFFER = 'Buffer',
/**
* @private
* @const
* @type {string}
*/
STREAM_TYPE_BASE64 = 'Base64',
/**
* @private
* @const
* @type {string}
*/
FUNCTION = 'function',
/**
* @private
* @const
* @type {string}
*/
STRING = 'string',
/**
* @private
* @const
* @type {String}
*/
HTTP_X_X = 'HTTP/X.X',
/**
* @private
* @const
* @type {String}
*/
SP = ' ',
/**
* @private
* @const
* @type {String}
*/
CRLF = '\r\n',
/**
* @private
* @const
* @type {RegExp}
*/
REGEX_JSONP_LEFT = /^[^{(].*\(/,
/**
* @private
* @const
* @type {RegExp}
*/
REGEX_JSONP_RIGHT = /\)[^}].*$|\)$/,
/**
* Remove JSON padded string to pure JSON
*
* @private
* @param {String} str -
* @returns {String}
*/
stripJSONP = function (str) {
return str.replace(REGEX_JSONP_LEFT, E).replace(REGEX_JSONP_RIGHT, E);
},
/**
* @private
* @type {Boolean}
*/
supportsBuffer = (typeof Buffer !== undefined) && _.isFunction(Buffer.byteLength),
/**
* Normalizes an input Buffer, Buffer.toJSON() or base64 string into a Buffer or ArrayBuffer.
*
* @private
* @param {Buffer|Object} stream - An instance of Buffer, Buffer.toJSON(), or Base64 string
* @returns {Buffer|ArrayBuffer|undefined}
*/
normalizeStream = function (stream) {
if (!stream) { return; }
// create buffer from buffer's JSON representation
if (stream.type === STREAM_TYPE_BUFFER && _.isArray(stream.data)) {
// @todo Add tests for Browser environments, where ArrayBuffer is returned instead of Buffer
return typeof Buffer === FUNCTION ? Buffer.from(stream.data) : new Uint8Array(stream.data).buffer;
}
// create buffer from base64 string
if (stream.type === STREAM_TYPE_BASE64 && typeof stream.data === STRING) {
return Buffer.from(stream.data, BASE64);
}
// probably it's already of type buffer
return stream;
},
Response; // constructor
/**
* @typedef Response.definition
* @property {Number} code - define the response code
* @property {String=} [reason] - optionally, if the response has a non-standard response code reason, provide it here
* @property {Array<Header.definition>} [header]
* @property {Array<Cookie.definition>} [cookie]
* @property {String} [body]
* @property {Buffer|ArrayBuffer} [stream]
* @property {Number} responseTime
*
* @todo pluralise `header`, `cookie`
*/
_.inherit((
/**
* Response holds data related to the request body. By default, it provides a nice wrapper for url-encoded,
* form-data, and raw types of request bodies.
*
* @constructor
* @extends {Property}
*
* @param {Response.definition} options -
*/
Response = function PostmanResponse (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Response.super_.apply(this, arguments);
this.update(options || {});
}), Property);
_.assign(Response.prototype, /** @lends Response.prototype */ {
update (options) {
// options.stream accepts Buffer, Buffer.toJSON() or base64 string
// @todo this temporarily doubles the memory footprint (options.stream + generated buffer).
var stream = normalizeStream(options.stream);
_.mergeDefined((this._details = _.clone(httpReasons.lookup(options.code))), {
name: _.choose(options.reason, options.status),
code: options.code,
standardName: this._details.name
});
_.mergeDefined(this, /** @lends Response.prototype */ {
/**
* @type {Request}
*/
originalRequest: options.originalRequest ? new Request(options.originalRequest) : undefined,
/**
* @type {String}
*/
status: this._details.name,
/**
* @type {Number}
*/
code: options.code,
/**
* @type {HeaderList}
*/
headers: new HeaderList(this, options.header),
/**
* @type {String}
*/
body: options.body,
/**
* @private
*
* @type {Buffer|UInt8Array}
*/
stream: (options.body && _.isObject(options.body)) ? options.body : stream,
/**
* @type {CookieList}
*/
cookies: new CookieList(this, options.cookie),
/**
* Time taken for the request to complete.
*
* @type {Number}
*/
responseTime: options.responseTime,
/**
* @private
* @type {Number}
*/
responseSize: stream && stream.byteLength
});
}
});
_.assign(Response.prototype, /** @lends Response.prototype */ {
/**
* Defines that this property requires an ID field
*
* @private
* @readOnly
*/
_postman_propertyRequiresId: true,
/**
* Convert this response into a JSON serializable object. The _details meta property is omitted.
*
* @returns {Object}
*
* @todo consider switching to a different response buffer (stream) representation as Buffer.toJSON
* appears to cause multiple performance issues.
*/
toJSON: function () {
// @todo benchmark PropertyBase.toJSON, response Buffer.toJSON or _.cloneElement might
// be the bottleneck.
var response = PropertyBase.toJSON(this);
response._details && (delete response._details);
return response;
},
/**
* Get the http response reason phrase based on the current response code.
*
* @returns {String|undefined}
*/
reason: function () {
return this.status || httpReasons.lookup(this.code).name;
},
/**
* Creates a JSON representation of the current response details, and returns it.
*
* @returns {Object} A set of response details, including the custom server reason.
* @private
*/
details: function () {
if (!this._details || this._details.code !== this.code) {
this._details = _.clone(httpReasons.lookup(this.code));
this._details.code = this.code;
this._details.standardName = this._details.name;
}
return _.clone(this._details);
},
/**
* Get the response body as a string/text.
*
* @returns {String|undefined}
*/
text: function () {
return (this.stream ? util.bufferOrArrayBufferToString(this.stream, this.contentInfo().charset) : this.body);
},
/**
* Get the response body as a JavaScript object. Note that it throws an error if the response is not a valid JSON
*
* @param {Function=} [reviver] -
* @param {Boolean} [strict=false] Specify whether JSON parsing will be strict. This will fail on comments and BOM
* @example
* // assuming that the response is stored in a collection instance `myCollection`
* var response = myCollection.items.one('some request').responses.idx(0),
* jsonBody;
* try {
* jsonBody = response.json();
* }
* catch (e) {
* console.log("There was an error parsing JSON ", e);
* }
* // log the root-level keys in the response JSON.
* console.log('All keys in json response: ' + Object.keys(json));
*
* @returns {Object}
*/
json: function (reviver, strict) {
return LJSON.parse(this.text(), reviver, strict);
},
/**
* Get the JSON from response body that reuturns JSONP response.
*
* @param {Function=} [reviver] -
* @param {Boolean} [strict=false] Specify whether JSON parsing will be strict. This will fail on comments and BOM
*
* @throws {JSONError} when response body is empty
*/
jsonp: function (reviver, strict) {
return LJSON.parse(stripJSONP(this.text() || /* istanbul ignore next */ E), reviver, strict);
},
/**
* Extracts mime type, format, charset, extension and filename of the response content
* A fallback of default filename is given, if filename is not present in content-disposition header
*
* @returns {Response.ResponseContentInfo} - contentInfo for the response
*/
contentInfo: function () {
return contentInfo(this);
},
/**
* @private
* @deprecated discontinued in v4.0
*/
mime: function () {
throw new Error('`Response#mime` has been discontinued, use `Response#contentInfo` instead.');
},
/**
* Converts the response to a dataURI that can be used for storage or serialisation. The data URI is formed using
* the following syntax `data:<content-type>;baseg4, <base64-encoded-body>`.
*
* @returns {String}
* @todo write unit tests
*/
dataURI: function () {
const { contentType } = this.contentInfo();
// if there is no mime detected, there is no accurate way to render this thing
/* istanbul ignore if */
if (!contentType) {
return E;
}
// we create the body string first from stream and then fallback to body
return `data:${contentType};base64, ` + ((!_.isNil(this.stream) &&
util.bufferOrArrayBufferToBase64(this.stream)) || (!_.isNil(this.body) && util.btoa(this.body)) || E);
},
/**
* Get the response size by computing the same from content length header or using the actual response body.
*
* @returns {Number}
* @todo write unit tests
*/
size: function () {
var sizeInfo = {
body: 0,
header: 0,
total: 0
},
contentEncoding = this.headers.get(CONTENT_ENCODING),
contentLength = this.headers.get(CONTENT_LENGTH),
isCompressed = false,
byteLength;
// if server sent encoded data, we should first try deriving length from headers
if (_.isString(contentEncoding)) {
// desensitise case of content encoding
contentEncoding = contentEncoding.toLowerCase();
// eslint-disable-next-line lodash/prefer-includes
isCompressed = (contentEncoding.indexOf('gzip') > -1) || (contentEncoding.indexOf('deflate') > -1);
}
// if 'Content-Length' header is present and encoding is of type gzip/deflate, we take body as declared by
// server. else we need to compute the same.
if (contentLength && isCompressed && util.isNumeric(contentLength)) {
sizeInfo.body = _.parseInt(contentLength, 10);
}
// if there is a stream defined which looks like buffer, use it's data and move on
else if (this.stream) {
byteLength = this.stream.byteLength;
sizeInfo.body = util.isNumeric(byteLength) ? byteLength :
/* istanbul ignore next */
0;
}
// otherwise, if body is defined, we try get the true length of the body
else if (!_.isNil(this.body)) {
sizeInfo.body = supportsBuffer ? Buffer.byteLength(this.body.toString()) :
/* istanbul ignore next */
this.body.toString().length;
}
// size of header is added
// https://tools.ietf.org/html/rfc7230#section-3
// HTTP-message = start-line (request-line / status-line)
// *( header-field CRLF )
// CRLF
// [ message-body ]
// status-line = HTTP-version SP status-code SP reason-phrase CRLF
sizeInfo.header = (HTTP_X_X + SP + this.code + SP + this.reason() + CRLF + CRLF).length +
this.headers.contentSize();
// compute the approximate total body size by adding size of header and body
sizeInfo.total = (sizeInfo.body || 0) + (sizeInfo.header);
return sizeInfo;
},
/**
* Returns the response encoding defined as header or detected from body.
*
* @private
* @returns {Object} - {format: string, source: string}
*/
encoding: function () {
var contentEncoding = this.headers.get(CONTENT_ENCODING),
body = this.stream || this.body,
source;
if (contentEncoding) {
source = HEADER;
}
// if the encoding is not found, we check
else if (body) { // @todo add detection for deflate
// eslint-disable-next-line lodash/prefer-matches
if (body[0] === 0x1F && body[1] === 0x8B && body[2] === 0x8) {
contentEncoding = GZIP;
}
if (contentEncoding) {
source = BODY;
}
}
return {
format: contentEncoding,
source: source
};
}
});
_.assign(Response, /** @lends Response */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Response',
/**
* Check whether an object is an instance of {@link ItemGroup}.
*
* @param {*} obj -
* @returns {Boolean}
*/
isResponse: function (obj) {
return Boolean(obj) && ((obj instanceof Response) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Response._postman_propertyName));
},
/**
* Converts the response object from the request module to the postman responseBody format
*
* @param {Object} response The response object, as received from the request module
* @param {Object} cookies -
* @returns {Object} The transformed responseBody
* @todo Add a key: `originalRequest` to the returned object as well, referring to response.request
*/
createFromNode: function (response, cookies) {
return new Response({
cookie: cookies,
body: response.body.toString(),
stream: response.body,
header: response.headers,
code: response.statusCode,
status: response.statusMessage,
responseTime: response.elapsedTime
});
},
/**
* @private
* @deprecated discontinued in v4.0
*/
mimeInfo: function () {
throw new Error('`Response.mimeInfo` has been discontinued, use `Response#contentInfo` instead.');
},
/**
* Returns the durations of each request phase in milliseconds
*
* @typedef Response.timings
* @property {Number} start - timestamp of the request sent from the client (in Unix Epoch milliseconds)
* @property {Object} offset - event timestamps in millisecond resolution relative to start
* @property {Number} offset.request - timestamp of the start of the request
* @property {Number} offset.socket - timestamp when the socket is assigned to the request
* @property {Number} offset.lookup - timestamp when the DNS has been resolved
* @property {Number} offset.connect - timestamp when the server acknowledges the TCP connection
* @property {Number} offset.secureConnect - timestamp when secure handshaking process is completed
* @property {Number} offset.response - timestamp when the first bytes are received from the server
* @property {Number} offset.end - timestamp when the last bytes of the response are received
* @property {Number} offset.done - timestamp when the response is received at the client
*
* @note If there were redirects, the properties reflect the timings
* of the final request in the redirect chain
*
* @param {Response.timings} timings -
* @returns {Object}
*
* @example Output
* Request.timingPhases(timings);
* {
* prepare: Number, // duration of request preparation
* wait: Number, // duration of socket initialization
* dns: Number, // duration of DNS lookup
* tcp: Number, // duration of TCP connection
* secureHandshake: Number, // duration of secure handshake
* firstByte: Number, // duration of HTTP server response
* download: Number, // duration of HTTP download
* process: Number, // duration of response processing
* total: Number // duration entire HTTP round-trip
* }
*
* @note if there were redirects, the properties reflect the timings of the
* final request in the redirect chain.
*/
timingPhases: function (timings) {
// bail out if timing information is not provided
if (!(timings && timings.offset)) {
return;
}
var phases,
offset = timings.offset;
// REFER: https://github.com/postmanlabs/postman-request/blob/v2.88.1-postman.5/request.js#L996
phases = {
prepare: offset.request,
wait: offset.socket - offset.request,
dns: offset.lookup - offset.socket,
tcp: offset.connect - offset.lookup,
firstByte: offset.response - offset.connect,
download: offset.end - offset.response,
process: offset.done - offset.end,
total: offset.done
};
if (offset.secureConnect) {
phases.secureHandshake = offset.secureConnect - offset.connect;
phases.firstByte = offset.response - offset.secureConnect;
}
return phases;
}
});
module.exports = {
Response
};

View File

@@ -0,0 +1,109 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
Url = require('./url').Url,
Script,
SCRIPT_NEWLINE_PATTERN = /\r?\n/g;
_.inherit((
/**
* Postman scripts that are executed upon events on a collection / request such as test and pre request.
*
* @constructor
* @extends {Property}
*
* @param {Object} options -
*/
Script = function PostmanScript (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Script.super_.apply(this, arguments);
options && this.update(options);
}), Property);
_.assign(Script.prototype, /** @lends Script.prototype */ {
/**
* Defines whether this property instances requires an id
*
* @private
* @readOnly
* @type {Boolean}
*/
_postman_propertyRequiresId: true,
/**
* Converts the script lines array to a single source string.
*
* @returns {String}
*/
toSource: function () {
return this.exec ? this.exec.join('\n') : undefined;
},
/**
* Updates the properties of a Script.
*
* @param {Object} [options] -
* @param {String} [options.type] Script type
* @param {String} [options.src] Script source url
* @param {String[]|String} [options.exec] Script to execute
*/
update: function (options) {
// no splitting is being done here, as string scripts are split right before assignment below anyway
(_.isString(options) || _.isArray(options)) && (options = { exec: options });
if (!options) { return; } // in case definition object is missing, there is no point moving forward
// create the request property
/**
* @augments {Script.prototype}
* @type {string}
*/
this.type = options.type || 'text/javascript';
_.has(options, 'src') && (
/**
* @augments {Script.prototype}
* @type {Url}
*/
this.src = new Url(options.src)
);
if (!this.src && _.has(options, 'exec')) {
/**
* @augments {Script.prototype}
* @type {Array<string>}
*/
this.exec = _.isString(options.exec) ? options.exec.split(SCRIPT_NEWLINE_PATTERN) :
_.isArray(options.exec) ? options.exec : undefined;
}
}
});
_.assign(Script, /** @lends Script */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Script',
/**
* Check whether an object is an instance of {@link ItemGroup}.
*
* @param {*} obj -
* @returns {Boolean}
*/
isScript: function (obj) {
return Boolean(obj) && ((obj instanceof Script) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Script._postman_propertyName));
}
});
module.exports = {
Script
};

443
node_modules/postman-collection/lib/collection/url.js generated vendored Normal file
View File

@@ -0,0 +1,443 @@
var _ = require('../util').lodash,
url_parse = require('postman-url-encoder/parser').parse,
PropertyBase = require('./property-base').PropertyBase,
QueryParam = require('./query-param').QueryParam,
PropertyList = require('./property-list').PropertyList,
VariableList = require('./variable-list').VariableList,
E = '',
STRING = 'string',
FUNCTION = 'function',
PROTOCOL_HTTPS = 'https',
PROTOCOL_HTTP = 'http',
HTTPS_PORT = '443',
HTTP_PORT = '80',
PATH_SEPARATOR = '/',
PATH_VARIABLE_IDENTIFIER = ':',
PORT_SEPARATOR = ':',
DOMAIN_SEPARATOR = '.',
PROTOCOL_SEPARATOR = '://',
AUTH_SEPARATOR = ':',
AUTH_CREDENTIALS_SEPARATOR = '@',
QUERY_SEPARATOR = '?',
SEARCH_SEPARATOR = '#',
DEFAULT_PROTOCOL = PROTOCOL_HTTP + PROTOCOL_SEPARATOR,
MATCH_1 = '$1',
regexes = {
trimPath: /^\/((.+))$/,
splitDomain: /\.(?![^{]*\}{2})/g
},
Url;
_.inherit((
/**
* Defines a URL.
*
* @constructor
* @extends {PropertyBase}
* @param {Object|String} options -
*/
Url = function PostmanUrl (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Url.super_.apply(this, arguments);
// create the url properties
this.update(options);
}), PropertyBase);
_.assign(Url.prototype, /** @lends Url.prototype */ {
/**
* Set a URL.
*
* @draft
* @param {String|Object} url -
*/
update (url) {
!url && (url = E);
var parsedUrl = _.isString(url) ? Url.parse(url) : url,
auth = parsedUrl.auth,
protocol = parsedUrl.protocol,
port = parsedUrl.port,
path = parsedUrl.path,
hash = parsedUrl.hash,
host = parsedUrl.host,
query = parsedUrl.query,
variable = parsedUrl.variable;
// convert object based query string to array
// @todo: create a key value parser
if (query) {
if (_.isString(query)) {
query = QueryParam.parse(query);
}
if (!_.isArray(query) && _.keys(query).length) {
query = _.map(_.keys(query), function (key) {
return {
key: key,
value: query[key]
};
});
}
}
// backward compatibility with path variables being storing thins with `id`
if (_.isArray(variable)) {
variable = _.map(variable, function (v) {
_.isObject(v) && (v.key = v.key || v.id); // @todo Remove once path variables are deprecated
return v;
});
}
// expand string path name
if (_.isString(path)) {
path && (path = path.replace(regexes.trimPath, MATCH_1)); // remove leading slash for valid path
// if path is blank string, we set it to undefined, if '/' then single blank string array
path = path ? (path === PATH_SEPARATOR ? [E] : path.split(PATH_SEPARATOR)) : undefined;
}
// expand host string
_.isString(host) && (host = host.split(regexes.splitDomain));
_.assign(this, /** @lends Url.prototype */ {
/**
* @type {{ user: String, password: String }}
*/
auth: auth,
/**
* @type {String}
*/
protocol: protocol,
/**
* @type {String}
*/
port: port,
/**
* @type {Array<String>}
*/
path: path,
/**
* @type {String}
*/
hash: hash,
/**
* @type {Array<String>}
*/
host: host,
/**
* @type {PropertyList<QueryParam>}
*
* @todo consider setting this as undefined in v4 otherwise it's
* difficult to detect URL like `localhost/?`.
* currently it's replying upon a single member with empty key.
*/
query: new PropertyList(QueryParam, this, query || []),
/**
* @type {VariableList}
*/
variables: new VariableList(this, variable || [])
});
},
/**
* Add query parameters to the URL.
*
* @param {Object|String} params Key value pairs to add to the URL.
*/
addQueryParams (params) {
params = _.isString(params) ? QueryParam.parse(params) : params;
this.query.populate(params);
},
/**
* Removes query parameters from the URL.
*
* @param {Array<QueryParam>|Array<String>|String} params Params should be an array of strings, or an array of
* actual query parameters, or a string containing the parameter key.
* @note Input should *not* be a query string.
*/
removeQueryParams (params) {
params = _.isArray(params) ? _.map(params, function (param) {
return param.key ? param.key : param;
}) : [params];
this.query.remove(function (param) {
return _.includes(params, param.key);
});
},
/**
* @private
* @deprecated discontinued in v4.0
*/
getRaw () {
throw new Error('`Url#getRaw` has been discontinued, use `Url#toString` instead.');
},
/**
* Unparses a {PostmanUrl} into a string.
*
* @param {Boolean=} forceProtocol - Forces the URL to have a protocol
* @returns {String}
*/
toString (forceProtocol) {
var rawUrl = E,
protocol = this.protocol,
queryString,
authString;
forceProtocol && !protocol && (protocol = DEFAULT_PROTOCOL);
if (protocol) {
rawUrl += (_.endsWith(protocol, PROTOCOL_SEPARATOR) ? protocol : protocol + PROTOCOL_SEPARATOR);
}
if (this.auth) {
if (typeof this.auth.user === STRING) {
authString = this.auth.user;
}
if (typeof this.auth.password === STRING) {
!authString && (authString = E);
authString += AUTH_SEPARATOR + this.auth.password;
}
if (typeof authString === STRING) {
rawUrl += authString + AUTH_CREDENTIALS_SEPARATOR;
}
}
if (this.host) {
rawUrl += this.getHost();
}
if (typeof _.get(this.port, 'toString') === FUNCTION) {
rawUrl += PORT_SEPARATOR + this.port.toString();
}
if (this.path) {
rawUrl += this.getPath();
}
if (this.query && this.query.count()) {
queryString = this.getQueryString();
// either all the params are disabled or a single param is like { key: '' } (http://localhost?)
// in that case, query separator ? must be included in the raw URL.
// @todo return undefined or string from getQueryString method to distinguish
// no params vs empty param.
if (queryString === E) {
// check if there's any enabled param, if so, set queryString to empty string
// otherwise (all disabled), it will be set as undefined
queryString = this.query.find(function (param) { return !(param && param.disabled); }) && E;
}
if (typeof queryString === STRING) {
rawUrl += QUERY_SEPARATOR + queryString;
}
}
if (typeof this.hash === STRING) {
rawUrl += SEARCH_SEPARATOR + this.hash;
}
return rawUrl;
},
/**
* Returns the request path, with a leading '/'.
*
* @param {?Boolean=} [unresolved=false] -
* @returns {String}
*/
getPath (unresolved) {
// for unresolved case, this is super simple as that is how raw data is stored
if (unresolved) {
return PATH_SEPARATOR + this.path.join(PATH_SEPARATOR);
}
var self = this,
segments;
segments = _.transform(this.path, function (res, segment) {
var variable;
// check if the segment has path variable prefix followed by the variable name.
if (_.startsWith(segment, PATH_VARIABLE_IDENTIFIER) && segment !== PATH_VARIABLE_IDENTIFIER) {
variable = self.variables.one(segment.slice(1)); // remove path variable prefix.
}
variable = variable && variable.valueOf && variable.valueOf();
res.push(_.isString(variable) ? variable : segment);
}, []);
return PATH_SEPARATOR + segments.join(PATH_SEPARATOR); // add leading slash
},
/**
* Returns the stringified query string for this URL.
*
* @returns {String}
*/
getQueryString () {
if (!this.query.count()) {
return E;
}
return QueryParam.unparse(this.query.all());
},
/**
* Returns the complete path, including the query string.
*
* @returns {*|String}
* @example /something/postman?hi=notbye
*/
getPathWithQuery () {
var path = this.getPath();
// check count first so that, we can ensure that ba `?` is always appended, even if a blank query string exists
if (this.query.count()) {
path += (QUERY_SEPARATOR + this.getQueryString());
}
return path;
},
/**
* Returns the host part of the URL
*
* @returns {String}
*/
getHost () {
if (!this.host) {
return E;
}
return _.isArray(this.host) ? this.host.join(DOMAIN_SEPARATOR) : this.host.toString();
},
/**
* Returns the host *and* port (if any), separated by a ":"
*
* @param {?Boolean} [forcePort=false] - forces the port to be added even for the protocol default ones (89, 443)
* @returns {String}
*/
getRemote (forcePort) {
var host = this.getHost(),
port = this.port && this.port.toString();
if (forcePort && !port) { // this (!port) works since it assumes port as a string
port = this.protocol && (this.protocol === PROTOCOL_HTTPS) ? HTTPS_PORT : HTTP_PORT;
}
return port ? (host + PORT_SEPARATOR + port) : host;
},
/**
* Returns a OAuth1.0-a compatible representation of the request URL, also called "Base URL".
* For details, http://oauth.net/core/1.0a/#anchor13
*
* todo: should we ignore the auth parameters of the URL or not? (the standard does not mention them)
* we currently are.
*
* @private
* @returns {String}
*
* @deprecated since v3.5 in favour of getBaseUrl
* @note not discontinue yet because it's used in Twitter APIs public collections
*/
getOAuth1BaseUrl () {
var protocol = this.protocol || PROTOCOL_HTTP,
port = this.port ? this.port.toString() : undefined,
host = ((port === HTTP_PORT ||
port === HTTPS_PORT ||
port === undefined) && this.host.join(DOMAIN_SEPARATOR)) || (this.host.join(DOMAIN_SEPARATOR) +
PORT_SEPARATOR + port),
path = this.getPath();
protocol = (_.endsWith(protocol, PROTOCOL_SEPARATOR) ? protocol : protocol + PROTOCOL_SEPARATOR);
return protocol.toLowerCase() + host.toLowerCase() + path;
}
});
_.assign(Url, /** @lends Url */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Url',
/**
* Parses a string to a PostmanUrl, decomposing the URL into it's constituent parts,
* such as path, host, port, etc.
*
* @param {String} url -
* @returns {Object}
*/
parse: function (url) {
url = url_parse(url);
var pathVariables,
pathVariableKeys = {};
if (url.auth) {
url.auth = {
user: url.auth[0],
password: url.auth[1]
};
}
if (url.query) {
url.query = url.query.map(QueryParam.parseSingle);
}
// extract path variables
pathVariables = _.transform(url.path, function (res, segment) {
// check if the segment has path variable prefix followed by the variable name and
// the variable is not already added in the list.
if (_.startsWith(segment, PATH_VARIABLE_IDENTIFIER) &&
segment !== PATH_VARIABLE_IDENTIFIER &&
!pathVariableKeys[segment]) {
pathVariableKeys[segment] = true;
res.push({ key: segment.slice(1) }); // remove path variable prefix.
}
}, []);
url.variable = pathVariables.length ? pathVariables : undefined;
return url;
},
/**
* Checks whether an object is a Url
*
* @param {*} obj -
* @returns {Boolean}
*/
isUrl: function (obj) {
return Boolean(obj) && ((obj instanceof Url) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Url._postman_propertyName));
}
});
module.exports = {
Url
};

View File

@@ -0,0 +1,196 @@
var _ = require('../util').lodash,
PropertyList = require('./property-list').PropertyList,
Property = require('./property').Property,
Variable = require('./variable').Variable,
VariableList;
_.inherit((
/**
* @constructor
* @extends {PropertyList}
*
* @param {Property} parent -
* @param {Object|Array} populate -
*/
VariableList = function PostmanVariableList (parent, populate) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
VariableList.super_.call(this, Variable, parent, populate);
}), PropertyList);
_.assign(VariableList.prototype, /** @lends VariableList.prototype */ {
/**
* Replaces the variable tokens inside a string with its actual values.
*
* @param {String} str -
* @param {Object} [overrides] - additional objects to lookup for variable values
* @returns {String}
*/
replace (str, overrides) {
return Property.replaceSubstitutions(str, this, overrides);
},
/**
* Recursively replace strings in an object with instances of variables. Note that it clones the original object. If
* the `mutate` param is set to true, then it replaces the same object instead of creating a new one.
*
* @param {Array|Object} obj -
* @param {?Array<Object>=} [overrides] - additional objects to lookup for variable values
* @param {Boolean=} [mutate=false] -
* @returns {Array|Object}
*/
substitute (obj, overrides, mutate) {
var resolutionQueue = [], // we use this to store the queue of variable hierarchy
// this is an intermediate object to stimulate a property (makes the do-while loop easier)
variableSource = {
variables: this,
__parent: this.__parent
};
do { // iterate and accumulate as long as you find `.variables` in parent tree
variableSource.variables && resolutionQueue.push(variableSource.variables);
variableSource = variableSource.__parent;
} while (variableSource);
variableSource = null; // cautious cleanup
return Property.replaceSubstitutionsIn(obj, _.union(resolutionQueue, overrides), mutate);
},
/**
* Using this function, one can sync the values of this variable list from a reference object.
*
* @param {Object} obj -
* @param {Boolean=} track -
* @param {Boolean} [prune=true] -
*
* @returns {Object}
*/
syncFromObject (obj, track, prune) {
var list = this,
ops = track && {
created: [],
updated: [],
deleted: []
},
indexer = list._postman_listIndexKey,
tmp;
if (!_.isObject(obj)) { return ops; }
// ensure that all properties in the object is updated in this list
_.forOwn(obj, function (value, key) {
// we need to create new variable if exists or update existing
if (list.has(key)) {
list.one(key).set(value);
ops && ops.updated.push(key);
}
else {
tmp = { value };
tmp[indexer] = key;
list.add(tmp);
tmp = null;
ops && ops.created.push(key);
}
});
// now remove any variable that is not in source object
// @note - using direct `this.reference` list of keys here so that we can mutate the list while iterating
// on it
if (prune !== false) {
_.forEach(list.reference, function (value, key) {
if (_.has(obj, key)) { return; } // de not delete if source obj has this variable
list.remove(key); // use PropertyList functions to remove so that the .members array is cleared too
ops && ops.deleted.push(key);
});
}
return ops;
},
/**
* Transfer all variables from this list to an object
*
* @param {Object=} [obj] -
* @returns {Object}
*/
syncToObject (obj) {
var list = this;
// in case user did not provide an object to mutate, create a new one
!_.isObject(obj) && (obj = {});
// delete extra variables from object that are not present in list
_.forEach(obj, function (value, key) {
!_.has(list.reference, key) && (delete obj[key]);
});
// we first sync all variables in this list to the object
list.each(function (variable) {
obj[variable.key] = variable.valueOf();
});
return obj;
},
/**
* Fetches a variable and normalize its reference if disabled.
* This updates the disabled variable `reference` in VariableList with its
* last enabled duplicate(if found) in the `members` list.
*
* @private
* @param {String} variableName - The name of the variable to get
* @returns {Variable} - In case of duplicates, returns last enabled
*/
oneNormalizedVariable (variableName) {
var indexKey = this._postman_listIndexKey, // `key` for Variable
variable = this.reference[variableName],
i;
if (variable && !variable.disabled) {
return variable;
}
// traverse the members list in reverse direction in order to find the last enabled
for (i = this.members.length - 1; i >= 0; i--) {
variable = this.members[i];
if (variable[indexKey] === variableName && !variable.disabled) {
// update the input variable reference if comparison is not disabled
this.reference[variableName] = variable;
break; // return updated reference variable
}
}
return this.reference[variableName];
}
});
_.assign(VariableList, /** @lends VariableList */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*
* @note that this is directly accessed only in case of VariableList from _.findValue lodash util mixin
*/
_postman_propertyName: 'VariableList',
/**
* Checks whether an object is a VariableList
*
* @param {*} obj -
* @returns {Boolean}
*/
isVariableList: function (obj) {
return Boolean(obj) && ((obj instanceof VariableList) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', VariableList._postman_propertyName));
}
});
module.exports = {
VariableList
};

View File

@@ -0,0 +1,468 @@
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
};

View File

@@ -0,0 +1,379 @@
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
};

View File

@@ -0,0 +1,99 @@
var _ = require('../util').lodash,
semver = require('semver'),
PropertyBase = require('./property-base').PropertyBase,
Version;
/**
* @typedef {Object|String} Version.definition
*/
_.inherit((
/**
* Defines a Version.
*
* @constructor
* @extends {PropertyBase}
* @param {Version.definition} definition -
*/
Version = function PostmanPropertyVersion (definition) {
// in case definition object is missing, there is no point moving forward
if (!definition) { return; }
// call the setter to process the version string and assign it to this object
this.set(definition);
}), PropertyBase);
_.assign(Version.prototype, /** @lends Version.prototype */ {
/**
* Set the version value as string or object with separate components of version
*
* @draft
* @param {object|string} value -
*/
set (value) {
// extract the version logic and in case it failes and value passed is an object, we use that assuming parsed
// value has been sent.
var ver = semver.parse(value) || value || {};
_.assign(this, /** @lends Version.prototype */ {
/**
* The raw URL string. If {@link Version#set} is called with a string parameter, the string is saved here
* before parsing various Version components.
*
* @type {String}
*/
raw: ver.raw,
/**
* @type {String}
*/
major: ver.major,
/**
* @type {String}
*/
minor: ver.minor,
/**
* @type {String}
*/
patch: ver.patch,
/**
* @type {String}
*/
prerelease: ver.prerelease && ver.prerelease.join && ver.prerelease.join() || ver.prerelease,
/**
* @type {String}
*/
build: ver.build && ver.build.join && ver.build.join() || ver.build,
/**
* @type {String}
*/
string: ver.version
});
},
toString () {
// TODO: is this enough? should we build the semver back up?
return this.string || this.raw;
}
});
_.assign(Version, /** @lends Version */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Version'
});
module.exports = {
Version
};