304 lines
9.7 KiB
JavaScript
304 lines
9.7 KiB
JavaScript
/**
|
|
* @fileOverview
|
|
*
|
|
* This module consists all request body transformer functions based on the request body modes supported
|
|
* Ideally, this should one day move to a function in SDK something like request.getNodeRequestOptions()
|
|
*
|
|
*
|
|
* _
|
|
* ( ) ,,,,,
|
|
* \\ . . ,
|
|
* \\ | - D ,
|
|
* (._) \__- | ,
|
|
* | |..
|
|
* \\|_ , ,---- _ |----.
|
|
* \__ ( ( / ) _
|
|
* | \/ \. ' _.| \ ( )
|
|
* | \ /( / /\_ \ //
|
|
* \ / ( / / ) //
|
|
* ( , / / , (_.)
|
|
* |......\ | \,
|
|
* / / ) \---
|
|
* /___/___^//
|
|
*/
|
|
var _ = require('lodash'),
|
|
|
|
CONTENT_TYPE_HEADER_KEY = 'Content-Type',
|
|
|
|
/**
|
|
* Map content-type to respective body language.
|
|
*
|
|
* @private
|
|
* @type {Object}
|
|
*/
|
|
CONTENT_TYPE_LANGUAGE = {
|
|
'html': 'text/html',
|
|
'text': 'text/plain',
|
|
'json': 'application/json',
|
|
'javascript': 'application/javascript',
|
|
'xml': 'application/xml'
|
|
},
|
|
|
|
STRING = 'string',
|
|
E = '',
|
|
|
|
oneNormalizedHeader,
|
|
|
|
// the following two are reducer functions. we keep it defined here to avoid redefinition upon each parse
|
|
urlEncodedBodyReducer,
|
|
formDataBodyReducer;
|
|
|
|
/**
|
|
* Find the enabled header with the given name.
|
|
*
|
|
* @todo Add this helper in Collection SDK.
|
|
*
|
|
* @private
|
|
* @param {HeaderList} headers
|
|
* @param {String} name
|
|
* @returns {Header|undefined}
|
|
*/
|
|
oneNormalizedHeader = function oneNormalizedHeader (headers, name) {
|
|
var i,
|
|
header;
|
|
|
|
// get all headers with `name`
|
|
headers = headers.reference[name.toLowerCase()];
|
|
|
|
if (Array.isArray(headers)) {
|
|
// traverse the headers list in reverse direction in order to find the last enabled
|
|
for (i = headers.length - 1; i >= 0; i--) {
|
|
header = headers[i];
|
|
|
|
if (header && !header.disabled) {
|
|
return header;
|
|
}
|
|
}
|
|
|
|
// bail out if no enabled header was found
|
|
return;
|
|
}
|
|
|
|
// return the single enabled header
|
|
if (headers && !headers.disabled) {
|
|
return headers;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Reduces postman SDK url encoded form definition (flattened to array) into Node compatible body options
|
|
*
|
|
* @param {Object} form - url encoded form params accumulator
|
|
* @param {Object} param - url encoded form param
|
|
*
|
|
* @returns {Object}
|
|
*/
|
|
urlEncodedBodyReducer = function (form, param) {
|
|
if (!param || param.disabled) {
|
|
return form;
|
|
}
|
|
|
|
var key = param.key,
|
|
value = param.value;
|
|
|
|
// add the parameter to the form while accounting for duplicate values
|
|
if (!form.hasOwnProperty(key)) {
|
|
form[key] = value;
|
|
|
|
return form;
|
|
}
|
|
|
|
// at this point, we know that form has duplicate, so we need to accumulate it in an array
|
|
if (!Array.isArray(form[key])) {
|
|
form[key] = [form[key]];
|
|
}
|
|
|
|
form[key].push(value); // finally push the duplicate and return
|
|
|
|
return form;
|
|
};
|
|
|
|
/**
|
|
* Reduces postman SDK multi-part form definition (flattened to array) into Node compatible body options
|
|
*
|
|
* @param {Array} data - multi-part form params accumulator
|
|
* @param {Object} param - multi-part form param
|
|
*
|
|
* @returns {Array}
|
|
*/
|
|
formDataBodyReducer = function (data, param) {
|
|
if (!param || param.disabled) {
|
|
return data;
|
|
}
|
|
|
|
var formParam = {
|
|
key: param.key,
|
|
value: param.value
|
|
},
|
|
options; // we keep the default blank and then set to object wherever needed. saves doing object keyLength
|
|
|
|
// make sure that value is either string or read stream otherwise it'll cause error in postman-request
|
|
if (param.type !== 'file' && typeof formParam.value !== STRING) {
|
|
try {
|
|
formParam.value = JSON.stringify(formParam.value);
|
|
}
|
|
catch (err) {
|
|
formParam.value = E;
|
|
}
|
|
}
|
|
|
|
// make sure `filename` param is sent for every file without `value`
|
|
// so that `filename=""` is added to content-disposition header in form data
|
|
if (param.type === 'file' && !formParam.value && typeof param.fileName !== 'string') {
|
|
param.fileName = E;
|
|
formParam.value = E; // make sure value is not null/undefined ever
|
|
}
|
|
|
|
// if data has a truthy content type, we mutate the value to take the options. we are assuming that
|
|
// blank string will not be considered as an accepted content type.
|
|
if (param.contentType && typeof param.contentType === STRING) {
|
|
(options || (options = {})).contentType = param.contentType;
|
|
}
|
|
|
|
// additionally parse the file name and length if sent
|
|
// @note: Add support for fileName & fileLength option in Schema & SDK.
|
|
// The filepath property overrides filename and may contain a relative path.
|
|
if (typeof param.fileName === STRING) { (options || (options = {})).filename = param.fileName; }
|
|
if (typeof param.fileLength === 'number') { (options || (options = {})).knownLength = param.fileLength; }
|
|
|
|
|
|
// if options were set, add them to formParam
|
|
options && (formParam.options = options);
|
|
|
|
data.push(formParam);
|
|
|
|
return data;
|
|
};
|
|
|
|
/**
|
|
* This module exposes functions that are named corresponding to Postman collection body modes. It accepts the body
|
|
* definition, usually like `request.body.raw` where mode is `raw` and returns its equivalent structure that needs to be
|
|
* sent to node request module
|
|
*/
|
|
module.exports = {
|
|
/**
|
|
* @param {Object} content - request body content
|
|
* @param {Request} [request] - request object
|
|
* @returns {Object}
|
|
*/
|
|
raw: function (content, request) {
|
|
var contentLanguage = _.get(request, 'body.options.raw.language', 'text');
|
|
|
|
// Add `Content-Type` header from body options if not set already
|
|
if (request && !oneNormalizedHeader(request.headers, CONTENT_TYPE_HEADER_KEY)) {
|
|
request.headers.add({
|
|
key: CONTENT_TYPE_HEADER_KEY,
|
|
value: CONTENT_TYPE_LANGUAGE[contentLanguage] || CONTENT_TYPE_LANGUAGE.text,
|
|
system: true
|
|
});
|
|
}
|
|
|
|
if (typeof content !== STRING) {
|
|
content = JSON.stringify(content);
|
|
}
|
|
|
|
return {
|
|
body: content
|
|
};
|
|
},
|
|
|
|
/**
|
|
* @param {Object} content - request body content
|
|
* @returns {Object}
|
|
*/
|
|
urlencoded: function (content) {
|
|
if (content && _.isFunction(content.all)) { content = content.all(); } // flatten the body content
|
|
|
|
return {
|
|
form: _.reduce(content, urlEncodedBodyReducer, {})
|
|
};
|
|
},
|
|
|
|
/**
|
|
* @param {Object} content - request body content
|
|
* @returns {Object}
|
|
*/
|
|
formdata: function (content) {
|
|
if (content && _.isFunction(content.all)) { content = content.all(); } // flatten the body content
|
|
|
|
return {
|
|
formData: _.reduce(content, formDataBodyReducer, [])
|
|
};
|
|
},
|
|
|
|
/**
|
|
* @param {Object} content - request body content
|
|
* @returns {Object}
|
|
*/
|
|
file: function (content) {
|
|
return {
|
|
body: content && content.content
|
|
};
|
|
},
|
|
|
|
/**
|
|
* @param {Object} content - request body content
|
|
* @param {Request} [request] - Request object
|
|
* @returns {Object}
|
|
*/
|
|
graphql: function (content, request) {
|
|
var body;
|
|
|
|
// implicitly add `Content-Type` header if not set already
|
|
if (request && !oneNormalizedHeader(request.headers, CONTENT_TYPE_HEADER_KEY)) {
|
|
request.headers.add({
|
|
key: CONTENT_TYPE_HEADER_KEY,
|
|
value: CONTENT_TYPE_LANGUAGE.json,
|
|
system: true
|
|
});
|
|
}
|
|
|
|
// if `variables` is an object, just stringify the entire content
|
|
if (content && typeof content.variables !== STRING) {
|
|
// if any property of graphql is undefined, it will not get stringified
|
|
// as a result, if no content object's properties are present then the
|
|
// result will be a blank object being sent.
|
|
// note that this behavior has to be imitated later when we are
|
|
// receiving variables as string
|
|
return {
|
|
body: JSON.stringify({
|
|
query: content.query,
|
|
operationName: content.operationName,
|
|
variables: content.variables
|
|
})
|
|
};
|
|
}
|
|
|
|
// otherwise, traverse the graphql properties and generate the
|
|
// stringified content. This avoids parsing the variables.
|
|
body = [];
|
|
|
|
if (content.hasOwnProperty('query') && (typeof content.query === STRING)) {
|
|
body.push('"query":' + JSON.stringify(content.query));
|
|
}
|
|
|
|
if (content.hasOwnProperty('operationName') && (typeof content.operationName === STRING)) {
|
|
body.push('"operationName":' + JSON.stringify(content.operationName));
|
|
}
|
|
|
|
if (content.hasOwnProperty('variables') && (typeof content.variables === STRING) &&
|
|
// even though users are free to send even malformed json string, the case of empty string has to be
|
|
// specially disallowed since in most default cases if a text editor is used to accept this data, it will
|
|
// send a blank string for an empty text-editor state and that would be an error flow. That implies majority
|
|
// default use case will become error flow and handling for the same has to be also coded in every other
|
|
// place where runtime is used.
|
|
(content.variables !== E)) {
|
|
body.push('"variables":' + content.variables); // already a stringified JSON
|
|
}
|
|
|
|
return {
|
|
body: '{' + body.join(',') + '}' // note that [] body = {} ¯\_(ツ)_/¯
|
|
};
|
|
}
|
|
};
|