From 92c2430a7b0da845221fd760d563dfbb36cdfbf5 Mon Sep 17 00:00:00 2001 From: someshkoli <41543649+someshkoli@users.noreply.github.com> Date: Tue, 24 Mar 2020 03:24:05 +0530 Subject: [PATCH 1/2] Update README.md Example snippet had improper declaration of convert function and request instance. Fixed and tested --- codegens/python-requests/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codegens/python-requests/README.md b/codegens/python-requests/README.md index 45b26b449..aefb02217 100644 --- a/codegens/python-requests/README.md +++ b/codegens/python-requests/README.md @@ -30,9 +30,10 @@ It requires 3 mandatory parameters `request`, `callback` and `options` #### Example ```javascript -sdk = require('postman-collection'); +const sdk = require('postman-collection'); +const convert = require('.').convert; -var request = sdk.Request('https://www.google.com'), +var request = new sdk.Request('https://www.google.com'), options = {indentType: 'Tab', indentCount: 4, followRediredirect: false, trimRequestBody: true, requestTimeout: 0}; convert(request, options, function (err, snippet) { From d915c75249e3c1f610e034120060cf0815eaeda3 Mon Sep 17 00:00:00 2001 From: someshkoli Date: Tue, 31 Mar 2020 02:05:46 +0530 Subject: [PATCH 2/2] Adds support for axios codegen --- codegens/nodejs-axios/.gitignore | 43 ++ codegens/nodejs-axios/.npmignore | 76 +++ codegens/nodejs-axios/README.md | 42 ++ codegens/nodejs-axios/compile_script.sh | 1 + codegens/nodejs-axios/index.js | 1 + codegens/nodejs-axios/lib/axios.js | 175 +++++++ codegens/nodejs-axios/lib/index.js | 4 + codegens/nodejs-axios/lib/lodash.js | 456 ++++++++++++++++++ codegens/nodejs-axios/lib/parseRequest.js | 182 +++++++ codegens/nodejs-axios/lib/util.js | 88 ++++ codegens/nodejs-axios/npm-shrinkwrap.json | 42 ++ codegens/nodejs-axios/npm/test-lint.js | 58 +++ codegens/nodejs-axios/npm/test-newman.js | 59 +++ codegens/nodejs-axios/npm/test.js | 19 + codegens/nodejs-axios/package.json | 36 ++ .../nodejs-axios/test/newman/newman.test.js | 24 + 16 files changed, 1306 insertions(+) create mode 100644 codegens/nodejs-axios/.gitignore create mode 100644 codegens/nodejs-axios/.npmignore create mode 100644 codegens/nodejs-axios/README.md create mode 100644 codegens/nodejs-axios/compile_script.sh create mode 100644 codegens/nodejs-axios/index.js create mode 100644 codegens/nodejs-axios/lib/axios.js create mode 100644 codegens/nodejs-axios/lib/index.js create mode 100644 codegens/nodejs-axios/lib/lodash.js create mode 100644 codegens/nodejs-axios/lib/parseRequest.js create mode 100644 codegens/nodejs-axios/lib/util.js create mode 100644 codegens/nodejs-axios/npm-shrinkwrap.json create mode 100644 codegens/nodejs-axios/npm/test-lint.js create mode 100644 codegens/nodejs-axios/npm/test-newman.js create mode 100644 codegens/nodejs-axios/npm/test.js create mode 100644 codegens/nodejs-axios/package.json create mode 100644 codegens/nodejs-axios/test/newman/newman.test.js diff --git a/codegens/nodejs-axios/.gitignore b/codegens/nodejs-axios/.gitignore new file mode 100644 index 000000000..7a0e0e42d --- /dev/null +++ b/codegens/nodejs-axios/.gitignore @@ -0,0 +1,43 @@ +.DS_Store +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Coverage directory used by tools like istanbul +.coverage + +# node-waf configuration +.lock-wscript + + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +out/ + +codesnippet.js \ No newline at end of file diff --git a/codegens/nodejs-axios/.npmignore b/codegens/nodejs-axios/.npmignore new file mode 100644 index 000000000..79ad2ba5f --- /dev/null +++ b/codegens/nodejs-axios/.npmignore @@ -0,0 +1,76 @@ +### NPM Specific: Disregard recursive project files +### =============================================== +/.editorconfig +/.gitmodules +/test + +### Borrowed from .gitignore +### ======================== + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Prevent IDE stuff +.idea +.vscode +*.sublime-* + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +.coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +snippet.swift + +out/ diff --git a/codegens/nodejs-axios/README.md b/codegens/nodejs-axios/README.md new file mode 100644 index 000000000..a6b89e9ff --- /dev/null +++ b/codegens/nodejs-axios/README.md @@ -0,0 +1,42 @@ + +> Converts Postman-SDK Request into code snippet for . + +#### Prerequisites +To run Code-Gen, ensure that you have NodeJS >= v8. A copy of the NodeJS installable can be downloaded from https://nodejs.org/en/download/package-manager. + +## Using the Module +The module will expose an object which will have property `convert` which is the function for converting the Postman-SDK request to swift code snippet. + +### convert function +Convert function takes three parameters + +* `request` - Postman-SDK Request Object + +* `options` - options is an object which hsa following properties + * `indentType` - String denoting type of indentation for code snippet. eg: 'Space', 'Tab' + * `indentCount` - The number of indentation characters to add per code level + * `trimRequestBody` - Whether or not request body fields should be trimmed + +* `callback` - callback function with first parameter as error and second parameter as string for code snippet + +##### Example: +```js +var request = new sdk.Request('www.google.com'), //using postman sdk to create request + options = { + indentCount: 3, + indentType: 'Space', + requestTimeout: 200, + trimRequestBody: true + }; +convert(request, options, function(error, snippet) { + if (error) { + // handle error + } + // handle snippet +}); +``` +### Guidelines for using generated snippet + +* Since Postman-SDK Request object doesn't provide complete path of the file, it needs to be manually inserted in case of uploading a file. + +* This module doesn't support cookies. \ No newline at end of file diff --git a/codegens/nodejs-axios/compile_script.sh b/codegens/nodejs-axios/compile_script.sh new file mode 100644 index 000000000..ea879ce23 --- /dev/null +++ b/codegens/nodejs-axios/compile_script.sh @@ -0,0 +1 @@ +mkdir collection-sdk;cd collection-sdk;npm init -y;npm install axios \ No newline at end of file diff --git a/codegens/nodejs-axios/index.js b/codegens/nodejs-axios/index.js new file mode 100644 index 000000000..bb0a047c4 --- /dev/null +++ b/codegens/nodejs-axios/index.js @@ -0,0 +1 @@ +module.exports = require('./lib'); diff --git a/codegens/nodejs-axios/lib/axios.js b/codegens/nodejs-axios/lib/axios.js new file mode 100644 index 000000000..bf2b08648 --- /dev/null +++ b/codegens/nodejs-axios/lib/axios.js @@ -0,0 +1,175 @@ +var _ = require('./lodash'), + + parseRequest = require('./parseRequest'), + sanitize = require('./util').sanitize, + sanitizeOptions = require('./util').sanitizeOptions; + +/** + * returns snippet of nodejs(request) by parsing data from Postman-SDK request object + * + * @param {Object} request - Postman SDK request object + * @param {String} indentString - indentation required for code snippet + * @param {Object} options + * @returns {String} - nodejs(request) code snippet for given request object + */ +function makeSnippet (request, indentString, options) { + var snippet, + optionsArrayBody = [], + optionsArrayConfig = [], + isFormDataFile = false; + if (options.ES6_enabled) { + snippet = 'const '; + } + else { + snippet = 'var '; + } + snippet += 'axios = require(\'axios\'),\n'; + snippet += ' qs = require(\'qs\');\n'; + if (request.body && request.body.mode === 'formdata') { + _.forEach(request.body.toJSON().formdata, function (data) { + if (!data.disabled && data.type === 'file') { + isFormDataFile = true; + } + }); + } + if (isFormDataFile) { + if (options.ES6_enabled) { + snippet += 'const '; + } + else { + snippet += 'var '; + } + snippet += 'fs = require(\'fs\');\n'; + } + snippet += `axios.${request.method.toLowerCase()}('${sanitize(request.url.toString())}',\n`; + + if (request.body && !request.headers.has('Content-Type')) { + if (request.body.mode === 'file') { + request.addHeader({ + key: 'Content-Type', + value: 'text/plain' + }); + } + else if (request.body.mode === 'graphql') { + request.addHeader({ + key: 'Content-Type', + value: 'application/json' + }); + } + } + + if (request.method === 'PUT' || request.method === 'POST' || request.method === 'PATCH') { + if (request.body && request.body[request.body.mode]) { + optionsArrayBody.push( + parseRequest.parseBody(request.body.toJSON(), indentString, options.trimRequestBody, + request.headers.get('Content-Type')) + ); + console.log(optionsArrayBody[0]); + } + snippet += optionsArrayBody.join(',\n') + ',\n'; + } + + if (options.requestTimeout) { + optionsArrayConfig.push(indentString + `timeout: ${options.requestTimeout}`); + } + if (options.followRedirect === false) { + optionsArrayConfig.push(indentString + 'followRedirect: false'); + } + + optionsArrayConfig.push(parseRequest.parseHeader(request, indentString)); + snippet += '{' + optionsArrayConfig.join(',\n') + '\n}\n'; + snippet += ').then((response) => {\n'; + // snippet += 'var body = await JSON.stringify(res.data.body);'; + snippet += '\tconsole.log(JSON.stringify(response.data));\n'; + snippet += '}).catch((err) => {\n'; + snippet += '\tconsole.log(err);\n'; + snippet += '});\n'; + console.log(snippet); + return snippet; +} + +/** + * Used to get the options specific to this codegen + * + * @returns {Array} - Returns an array of option objects + */ +function getOptions () { + return [{ + name: 'Set indentation count', + id: 'indentCount', + type: 'positiveInteger', + default: 2, + description: 'Set the number of indentation characters to add per code level' + }, + { + name: 'Set indentation type', + id: 'indentType', + type: 'enum', + availableOptions: ['Tab', 'Space'], + default: 'Space', + description: 'Select the character used to indent lines of code' + }, + { + name: 'Set request timeout', + id: 'requestTimeout', + type: 'positiveInteger', + default: 0, + description: 'Set number of milliseconds the request should wait for a response' + + ' before timing out (use 0 for infinity)' + }, + { + name: 'Follow redirects', + id: 'followRedirect', + type: 'boolean', + default: true, + description: 'Automatically follow HTTP redirects' + }, + { + name: 'Trim request body fields', + id: 'trimRequestBody', + type: 'boolean', + default: false, + description: 'Remove white space and additional lines that may affect the server\'s response' + }, + { + name: 'Enable ES6 features', + id: 'ES6_enabled', + type: 'boolean', + default: false, + description: 'Modifies code snippet to incorporate ES6 (EcmaScript) features' + } + ]; +} + +/** + * Converts Postman sdk request object to nodejs request code snippet + * + * @param {Object} request - postman-SDK request object + * @param {Object} options + * @param {String} options.indentType - type for indentation eg: Space, Tab + * @param {String} options.indentCount - number of spaces or tabs for indentation. + * @param {Boolean} options.followRedirect - whether to enable followredirect + * @param {Boolean} options.trimRequestBody - whether to trim fields in request body or not + * @param {Boolean} options.ES6_enabled - whether to generate snippet with ES6 features + * @param {Number} options.requestTimeout : time in milli-seconds after which request will bail out + * @param {Function} callback - callback function with parameters (error, snippet) + */ +function convert (request, options, callback) { + if (!_.isFunction(callback)) { + throw new Error('NodeJS-Request-Converter: callback is not valid function'); + } + options = sanitizeOptions(options, getOptions()); + + // String representing value of indentation required + var indentString; + + indentString = options.indentType === 'Tab' ? '\t' : ' '; + indentString = indentString.repeat(options.indentCount); + + return callback(null, makeSnippet(request, indentString, options)); +} + +module.exports = { + convert: convert, + getOptions: getOptions +}; diff --git a/codegens/nodejs-axios/lib/index.js b/codegens/nodejs-axios/lib/index.js new file mode 100644 index 000000000..3c01771b0 --- /dev/null +++ b/codegens/nodejs-axios/lib/index.js @@ -0,0 +1,4 @@ +module.exports = { + convert: require('./axios').convert, + getOptions: require('./axios').getOptions +}; diff --git a/codegens/nodejs-axios/lib/lodash.js b/codegens/nodejs-axios/lib/lodash.js new file mode 100644 index 000000000..55ea6666d --- /dev/null +++ b/codegens/nodejs-axios/lib/lodash.js @@ -0,0 +1,456 @@ +/* istanbul ignore next */ +module.exports = { + + /** + * Checks if `value` is an empty object, array or string. + * + * Objects are considered empty if they have no own enumerable string keyed + * properties. + * + * Values such as strings, arrays are considered empty if they have a `length` of `0`. + * + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is empty, else `false`. + * @example + * + * isEmpty(null) + * // => true + * + * isEmpty(true) + * // => true + * + * isEmpty(1) + * // => true + * + * isEmpty([1, 2, 3]) + * // => false + * + * isEmpty('abc') + * // => false + * + * isEmpty({ 'a': 1 }) + * // => false + */ + isEmpty: function (value) { + // eslint-disable-next-line lodash/prefer-is-nil + if (value === null || value === undefined) { + return true; + } + if (Array.isArray(value) || typeof value === 'string' || typeof value.splice === 'function') { + return !value.length; + } + + for (const key in value) { + if (Object.prototype.hasOwnProperty.call(value, key)) { + return false; + } + } + + return true; + }, + + /** + * Checks if `value` is `undefined`. + * + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`. + * @example + * + * isUndefined(void 0) + * // => true + * + * isUndefined(null) + * // => false + */ + isUndefined: function (value) { + return value === undefined; + }, + + /** + * Checks if `func` is classified as a `Function` object. + * + * @param {*} func The value to check. + * @returns {boolean} Returns `true` if `func` is a function, else `false`. + * @example + * + * isFunction(self.isEmpty) + * // => true + * + * isFunction(/abc/) + * // => false + */ + isFunction: function (func) { + return typeof func === 'function'; + }, + + /** + * Converts the first character of `string` to upper case and the remaining + * to lower case. + * + * @param {string} [string=''] The string to capitalize. + * @returns {string} Returns the capitalized string. + * @example + * + * capitalize('FRED') + * // => 'Fred' + * + * capitalize('john') + * // => 'John' + */ + + capitalize: function (string) { + return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase(); + }, + + /** + * Reduces `array` to a value which is the accumulated result of running + * each element in `array` thru `iteratee`, where each successive + * invocation is supplied the return value of the previous. If `accumulator` + * is not given, the first element of `array` is used as the initial + * value. The iteratee is invoked with four arguments: + * (accumulator, value, index|key, array). + * + * @param {Array} array The Array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {*} [accumulator] The initial value. + * @returns {*} Returns the accumulated value. + * @example + * + * reduce([1, 2], (sum, n) => sum + n, 0) + * // => 3 + * + */ + reduce: function (array, iteratee, accumulator) { + return array.reduce(iteratee, accumulator); + }, + + /** + * Iterates over elements of `array`, returning an array of all elements + * `predicate` returns truthy for. The predicate is invoked with three + * arguments: (value, index, array). + * + * @param {Array} array The array to iterate over. + * @param {Function|object} predicate The function/object invoked per iteration. + * @returns {Array} Returns the new filtered array. + * @example + * + * const users = [ + * { 'user': 'barney', 'active': true }, + * { 'user': 'fred', 'active': false } + * ] + * + * filter(users, ({ active }) => active) + * // => object for ['barney'] + */ + filter: function (array, predicate) { + if (typeof predicate === 'function') { + return array.filter(predicate); + } + var key = Object.keys(predicate), + val = predicate[key], + res = []; + array.forEach(function (item) { + if (item[key] && item[key] === val) { + res.push(item); + } + }); + return res; + }, + + /** + * The opposite of `filter` this method returns the elements of `array` + * that `predicate` does **not** return truthy for. + * + * @param {Array} array collection to iterate over. + * @param {String} predicate The String that needs to have truthy value, invoked per iteration. + * @returns {Array} Returns the new filtered array. + * @example + * + * const users = [ + * { 'user': 'barney', 'active': true }, + * { 'user': 'fred', 'active': false } + * ] + * + * reject(users, 'active') + * // => object for ['fred'] + */ + reject: function (array, predicate) { + var res = []; + array.forEach((object) => { + if (!object[predicate]) { + res.push(object); + } + }); + return res; + }, + + /** + * Creates an array of values by running each element of `array` thru `iteratee`. + * The iteratee is invoked with three arguments: (value, index, array). + * + * @param {Array} array The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the new mapped array. + * @example + * + * function square(n) { + * return n * n + * } + * + * map([4, 8], square) + * // => [16, 64] + */ + map: function (array, iteratee) { + return array.map(iteratee); + }, + + /** + * Iterates over elements of `collection` and invokes `iteratee` for each element. + * The iteratee is invoked with three arguments: (value, index|key, collection). + * + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array|Object} Returns `collection`. + * @example + * + * forEach([1, 2], value => console.log(value)) + * // => Logs `1` then `2`. + * + * forEach({ 'a': 1, 'b': 2 }, (value, key) => console.log(key)) + * // => Logs 'a' then 'b' + */ + + forEach: function (collection, iteratee) { + if (collection === null) { + return null; + } + + if (Array.isArray(collection)) { + return collection.forEach(iteratee); + } + const iterable = Object(collection), + props = Object.keys(collection); + var index = -1, + key, i; + + for (i = 0; i < props.length; i++) { + key = props[++index]; + iteratee(iterable[key], key, iterable); + } + return collection; + }, + + /** + * Checks if `value` is in `collection`. If `collection` is a string, it's + * checked for a substring of `value`, otherwise it checks if the `value` is present + * as a key in a `collection` object. + * + * @param {Array|Object|string} collection The collection to inspect. + * @param {*} value The value to search for. + * @returns {boolean} Returns `true` if `value` is found, else `false`. + * @example + * + * _.includes([1, 2, 3], 1); + * // => true + * + * _.includes({ 'a': 1, 'b': 2 }, 1); + * // => true + * + * _.includes('abcd', 'bc'); + * // => true + */ + includes: function (collection, value) { + if (Array.isArray(collection) || typeof collection === 'string') { + return collection.includes(value); + } + for (var key in collection) { + if (collection.hasOwnProperty(key)) { + if (collection[key] === value) { + return true; + } + } + } + return false; + }, + + /** + * Gets the size of `collection` by returning its length for array and strings. + * For objects it returns the number of enumerable string keyed + * properties. + * + * @param {Array|Object|string} collection The collection to inspect. + * @returns {number} Returns the collection size. + * @example + * + * size([1, 2, 3]) + * // => 3 + * + * size({ 'a': 1, 'b': 2 }) + * // => 2 + * + * size('pebbles') + * // => 7 + */ + size: function (collection) { + // eslint-disable-next-line lodash/prefer-is-nil + if (collection === null || collection === undefined) { + return 0; + } + if (Array.isArray(collection) || typeof collection === 'string') { + return collection.length; + } + + return Object.keys(collection).length; + }, + + /** + * Converts all elements in `array` into a string separated by `separator`. + * + * @param {Array} array The array to convert. + * @param {string} [separator=','] The element separator. + * @returns {string} Returns the joined string. + * @example + * + * _.join(['a', 'b', 'c'], '~'); + * // => 'a~b~c' + */ + join: function (array, separator) { + if (array === null) { + return ''; + } + return array.join(separator); + }, + + /** + * Removes trailing whitespace or specified characters from `string`. + * + * @param {string} [string=''] The string to trim. + * @param {string} [chars=whitespace] The characters to trim. + * @returns {string} Returns the trimmed string. + * @example + * + * trimEnd(' abc ') + * // => ' abc' + * + * trimEnd('-_-abc-_-', '_-') + * // => '-_-abc' + */ + trimEnd: function (string, chars) { + if (!string) { + return ''; + } + if (string && !chars) { + return string.replace(/\s*$/, ''); + } + chars += '$'; + return string.replace(new RegExp(chars, 'g'), ''); + }, + + /** + * Returns the index of the first + * element `predicate` returns truthy for. + * + * @param {Array} array The array to inspect. + * @param {Object} predicate The exact object to be searched for in the array. + * @returns {number} Returns the index of the found element, else `-1`. + * @example + * + * var users = [ + * { 'user': 'barney', 'active': false }, + * { 'user': 'fred', 'active': false }, + * { 'user': 'pebbles', 'active': true } + * ]; + * + * _.findIndex(users, { 'user': 'fred', 'active': false }); + * // => 1 + * + * _.findIndex(users, {'active' : false}); + * // => 0 + * + */ + findIndex: function (array, predicate) { + var length = array === null ? 0 : array.length, + index = -1, + keys = Object.keys(predicate), + found, i; + if (!length) { + return -1; + } + for (i = 0; i < array.length; i++) { + found = true; + // eslint-disable-next-line no-loop-func + keys.forEach((key) => { + if (!(array[i][key] && array[i][key] === predicate[key])) { + found = false; + } + }); + if (found) { + index = i; + break; + } + } + return index; + }, + + /** + * Gets the value at `path` of `object`. If the resolved value is + * `undefined`, the `defaultValue` is returned in its place. + * + * @param {Object} object The object to query. + * @param {string} path The path of the property to get. + * @param {*} [defaultValue] The value returned for `undefined` resolved values. + * @returns {*} Returns the resolved value. + * @example + * + * const object = { a: {b : 'c'} } + * + * + * get(object, 'a.b.c', 'default') + * // => 'default' + * + * get(object, 'a.b', 'default') + * // => 'c' + */ + get: function (object, path, defaultValue) { + if (object === null) { + return undefined; + } + var arr = path.split('.'), + res = object, + i; + for (i = 0; i < arr.length; i++) { + res = res[arr[i]]; + if (res === undefined) { + return defaultValue; + } + } + return res; + }, + + /** + * Checks if `predicate` returns truthy for **all** elements of `array`. + * Iteration is stopped once `predicate` returns falsey. The predicate is + * invoked with three arguments: (value, index, array). + * + * @param {Array} array The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if all elements pass the predicate check, + * else `false`. + * @example + * + * every([true, 1, null, 'yes'], Boolean) + * // => false + */ + every: function (array, predicate) { + var index = -1, + length = array === null ? 0 : array.length; + + while (++index < length) { + if (!predicate(array[index], index, array)) { + return false; + } + } + return true; + } + +}; diff --git a/codegens/nodejs-axios/lib/parseRequest.js b/codegens/nodejs-axios/lib/parseRequest.js new file mode 100644 index 000000000..512694c98 --- /dev/null +++ b/codegens/nodejs-axios/lib/parseRequest.js @@ -0,0 +1,182 @@ +var _ = require('./lodash'), + + sanitize = require('./util').sanitize, + path = require('path'); + +/** + * parses body of request when type of the request body is formdata or urlencoded and + * returns code snippet for nodejs to add body + * + * @param {Array} dataArray - array containing body elements of request + * @param {String} indentString - string required for indentation + * @param {Boolean} trimBody - indicates whether to trim body or not + */ +function extractFormData (dataArray, indentString, trimBody) { + if (!dataArray) { + return ''; + } + var snippetString = _.reduce(dataArray, (accumalator, item) => { + var requestBody = {}; + if (item.disabled) { + return accumalator; + } + /* istanbul ignore next */ + if (item.type === 'file') { + /** + * creating snippet to send file in nodejs request + * for example: + * 'fieldname': { + * 'value': fs.createStream('filename.ext'), + * 'options': { + * 'filename': 'filename.ext', + * 'contentType: null + * } + * } + * } + */ + if (Array.isArray(item.src) && item.src.length) { + let fileSnippet = '', + fileArray = []; + _.forEach(item.src, (filePath) => { + fileArray.push(`${indentString.repeat(3)}fs.createReadStream('${sanitize(filePath, trimBody)}')`); + }); + if (fileArray.length) { + fileSnippet += `${indentString.repeat(2)}'${sanitize(item.key, trimBody)}': ` + + `[\n${fileArray.join(',\n')}\n${indentString.repeat(2)}]`; + accumalator.push(fileSnippet); + } + else { + return accumalator; + } + } + else if (typeof item.src !== 'string') { + accumalator.push([ + indentString.repeat(2) + `'${sanitize(item.key, trimBody)}': {`, + indentString.repeat(3) + '\'value\': fs.createReadStream(\'/path/to/file\'),', + indentString.repeat(3) + '\'options\': {', + indentString.repeat(4) + '\'filename\': \'filename\'', + indentString.repeat(4) + '\'contentType\': null', + indentString.repeat(3) + '}', + indentString.repeat(2) + '}' + ].join('\n')); + } + else { + // eslint-disable-next-line one-var + var pathArray = item.src.split(path.sep), + fileName = pathArray[pathArray.length - 1]; + accumalator.push([ + indentString.repeat(2) + `'${sanitize(item.key, trimBody)}': {`, + indentString.repeat(3) + `'value': fs.createReadStream('${sanitize(item.src, trimBody)}'),`, + indentString.repeat(3) + '\'options\': {', + indentString.repeat(4) + `'filename': '${sanitize(fileName, trimBody)}',`, + indentString.repeat(4) + '\'contentType\': null', + indentString.repeat(3) + '}', + indentString.repeat(2) + '}' + ].join('\n')); + } + } + else { + requestBody[sanitize(item.key, trimBody)] = sanitize(item.value, trimBody); + accumalator.push( + indentString.repeat(2) + + `'${sanitize(item.key, trimBody)}': '${sanitize(item.value, trimBody)}'` + ); + } + return accumalator; + }, []); + return '{' + snippetString.join(',') + '}'; +} + +/** + * Parses body object based on mode of body and returns code snippet + * + * @param {Object} requestbody - json object for body of request + * @param {String} indentString - string for indentation + * @param {Boolean} trimBody - indicates whether to trim body fields or not + * @param {String} contentType Content type of the body being sent + */ +function parseBody (requestbody, indentString, trimBody, contentType) { + if (requestbody) { + switch (requestbody.mode) { + case 'raw': + if (contentType === 'application/json') { + try { + let jsonBody = JSON.parse(requestbody[requestbody.mode]); + return `JSON.stringify(${JSON.stringify(jsonBody)})\n`; + } + catch (error) { + return `${JSON.stringify(requestbody[requestbody.mode])}\n`; + } + } + return `${JSON.stringify(requestbody[requestbody.mode])}\n`; + // eslint-disable-next-line no-case-declarations + case 'graphql': + let query = requestbody[requestbody.mode].query, + graphqlVariables; + try { + graphqlVariables = JSON.parse(requestbody[requestbody.mode].variables); + } + catch (e) { + graphqlVariables = {}; + } + return 'JSON.stringify({\n' + + `${indentString.repeat(2)}query: '${sanitize(query, trimBody)}',\n` + + `${indentString.repeat(2)}variables: ${JSON.stringify(graphqlVariables)}\n` + + `${indentString}})`; + case 'formdata': + return `{${extractFormData(requestbody[requestbody.mode], indentString, trimBody)}` + + indentString + '}'; + case 'urlencoded': + return `qs.stringify(${extractFormData(requestbody[requestbody.mode], indentString, trimBody)})`; + /* istanbul ignore next */ + case 'file': + // return 'formData: {\n' + + // extractFormData(requestbody[requestbody.mode], indentString, trimBody) + + // indentString + '}'; + return 'body: ""\n'; + default: + return ''; + } + } + return ''; +} + +/** + * parses header of request object and returns code snippet of nodejs request to add header + * + * @param {Object} request - Postman SDK request object + * @param {String} indentString - indentation required in code snippet + * @returns {String} - code snippet of nodejs request to add header + */ +function parseHeader (request, indentString) { + var headerObject = request.getHeaders({enabled: true}), + headerSnippet = indentString + '\'headers\': {\n'; + + if (!_.isEmpty(headerObject)) { + headerSnippet += _.reduce(Object.keys(headerObject), function (accumalator, key) { + if (Array.isArray(headerObject[key])) { + var headerValues = []; + _.forEach(headerObject[key], (value) => { + headerValues.push(`'${sanitize(value)}'`); + }); + accumalator.push( + indentString.repeat(2) + `'${sanitize(key, true)}': [${headerValues.join(', ')}]` + ); + } + else { + accumalator.push( + indentString.repeat(2) + `'${sanitize(key, true)}': '${sanitize(headerObject[key])}'` + ); + } + return accumalator; + }, []).join(',\n') + '\n'; + } + + headerSnippet += indentString + '}'; + return headerSnippet; +} + +module.exports = { + parseBody: parseBody, + parseHeader: parseHeader +}; diff --git a/codegens/nodejs-axios/lib/util.js b/codegens/nodejs-axios/lib/util.js new file mode 100644 index 000000000..e5e8b2a4b --- /dev/null +++ b/codegens/nodejs-axios/lib/util.js @@ -0,0 +1,88 @@ + +/** + * sanitizes input string by handling escape characters eg: converts '''' to '\'\'' + * and trim input if required + * + * @param {String} inputString + * @param {Boolean} [trim] - indicates whether to trim string or not + * @returns {String} + */ +function sanitize (inputString, trim) { + if (typeof inputString !== 'string') { + return ''; + } + (trim) && (inputString = inputString.trim()); + return inputString.replace(/\\/g, '\\\\').replace(/'/g, '\\\'').replace(/\n/g, '\\n'); +} + +/** + * sanitizes input options + * + * @param {Object} options - Options provided by the user + * @param {Array} optionsArray - options array received from getOptions function + * + * @returns {Object} - Sanitized options object + */ +function sanitizeOptions (options, optionsArray) { + var result = {}, + defaultOptions = {}, + id; + optionsArray.forEach((option) => { + defaultOptions[option.id] = { + default: option.default, + type: option.type + }; + if (option.type === 'enum') { + defaultOptions[option.id].availableOptions = option.availableOptions; + } + }); + + for (id in options) { + if (options.hasOwnProperty(id)) { + if (defaultOptions[id] === undefined) { + continue; + } + switch (defaultOptions[id].type) { + case 'boolean': + if (typeof options[id] !== 'boolean') { + result[id] = defaultOptions[id].default; + } + else { + result[id] = options[id]; + } + break; + case 'positiveInteger': + if (typeof options[id] !== 'number' || options[id] < 0) { + result[id] = defaultOptions[id].default; + } + else { + result[id] = options[id]; + } + break; + case 'enum': + if (!defaultOptions[id].availableOptions.includes(options[id])) { + result[id] = defaultOptions[id].default; + } + else { + result[id] = options[id]; + } + break; + default: + result[id] = options[id]; + } + } + } + + for (id in defaultOptions) { + if (defaultOptions.hasOwnProperty(id)) { + if (result[id] === undefined) { + result[id] = defaultOptions[id].default; + } + } + } + return result; +} +module.exports = { + sanitize: sanitize, + sanitizeOptions: sanitizeOptions +}; diff --git a/codegens/nodejs-axios/npm-shrinkwrap.json b/codegens/nodejs-axios/npm-shrinkwrap.json new file mode 100644 index 000000000..20f247c73 --- /dev/null +++ b/codegens/nodejs-axios/npm-shrinkwrap.json @@ -0,0 +1,42 @@ +{ + "name": "@postman/codegen-nodejs-axios", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "qs": { + "version": "6.9.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", + "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==" + } + } +} diff --git a/codegens/nodejs-axios/npm/test-lint.js b/codegens/nodejs-axios/npm/test-lint.js new file mode 100644 index 000000000..b113b8bfd --- /dev/null +++ b/codegens/nodejs-axios/npm/test-lint.js @@ -0,0 +1,58 @@ +#!/usr/bin/env node +var shell = require('shelljs'), + chalk = require('chalk'), + async = require('async'), + ESLintCLIEngine = require('eslint').CLIEngine, + + /** + * The list of source code files / directories to be linted. + * + * @type {Array} + */ + LINT_SOURCE_DIRS = [ + './lib', + './bin', + './test', + './examples/*.js', + './npm/*.js', + './index.js' + ]; + +module.exports = function (exit) { + // banner line + console.info(chalk.yellow.bold('\nLinting files using eslint...')); + + async.waterfall([ + + /** + * Instantiates an ESLint CLI engine and runs it in the scope defined within LINT_SOURCE_DIRS. + * + * @param {Function} next - The callback function whose invocation marks the end of the lint test run. + * @returns {*} + */ + function (next) { + next(null, (new ESLintCLIEngine()).executeOnFiles(LINT_SOURCE_DIRS)); + }, + + /** + * Processes a test report from the Lint test runner, and displays meaningful results. + * + * @param {Object} report - The overall test report for the current lint test. + * @param {Object} report.results - The set of test results for the current lint run. + * @param {Function} next - The callback whose invocation marks the completion of the post run tasks. + * @returns {*} + */ + function (report, next) { + var errorReport = ESLintCLIEngine.getErrorResults(report.results); + // log the result to CLI + console.info(ESLintCLIEngine.getFormatter()(report.results)); + // log the success of the parser if it has no errors + (errorReport && !errorReport.length) && console.info(chalk.green('eslint ok!')); + // ensure that the exit code is non zero in case there was an error + next(Number(errorReport && errorReport.length) || 0); + } + ], exit); +}; + +// ensure we run this script exports if this is a direct stdin.tty run +!module.parent && module.exports(shell.exit); diff --git a/codegens/nodejs-axios/npm/test-newman.js b/codegens/nodejs-axios/npm/test-newman.js new file mode 100644 index 000000000..0c8559a8e --- /dev/null +++ b/codegens/nodejs-axios/npm/test-newman.js @@ -0,0 +1,59 @@ +#!/usr/bin/env node +/* eslint-env node, es6 */ +// --------------------------------------------------------------------------------------------------------------------- +// This script is intended to execute all unit tests. +// --------------------------------------------------------------------------------------------------------------------- + +var shell = require('shelljs'), + + // set directories and files for test and coverage report + path = require('path'), + + NYC = require('nyc'), + chalk = require('chalk'), + recursive = require('recursive-readdir'), + + COV_REPORT_PATH = '.coverage', + SPEC_SOURCE_DIR = path.join(__dirname, '..', 'test', 'newman'); + +module.exports = function (exit) { + // banner line + console.info(chalk.yellow.bold('Running newman tests using mocha on node...')); + + shell.test('-d', COV_REPORT_PATH) && shell.rm('-rf', COV_REPORT_PATH); + shell.mkdir('-p', COV_REPORT_PATH); + + var Mocha = require('mocha'), + nyc = new NYC({ + reportDir: COV_REPORT_PATH, + tempDirectory: COV_REPORT_PATH, + reporter: ['text', 'lcov', 'text-summary'], + exclude: ['config', 'test'], + hookRunInContext: true, + hookRunInThisContext: true + }); + + nyc.wrap(); + // add all spec files to mocha + recursive(SPEC_SOURCE_DIR, function (err, files) { + if (err) { console.error(err); return exit(1); } + + var mocha = new Mocha({ timeout: 1000 * 60 }); + + files.filter(function (file) { // extract all test files + return (file.substr(-8) === '.test.js'); + }).forEach(mocha.addFile.bind(mocha)); + + mocha.run(function (runError) { + runError && console.error(runError.stack || runError); + + nyc.reset(); + nyc.writeCoverageFile(); + nyc.report(); + exit(runError ? 1 : 0); + }); + }); +}; + +// ensure we run this script exports if this is a direct stdin.tty run +!module.parent && module.exports(shell.exit); diff --git a/codegens/nodejs-axios/npm/test.js b/codegens/nodejs-axios/npm/test.js new file mode 100644 index 000000000..ee0be8b7d --- /dev/null +++ b/codegens/nodejs-axios/npm/test.js @@ -0,0 +1,19 @@ +#!/usr/bin/env node +var chalk = require('chalk'), + exit = require('shelljs').exit, + prettyms = require('pretty-ms'), + startedAt = Date.now(), + name = require('../package.json').name; + +require('async').series([ + require('./test-lint'), + require('./test-newman') + // Add a separate folder for every new suite of tests + // require('./test-unit') + // require('./test-browser') + // require('./test-integration') +], function (code) { + // eslint-disable-next-line max-len + console.info(chalk[code ? 'red' : 'green'](`\n${name}: duration ${prettyms(Date.now() - startedAt)}\n${name}: ${code ? 'not ok' : 'ok'}!`)); + exit(code && (typeof code === 'number' ? code : 1) || 0); +}); diff --git a/codegens/nodejs-axios/package.json b/codegens/nodejs-axios/package.json new file mode 100644 index 000000000..77f13da83 --- /dev/null +++ b/codegens/nodejs-axios/package.json @@ -0,0 +1,36 @@ +{ + "name": "@postman/codegen-nodejs-axios", + "version": "0.0.1", + "description": "Converts Postman SDK Requests to swift code snippet", + "main": "index.js", + "com_postman_plugin": { + "type": "code_generator", + "lang": "nodejs", + "variant": "axios", + "syntax_mode": "javascript" + }, + "directories": { + "lib": "lib", + "test": "test" + }, + "scripts": { + "test": "node npm/test.js", + "test-lint": "node npm/test-lint.js", + "test-newman": "node npm/test-newman.js" + }, + "repository": { + "type": "git", + "url": "" + }, + "author": "Postman Labs ", + "license": "Apache-2.0", + "homepage": "https://github.com/postmanlabs/code-generators/tree/master/codegens/nodejs-axios", + "dependencies": { + "axios": "0.19.2", + "qs": "6.9.3" + }, + "devDependencies": {}, + "engines": { + "node": ">=8" + } +} diff --git a/codegens/nodejs-axios/test/newman/newman.test.js b/codegens/nodejs-axios/test/newman/newman.test.js new file mode 100644 index 000000000..781cb7f38 --- /dev/null +++ b/codegens/nodejs-axios/test/newman/newman.test.js @@ -0,0 +1,24 @@ +var runNewmanTest = require('../../../../test/codegen/newman/newmanTestUtil').runNewmanTest, + convert = require('../../index').convert; + + +describe('nodejs-axios Converter', function () { + describe('convert for different request types', function () { + var options = { + indentType: 'Space', + indentCount: 4 + }, + testConfig = { + // filename along with the appropriate version of the file. This file will be used to run the snippet. + fileName: 'codesnippet.js', + // Run script required to run the generated code snippet + runScript: 'node codesnippet.js', + // Compile script required to compile the code snippet + // compileScript: 'mkdir request-axios;cd request-axios;npm init -y;npm install axios', + compileScript: '', + // Array of name of collections for which newman tests has to be skipped. + skipCollections: ['sameNameHeadersCollection', 'formdataCollection', 'formdataFileCollection'] + }; + runNewmanTest(convert, options, testConfig); + }); +});