Skip to content

Commit e8df780

Browse files
authored
Merge pull request #69 from postmanlabs/release/v1.7.0
Release version v1.7.0
2 parents f7d5b61 + a0db0ef commit e8df780

File tree

9 files changed

+184
-25
lines changed

9 files changed

+184
-25
lines changed

CHANGELOG.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
## [Unreleased]
44

5+
## [v1.7.0] - 2023-06-27
6+
7+
### Added
8+
9+
- Fix for - [#9941](https://github.com/postmanlabs/postman-app-support/issues/9941) Add method to identify GraphQL requests from body data
10+
11+
### Changed
12+
13+
- Assigned user errors for various handled errors
14+
515
## [v1.6.0] - 2023-04-17
616

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

106-
[Unreleased]: https://github.com/postmanlabs/curl-to-postman/compare/v1.6.0...HEAD
116+
[Unreleased]: https://github.com/postmanlabs/curl-to-postman/compare/v1.7.0...HEAD
117+
118+
[v1.7.0]: https://github.com/postmanlabs/curl-to-postman/compare/v1.6.0...v1.7.0
107119

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

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "curl-to-postmanv2",
3-
"version": "1.6.0",
3+
"version": "1.7.0",
44
"description": "Convert a given CURL command to a Postman request",
55
"main": "index.js",
66
"com_postman_plugin": {

src/UserError.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* constructor userError
3+
* @constructor
4+
* @param {*} message errorMessage
5+
* @param {*} data additional data to be reported
6+
*/
7+
class UserError extends Error {
8+
constructor(message, data) {
9+
super(message);
10+
this.name = 'UserError';
11+
this.data = data || {};
12+
}
13+
}
14+
15+
module.exports = UserError;

src/constants.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* eslint-disable max-len */
2+
3+
module.exports = {
4+
USER_ERRORS: {
5+
INVALID_FORMAT: 'Invalid format for cURL.',
6+
METHOD_NOT_SUPPORTED: (_, method) => { return `The method ${method} is not supported.`; },
7+
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.',
8+
UNABLE_TO_PARSE_NO_URL: 'Unable to parse: Could not identify the URL. Please use the --url option.',
9+
CANNOT_DETECT_URL: 'Could not detect the URL from cURL. Please make sure it\'s a valid cURL.',
10+
INPUT_WITHOUT_OPTIONS: 'Only the URL can be provided without an option preceding it. All other inputs must be specified via options.',
11+
MALFORMED_URL: 'Please check your cURL string for malformed URL.'
12+
}
13+
};
14+

src/convert.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module.exports = function (input, cb) {
1515
if (result.error) {
1616
return cb(null, {
1717
result: false,
18+
error: result.error,
1819
reason: result.error.message
1920
});
2021
}

src/getMetaData.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module.exports = function (input, cb) {
1515
if (result.error) {
1616
return cb(null, {
1717
result: false,
18+
error: result.error,
1819
reason: result.error.message
1920
});
2021
}

src/lib.js

Lines changed: 101 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,23 @@ const commander = require('commander'),
44
shellQuote = require('../assets/shell-quote'),
55
unnecessaryOptions = require('../assets/unnecessaryOptions'),
66
supportedOptions = require('../assets/supportedOptions'),
7+
UserError = require('./UserError'),
8+
{ USER_ERRORS } = require('./constants'),
79
formDataOptions = ['-d', '--data', '--data-raw', '--data-binary', '--data-ascii'],
810
allowedOperators = ['<', '>', '(', ')'];
911

1012
var program,
1113

1214
curlConverter = {
1315
requestUrl: '',
14-
1516
initialize: function() {
17+
/**
18+
* Collects values from the command line arguments and adds them to the memo array.
19+
*
20+
* @param {string} str - The argument value to collect.
21+
* @param {Array} memo - The array to add the collected values to.
22+
* @returns {Array} - The updated memo array.
23+
*/
1624
function collectValues(str, memo) {
1725
memo.push(str);
1826
return memo;
@@ -111,7 +119,7 @@ var program,
111119

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

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

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

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

370376
if (string.match(inCorrectlyFormedcURLRegex1) || string.match(inCorrectlyFormedcURLRegex2)) {
371-
throw Error('Please check your cURL string for malformed URL');
377+
throw new UserError(USER_ERRORS.MALFORMED_URL);
372378
}
373379
}
374380
else if (_.isFunction(arg.startsWith) && arg.startsWith('$') && arg.length > 1) {
@@ -420,8 +426,8 @@ var program,
420426
}
421427
catch (e) {
422428
if (e.message === 'process.exit is not a function') {
423-
// happened because of
424-
e.message = 'Invalid format for cURL.';
429+
// happened because of
430+
return { error: new UserError(USER_ERRORS.INVALID_FORMAT) };
425431
}
426432
return { error: e };
427433
}
@@ -445,7 +451,7 @@ var program,
445451
}
446452
}
447453
catch (e) {
448-
throw new Error('Unable to parse: Could not identify the URL. Please use the --url option.');
454+
throw new UserError(USER_ERRORS.UNABLE_TO_PARSE_NO_URL);
449455
}
450456
}
451457
/* eslint-enable */
@@ -479,7 +485,7 @@ var program,
479485
this.requestUrl = argStr;
480486
}
481487
else {
482-
throw new Error('Could not detect the URL from cURL. Please make sure it\'s a valid cURL');
488+
throw new UserError(USER_ERRORS.CANNOT_DETECT_URL);
483489
}
484490
},
485491

@@ -666,10 +672,79 @@ var program,
666672
return this.validate(curlString, false);
667673
}
668674

