Merge pull request #840 from snippetkid/338-curl-to-request

Add new request from cURL command
This commit is contained in:
Anoop M D 2023-11-01 17:43:29 +05:30 committed by GitHub
commit 0e0f04c5ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 182 additions and 34 deletions

28
package-lock.json generated
View File

@ -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": {

View File

@ -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",

View File

@ -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()
@ -35,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) => {
@ -61,6 +71,22 @@ 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') {
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({
@ -124,9 +150,22 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
<label htmlFor="graphql-request" className="ml-1 cursor-pointer select-none">
GraphQL
</label>
<input
id="from-curl"
className="cursor-pointer ml-auto"
type="radio"
name="requestType"
onChange={formik.handleChange}
value="from-curl"
checked={formik.values.requestType === 'from-curl'}
/>
<label htmlFor="from-curl" className="ml-1 cursor-pointer select-none">
From cURL
</label>
</div>
</div>
<div className="mt-4">
<label htmlFor="requestName" className="block font-semibold">
Name
@ -148,38 +187,58 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
<div className="text-red-500">{formik.errors.requestName}</div>
) : null}
</div>
{formik.values.requestType !== 'from-curl' ? (
<>
<div className="mt-4">
<label htmlFor="request-url" className="block font-semibold">
URL
</label>
<div className="mt-4">
<label htmlFor="request-url" className="block font-semibold">
URL
</label>
<div className="flex items-center mt-2 ">
<div className="flex items-center h-full method-selector-container">
<HttpMethodSelector
method={formik.values.requestMethod}
onMethodSelect={(val) => formik.setFieldValue('requestMethod', val)}
/>
</div>
<div className="flex items-center flex-grow input-container h-full">
<input
id="request-url"
type="text"
name="requestUrl"
className="px-3 w-full "
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.requestUrl || ''}
/>
<div className="flex items-center mt-2 ">
<div className="flex items-center h-full method-selector-container">
<HttpMethodSelector
method={formik.values.requestMethod}
onMethodSelect={(val) => formik.setFieldValue('requestMethod', val)}
/>
</div>
<div className="flex items-center flex-grow input-container h-full">
<input
id="request-url"
type="text"
name="requestUrl"
className="px-3 w-full "
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.requestUrl || ''}
/>
</div>
</div>
{formik.touched.requestUrl && formik.errors.requestUrl ? (
<div className="text-red-500">{formik.errors.requestUrl}</div>
) : null}
</div>
</>
) : (
<div className="mt-4">
<label htmlFor="request-url" className="block font-semibold">
cURL Command
</label>
<textarea
name="curlCommand"
placeholder="Enter cURL request here.."
className="block textbox w-full mt-4"
style={{ resize: 'none' }}
value={formik.values.curlCommand}
onChange={formik.handleChange}
></textarea>
{formik.touched.curlCommand && formik.errors.curlCommand ? (
<div className="text-red-500">{formik.errors.curlCommand}</div>
) : null}
</div>
{formik.touched.requestUrl && formik.errors.requestUrl ? (
<div className="text-red-500">{formik.errors.requestUrl}</div>
) : null}
</div>
)}
</form>
</Modal>
</StyledWrapper>

View File

@ -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,

View File

@ -0,0 +1,60 @@
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);
};
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;
}
}
return {
url: request.url,
method: request.method,
body,
headers: headers
};
} catch (error) {
return null;
}
};