From bbcab8d33860dd71813190afe396eb65e4c74e96 Mon Sep 17 00:00:00 2001 From: Dipin Jagadish Date: Tue, 31 Oct 2023 19:32:31 +0000 Subject: [PATCH 1/2] feat: add new request from curl feature --- package-lock.json | 28 +++++ packages/bruno-app/package.json | 1 + .../components/Sidebar/NewRequest/index.js | 111 +++++++++++++----- .../ReduxStore/slices/collections/actions.js | 6 +- packages/bruno-app/src/utils/curl/index.js | 65 ++++++++++ 5 files changed, 177 insertions(+), 34 deletions(-) create mode 100644 packages/bruno-app/src/utils/curl/index.js diff --git a/package-lock.json b/package-lock.json index 79f3de50..cf7a7d95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12178,6 +12178,14 @@ "xml2js": "^0.4.5" } }, + "node_modules/parse-curl": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/parse-curl/-/parse-curl-0.2.6.tgz", + "integrity": "sha512-ENhXeIxG4A6wFvYSU87b0o3Tp6U+Wup069GYhXCn0ZP/E7evfvomTSM/MeEOs3QTyuye+u2r7fFRj6nX0q9kQA==", + "dependencies": { + "shellwords": "^0.1.0" + } + }, "node_modules/parse-headers": { "version": "2.0.5", "dev": true, @@ -14515,6 +14523,11 @@ "node": ">=8" } }, + "node_modules/shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" + }, "node_modules/side-channel": { "version": "1.0.4", "license": "MIT", @@ -16470,6 +16483,7 @@ "mousetrap": "^1.6.5", "nanoid": "3.3.4", "next": "12.3.3", + "parse-curl": "^0.2.6", "path": "^0.12.7", "platform": "^1.3.6", "posthog-node": "^2.1.0", @@ -20540,6 +20554,7 @@ "mousetrap": "^1.6.5", "nanoid": "3.3.4", "next": "12.3.3", + "parse-curl": "*", "path": "^0.12.7", "platform": "^1.3.6", "posthog-node": "^2.1.0", @@ -25603,6 +25618,14 @@ "xml2js": "^0.4.5" } }, + "parse-curl": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/parse-curl/-/parse-curl-0.2.6.tgz", + "integrity": "sha512-ENhXeIxG4A6wFvYSU87b0o3Tp6U+Wup069GYhXCn0ZP/E7evfvomTSM/MeEOs3QTyuye+u2r7fFRj6nX0q9kQA==", + "requires": { + "shellwords": "^0.1.0" + } + }, "parse-headers": { "version": "2.0.5", "dev": true @@ -27036,6 +27059,11 @@ "version": "3.0.0", "dev": true }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" + }, "side-channel": { "version": "1.0.4", "requires": { diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index c3f82569..77ff96b6 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -42,6 +42,7 @@ "mousetrap": "^1.6.5", "nanoid": "3.3.4", "next": "12.3.3", + "parse-curl": "^0.2.6", "path": "^0.12.7", "platform": "^1.3.6", "posthog-node": "^2.1.0", diff --git a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js index 2e54b56a..1476a7de 100644 --- a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js @@ -11,6 +11,7 @@ import { addTab } from 'providers/ReduxStore/slices/tabs'; import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelector'; import { getDefaultRequestPaneTab } from 'utils/collections'; import StyledWrapper from './StyledWrapper'; +import { getRequestFromCurlCommand } from 'utils/curl'; const NewRequest = ({ collection, item, isEphemeral, onClose }) => { const dispatch = useDispatch(); @@ -21,7 +22,8 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { requestName: '', requestType: 'http-request', requestUrl: '', - requestMethod: 'GET' + requestMethod: 'GET', + curlCommand: '' }, validationSchema: Yup.object({ requestName: Yup.string() @@ -61,6 +63,23 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { onClose(); }) .catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request')); + } else if (values.requestType === 'from-curl') { + getRequestFromCurlCommand(values.curlCommand).then((request) => + dispatch( + newHttpRequest({ + requestName: values.requestName, + requestType: 'http-request', + requestUrl: request.url, + requestMethod: request.method, + collectionUid: collection.uid, + itemUid: item ? item.uid : null, + headers: request.headers, + body: request.body + }) + ) + .then(() => onClose()) + .catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request')) + ); } else { dispatch( newHttpRequest({ @@ -124,9 +143,22 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { + + + + -
+ {formik.values.requestType !== 'from-curl' ? ( + <> +
+ -
- - -
-
- formik.setFieldValue('requestMethod', val)} - /> -
-
- +
+
+ formik.setFieldValue('requestMethod', val)} + /> +
+
+ +
+
+ {formik.touched.requestUrl && formik.errors.requestUrl ? ( +
{formik.errors.requestUrl}
+ ) : null}
+ + ) : ( +
+ +
- {formik.touched.requestUrl && formik.errors.requestUrl ? ( -
{formik.errors.requestUrl}
- ) : null} -
+ )} diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index 20a68b80..2ef10795 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -568,7 +568,7 @@ export const moveItemToRootOfCollection = (collectionUid, draggedItemUid) => (di }; export const newHttpRequest = (params) => (dispatch, getState) => { - const { requestName, requestType, requestUrl, requestMethod, collectionUid, itemUid } = params; + const { requestName, requestType, requestUrl, requestMethod, collectionUid, itemUid, headers, body } = params; return new Promise((resolve, reject) => { const state = getState(); @@ -591,9 +591,9 @@ export const newHttpRequest = (params) => (dispatch, getState) => { request: { method: requestMethod, url: requestUrl, - headers: [], + headers: headers ?? [], params, - body: { + body: body ?? { mode: 'none', json: null, text: null, diff --git a/packages/bruno-app/src/utils/curl/index.js b/packages/bruno-app/src/utils/curl/index.js new file mode 100644 index 00000000..37282f09 --- /dev/null +++ b/packages/bruno-app/src/utils/curl/index.js @@ -0,0 +1,65 @@ +import * as parse from 'parse-curl'; +import { BrunoError } from 'utils/common/error'; +import { parseQueryParams } from 'utils/url'; + +export const getRequestFromCurlCommand = (command) => { + const parseFormData = (parsedBody) => { + parseQueryParams(parsedBody); + }; + return new Promise((resolve, reject) => { + try { + const request = parse(command); + const parsedHeader = request?.header; + const headers = + parsedHeader && + Object.keys(parsedHeader).map((key) => ({ name: key, value: parsedHeader[key], enabled: true })); + + const contentType = headers?.find((h) => h.name.toLowerCase() === 'content-type'); + const body = { + mode: 'none', + json: null, + text: null, + xml: null, + sparql: null, + multipartForm: null, + formUrlEncoded: null + }; + const parsedBody = request?.body; + if (parsedBody && contentType) { + switch (contentType.value.toLowerCase()) { + case 'application/json': + body.mode = 'json'; + body.json = parsedBody; + break; + case 'text/xml': + body.mode = 'xml'; + body.xml = parsedBody; + break; + case 'application/x-www-form-urlencoded': + body.mode = 'formUrlEncoded'; + body.formUrlEncoded = parseFormData(parsedBody); + break; + case 'multipart/form-data': + body.mode = 'multipartForm'; + body.multipartForm = parsedBody; + break; + case 'text/plain': + default: + body.mode = 'text'; + body.text = parsedBody; + break; + } + } + debugger; + console.log(request); + return resolve({ + url: request.url, + method: request.method, + body, + headers: headers + }); + } catch (error) { + return reject(new BrunoError('Unable to parse the cURL command')); + } + }); +}; From dbd3ab6064298e952a726b4c344250090b7ed1ff Mon Sep 17 00:00:00 2001 From: Dipin Jagadish Date: Wed, 1 Nov 2023 10:58:07 +0000 Subject: [PATCH 2/2] fix: validate curl command before submit --- .../components/Sidebar/NewRequest/index.js | 42 +++++--- packages/bruno-app/src/utils/curl/index.js | 101 +++++++++--------- 2 files changed, 74 insertions(+), 69 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js index 1476a7de..f6bcdfeb 100644 --- a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js @@ -37,6 +37,14 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { const trimmedValue = value ? value.trim().toLowerCase() : ''; return !['collection', 'folder'].includes(trimmedValue); } + }), + curlCommand: Yup.string() + .min(1, 'must be at least 1 character') + .required('curlCommand is required') + .test({ + name: 'curlCommand', + message: `Invalid cURL Command`, + test: (value) => getRequestFromCurlCommand(value) !== null }) }), onSubmit: (values) => { @@ -64,22 +72,21 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { }) .catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request')); } else if (values.requestType === 'from-curl') { - getRequestFromCurlCommand(values.curlCommand).then((request) => - dispatch( - newHttpRequest({ - requestName: values.requestName, - requestType: 'http-request', - requestUrl: request.url, - requestMethod: request.method, - collectionUid: collection.uid, - itemUid: item ? item.uid : null, - headers: request.headers, - body: request.body - }) - ) - .then(() => onClose()) - .catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request')) - ); + const request = getRequestFromCurlCommand(values.curlCommand); + dispatch( + newHttpRequest({ + requestName: values.requestName, + requestType: 'http-request', + requestUrl: request.url, + requestMethod: request.method, + collectionUid: collection.uid, + itemUid: item ? item.uid : null, + headers: request.headers, + body: request.body + }) + ) + .then(() => onClose()) + .catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request')); } else { dispatch( newHttpRequest({ @@ -227,6 +234,9 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { value={formik.values.curlCommand} onChange={formik.handleChange} > + {formik.touched.curlCommand && formik.errors.curlCommand ? ( +
{formik.errors.curlCommand}
+ ) : null}
)} diff --git a/packages/bruno-app/src/utils/curl/index.js b/packages/bruno-app/src/utils/curl/index.js index 37282f09..30080e46 100644 --- a/packages/bruno-app/src/utils/curl/index.js +++ b/packages/bruno-app/src/utils/curl/index.js @@ -6,60 +6,55 @@ export const getRequestFromCurlCommand = (command) => { const parseFormData = (parsedBody) => { parseQueryParams(parsedBody); }; - return new Promise((resolve, reject) => { - try { - const request = parse(command); - const parsedHeader = request?.header; - const headers = - parsedHeader && - Object.keys(parsedHeader).map((key) => ({ name: key, value: parsedHeader[key], enabled: true })); + try { + const request = parse(command); + const parsedHeader = request?.header; + const headers = + parsedHeader && Object.keys(parsedHeader).map((key) => ({ name: key, value: parsedHeader[key], enabled: true })); - const contentType = headers?.find((h) => h.name.toLowerCase() === 'content-type'); - const body = { - mode: 'none', - json: null, - text: null, - xml: null, - sparql: null, - multipartForm: null, - formUrlEncoded: null - }; - const parsedBody = request?.body; - if (parsedBody && contentType) { - switch (contentType.value.toLowerCase()) { - case 'application/json': - body.mode = 'json'; - body.json = parsedBody; - break; - case 'text/xml': - body.mode = 'xml'; - body.xml = parsedBody; - break; - case 'application/x-www-form-urlencoded': - body.mode = 'formUrlEncoded'; - body.formUrlEncoded = parseFormData(parsedBody); - break; - case 'multipart/form-data': - body.mode = 'multipartForm'; - body.multipartForm = parsedBody; - break; - case 'text/plain': - default: - body.mode = 'text'; - body.text = parsedBody; - break; - } + const contentType = headers?.find((h) => h.name.toLowerCase() === 'content-type'); + const body = { + mode: 'none', + json: null, + text: null, + xml: null, + sparql: null, + multipartForm: null, + formUrlEncoded: null + }; + const parsedBody = request?.body; + if (parsedBody && contentType) { + switch (contentType.value.toLowerCase()) { + case 'application/json': + body.mode = 'json'; + body.json = parsedBody; + break; + case 'text/xml': + body.mode = 'xml'; + body.xml = parsedBody; + break; + case 'application/x-www-form-urlencoded': + body.mode = 'formUrlEncoded'; + body.formUrlEncoded = parseFormData(parsedBody); + break; + case 'multipart/form-data': + body.mode = 'multipartForm'; + body.multipartForm = parsedBody; + break; + case 'text/plain': + default: + body.mode = 'text'; + body.text = parsedBody; + break; } - debugger; - console.log(request); - return resolve({ - url: request.url, - method: request.method, - body, - headers: headers - }); - } catch (error) { - return reject(new BrunoError('Unable to parse the cURL command')); } - }); + return { + url: request.url, + method: request.method, + body, + headers: headers + }; + } catch (error) { + return null; + } };