669-
return { result: false, reason: e.message };
675+
return { result: false, reason: e.message, error: e };
676+
}
677+
},
678+
679+
/**
680+
* Escape JSON strings before JSON.parse
681+
*
682+
* @param {string} jsonString - Input JSON string
683+
* @returns {string} - JSON string with escaped characters
684+
*/
685+
escapeJson: function (jsonString) {
686+
// eslint-disable-next-line no-implicit-globals
687+
meta = { // table of character substitutions
688+
'\t': '\\t',
689+
'\n': '\\n',
690+
'\f': '\\f',
691+
'\r': '\\r'
692+
};
693+
return jsonString.replace(/[\t\n\f\r]/g, (char) => {
694+
return meta[char];
695+
});
696+
},
697+
698+
/**
699+
* Identifies whether the input data string is a graphql query or not
700+
*
701+
* @param {string} dataString - Input data string to check if it is a graphql query
702+
* @param {string} contentType - Content type header value
703+
* @returns {Object} - { result: true, graphql: {Object} } if dataString is a graphql query else { result: false }
704+
*/
705+
identifyGraphqlRequest: function (dataString, contentType) {
706+
try {
707+
const rawDataObj = _.attempt(JSON.parse, this.escapeJson(dataString));
708+
if (contentType === 'application/json' && rawDataObj && !_.isError(rawDataObj)) {
709+
if (!_.has(rawDataObj, 'query') || !_.isString(rawDataObj.query)) {
710+
return { result: false };
711+
}
712+
if (_.has(rawDataObj, 'variables')) {
713+
if (!_.isObject(rawDataObj.variables)) {
714+
return { result: false };
715+
}
716+
}
717+
else {
718+
rawDataObj.variables = {};
719+
}
720+
if (_.has(rawDataObj, 'operationName')) {
721+
if (!_.isString(rawDataObj.operationName)) {
722+
return { result: false };
723+
}
724+
}
725+
else {
726+
rawDataObj.operationName = '';
727+
}
728+
if (_.keys(rawDataObj).length === 3) {
729+
const graphqlVariables = JSON.stringify(rawDataObj.variables, null, 2);
730+
return {
731+
result: true,
732+
graphql: {
733+
query: rawDataObj.query,
734+
operationName: rawDataObj.operationName,
735+
variables: graphqlVariables === '{}' ? '' : graphqlVariables
736+
}
737+
};
738+
}
739+
}
740+
return { result: false };
741+
}
742+
catch (e) {
743+
return { result: false };
670744
}
671745
},
672746

747+
673748
convertCurlToRequest: function(curlString, shouldRetry = true) {
674749
try {
675750
this.initialize();
@@ -741,10 +816,19 @@ var program,
741816
bodyArr.push(this.trimQuotesFromString(dataUrlencode));
742817
bodyArr.push(this.trimQuotesFromString(dataAsciiString));
743818

744-
request.body.mode = 'raw';
745-
request.body.raw = _.join(_.reject(bodyArr, (ele) => {
746-
return !ele;
747-
}), '&');
819+
const rawDataString = _.join(_.reject(bodyArr, (ele) => {
820+
return !ele;
821+
}), '&'),
822+
graphqlRequestData = this.identifyGraphqlRequest(rawDataString, content_type);
823+
824+
if (graphqlRequestData.result) {
825+
request.body.mode = 'graphql';
826+
request.body.graphql = graphqlRequestData.graphql;
827+
}
828+
else {
829+
request.body.mode = 'raw';
830+
request.body.raw = rawDataString;
831+
}
748832

749833
urlData = request.data;
750834
}
@@ -796,7 +880,7 @@ var program,
796880
}
797881
}
798882
if (e.message === 'process.exit is not a function') {
799-
e.message = 'Invalid format for cURL.';
883+
return { error: new UserError(USER_ERRORS.INVALID_FORMAT) };
800884
}
801885
return { error: e };
802886
}

0 commit comments

Comments
 (0)