Skip to content

Commit

Permalink
Merge pull request #69 from postmanlabs/release/v1.7.0
Browse files Browse the repository at this point in the history
Release version v1.7.0
  • Loading branch information
aman-v-singh authored Jun 27, 2023
2 parents f7d5b61 + a0db0ef commit e8df780
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 25 deletions.
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

## [Unreleased]

## [v1.7.0] - 2023-06-27

### Added

- Fix for - [#9941](https://github.com/postmanlabs/postman-app-support/issues/9941) Add method to identify GraphQL requests from body data

### Changed

- Assigned user errors for various handled errors

## [v1.6.0] - 2023-04-17

### Added
Expand Down Expand Up @@ -103,7 +113,9 @@ Newer releases follow the [Keep a Changelog](https://keepachangelog.com) format.
- Conforming to the internal Postman plugin interface
- Fixes for Github issues - 4770,3623,3135,4018,5737,5286, among others

[Unreleased]: https://github.com/postmanlabs/curl-to-postman/compare/v1.6.0...HEAD
[Unreleased]: https://github.com/postmanlabs/curl-to-postman/compare/v1.7.0...HEAD

[v1.7.0]: https://github.com/postmanlabs/curl-to-postman/compare/v1.6.0...v1.7.0

[v1.6.0]: https://github.com/postmanlabs/curl-to-postman/compare/v1.5.0...v1.6.0

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "curl-to-postmanv2",
"version": "1.6.0",
"version": "1.7.0",
"description": "Convert a given CURL command to a Postman request",
"main": "index.js",
"com_postman_plugin": {
Expand Down
15 changes: 15 additions & 0 deletions src/UserError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* constructor userError
* @constructor
* @param {*} message errorMessage
* @param {*} data additional data to be reported
*/
class UserError extends Error {
constructor(message, data) {
super(message);
this.name = 'UserError';
this.data = data || {};
}
}

module.exports = UserError;
14 changes: 14 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* eslint-disable max-len */

module.exports = {
USER_ERRORS: {
INVALID_FORMAT: 'Invalid format for cURL.',
METHOD_NOT_SUPPORTED: (_, method) => { return `The method ${method} is not supported.`; },
UNABLE_TO_PARSE_HEAD_AND_DATA: 'Unable to parse: Both (--head/-I) and (-d/--data/--data-raw/--data-binary/--data-ascii/--data-urlencode) are not supported.',
UNABLE_TO_PARSE_NO_URL: 'Unable to parse: Could not identify the URL. Please use the --url option.',
CANNOT_DETECT_URL: 'Could not detect the URL from cURL. Please make sure it\'s a valid cURL.',
INPUT_WITHOUT_OPTIONS: 'Only the URL can be provided without an option preceding it. All other inputs must be specified via options.',
MALFORMED_URL: 'Please check your cURL string for malformed URL.'
}
};

1 change: 1 addition & 0 deletions src/convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = function (input, cb) {
if (result.error) {
return cb(null, {
result: false,
error: result.error,
reason: result.error.message
});
}
Expand Down
1 change: 1 addition & 0 deletions src/getMetaData.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = function (input, cb) {
if (result.error) {
return cb(null, {
result: false,
error: result.error,
reason: result.error.message
});
}
Expand Down
118 changes: 101 additions & 17 deletions src/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@ const commander = require('commander'),
shellQuote = require('../assets/shell-quote'),
unnecessaryOptions = require('../assets/unnecessaryOptions'),
supportedOptions = require('../assets/supportedOptions'),
UserError = require('./UserError'),
{ USER_ERRORS } = require('./constants'),
formDataOptions = ['-d', '--data', '--data-raw', '--data-binary', '--data-ascii'],
allowedOperators = ['<', '>', '(', ')'];

var program,

curlConverter = {
requestUrl: '',

initialize: function() {
/**
* Collects values from the command line arguments and adds them to the memo array.
*
* @param {string} str - The argument value to collect.
* @param {Array} memo - The array to add the collected values to.
* @returns {Array} - The updated memo array.
*/
function collectValues(str, memo) {
memo.push(str);
return memo;
Expand Down Expand Up @@ -111,7 +119,7 @@ var program,

if (validMethods.indexOf(curlObj.request.toUpperCase()) === -1) {
// the method is still not valid
throw new Error('The method ' + curlObj.request + ' is not supported');
throw new UserError(USER_ERRORS.METHOD_NOT_SUPPORTED`${curlObj.request}`);
}
}

Expand All @@ -120,8 +128,7 @@ var program,
if ((curlObj.data.length > 0 || curlObj.dataAscii.length > 0 ||
curlObj.dataBinary || curlObj.dataUrlencode.length > 0) &&
curlObj.head && !curlObj.get) {
throw new Error('Unable to parse: Both (--head/-I) and' +
' (-d/--data/--data-raw/--data-binary/--data-ascii/--data-urlencode) are not supported');
throw new UserError(USER_ERRORS.UNABLE_TO_PARSE_HEAD_AND_DATA);
}

/**
Expand All @@ -130,8 +137,7 @@ var program,
* once it fails here using convertForCMDFormat()
*/
if (curlObj.args.length > 1 && _.includes(curlObj.args, '^')) {
throw new Error('Only the URL can be provided without an option preceding it.' +
' All other inputs must be specified via options.');
throw new UserError(USER_ERRORS.INPUT_WITHOUT_OPTIONS);
}
},

Expand Down Expand Up @@ -368,7 +374,7 @@ var program,
inCorrectlyFormedcURLRegex2 = /(\w+=\w+&?)/g; // checks - foo?bar=1&baz=2

if (string.match(inCorrectlyFormedcURLRegex1) || string.match(inCorrectlyFormedcURLRegex2)) {
throw Error('Please check your cURL string for malformed URL');
throw new UserError(USER_ERRORS.MALFORMED_URL);
}
}
else if (_.isFunction(arg.startsWith) && arg.startsWith('$') && arg.length > 1) {
Expand Down Expand Up @@ -420,8 +426,8 @@ var program,
}
catch (e) {
if (e.message === 'process.exit is not a function') {
// happened because of
e.message = 'Invalid format for cURL.';
// happened because of
return { error: new UserError(USER_ERRORS.INVALID_FORMAT) };
}
return { error: e };
}
Expand All @@ -445,7 +451,7 @@ var program,
}
}
catch (e) {
throw new Error('Unable to parse: Could not identify the URL. Please use the --url option.');
throw new UserError(USER_ERRORS.UNABLE_TO_PARSE_NO_URL);
}
}
/* eslint-enable */
Expand Down Expand Up @@ -479,7 +485,7 @@ var program,
this.requestUrl = argStr;
}
else {
throw new Error('Could not detect the URL from cURL. Please make sure it\'s a valid cURL');
throw new UserError(USER_ERRORS.CANNOT_DETECT_URL);
}
},

Expand Down Expand Up @@ -666,10 +672,79 @@ var program,
return this.validate(curlString, false);
}

return { result: false, reason: e.message };
return { result: false, reason: e.message, error: e };
}
},

/**
* Escape JSON strings before JSON.parse
*
* @param {string} jsonString - Input JSON string
* @returns {string} - JSON string with escaped characters
*/
escapeJson: function (jsonString) {
// eslint-disable-next-line no-implicit-globals
meta = { // table of character substitutions
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r'
};
return jsonString.replace(/[\t\n\f\r]/g, (char) => {
return meta[char];
});
},

/**
* Identifies whether the input data string is a graphql query or not
*
* @param {string} dataString - Input data string to check if it is a graphql query
* @param {string} contentType - Content type header value
* @returns {Object} - { result: true, graphql: {Object} } if dataString is a graphql query else { result: false }
*/
identifyGraphqlRequest: function (dataString, contentType) {
try {
const rawDataObj = _.attempt(JSON.parse, this.escapeJson(dataString));
if (contentType === 'application/json' && rawDataObj && !_.isError(rawDataObj)) {
if (!_.has(rawDataObj, 'query') || !_.isString(rawDataObj.query)) {
return { result: false };
}
if (_.has(rawDataObj, 'variables')) {
if (!_.isObject(rawDataObj.variables)) {
return { result: false };
}
}
else {
rawDataObj.variables = {};
}
if (_.has(rawDataObj, 'operationName')) {
if (!_.isString(rawDataObj.operationName)) {
return { result: false };
}
}
else {
rawDataObj.operationName = '';
}
if (_.keys(rawDataObj).length === 3) {
const graphqlVariables = JSON.stringify(rawDataObj.variables, null, 2);
return {
result: true,
graphql: {
query: rawDataObj.query,
operationName: rawDataObj.operationName,
variables: graphqlVariables === '{}' ? '' : graphqlVariables
}
};
}
}
return { result: false };
}
catch (e) {
return { result: false };
}
},


convertCurlToRequest: function(curlString, shouldRetry = true) {
try {
this.initialize();
Expand Down Expand Up @@ -741,10 +816,19 @@ var program,
bodyArr.push(this.trimQuotesFromString(dataUrlencode));
bodyArr.push(this.trimQuotesFromString(dataAsciiString));

request.body.mode = 'raw';
request.body.raw = _.join(_.reject(bodyArr, (ele) => {
return !ele;
}), '&');
const rawDataString = _.join(_.reject(bodyArr, (ele) => {
return !ele;
}), '&'),
graphqlRequestData = this.identifyGraphqlRequest(rawDataString, content_type);

if (graphqlRequestData.result) {
request.body.mode = 'graphql';
request.body.graphql = graphqlRequestData.graphql;
}
else {
request.body.mode = 'raw';
request.body.raw = rawDataString;
}

urlData = request.data;
}
Expand Down Expand Up @@ -796,7 +880,7 @@ var program,
}
}
if (e.message === 'process.exit is not a function') {
e.message = 'Invalid format for cURL.';
return { error: new UserError(USER_ERRORS.INVALID_FORMAT) };
}
return { error: e };
}
Expand Down
Loading

0 comments on commit e8df780

Please sign in to comment.