forked from extern/bruno
feat: add new request from curl feature
This commit is contained in:
parent
fa8ec4267f
commit
bbcab8d338
28
package-lock.json
generated
28
package-lock.json
generated
@ -12178,6 +12178,14 @@
|
|||||||
"xml2js": "^0.4.5"
|
"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": {
|
"node_modules/parse-headers": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -14515,6 +14523,11 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/side-channel": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -16470,6 +16483,7 @@
|
|||||||
"mousetrap": "^1.6.5",
|
"mousetrap": "^1.6.5",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"next": "12.3.3",
|
"next": "12.3.3",
|
||||||
|
"parse-curl": "^0.2.6",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"platform": "^1.3.6",
|
"platform": "^1.3.6",
|
||||||
"posthog-node": "^2.1.0",
|
"posthog-node": "^2.1.0",
|
||||||
@ -20540,6 +20554,7 @@
|
|||||||
"mousetrap": "^1.6.5",
|
"mousetrap": "^1.6.5",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"next": "12.3.3",
|
"next": "12.3.3",
|
||||||
|
"parse-curl": "*",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"platform": "^1.3.6",
|
"platform": "^1.3.6",
|
||||||
"posthog-node": "^2.1.0",
|
"posthog-node": "^2.1.0",
|
||||||
@ -25603,6 +25618,14 @@
|
|||||||
"xml2js": "^0.4.5"
|
"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": {
|
"parse-headers": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"dev": true
|
"dev": true
|
||||||
@ -27036,6 +27059,11 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"dev": true
|
"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": {
|
"side-channel": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
"mousetrap": "^1.6.5",
|
"mousetrap": "^1.6.5",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"next": "12.3.3",
|
"next": "12.3.3",
|
||||||
|
"parse-curl": "^0.2.6",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"platform": "^1.3.6",
|
"platform": "^1.3.6",
|
||||||
"posthog-node": "^2.1.0",
|
"posthog-node": "^2.1.0",
|
||||||
|
@ -11,6 +11,7 @@ import { addTab } from 'providers/ReduxStore/slices/tabs';
|
|||||||
import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelector';
|
import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelector';
|
||||||
import { getDefaultRequestPaneTab } from 'utils/collections';
|
import { getDefaultRequestPaneTab } from 'utils/collections';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { getRequestFromCurlCommand } from 'utils/curl';
|
||||||
|
|
||||||
const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -21,7 +22,8 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
requestName: '',
|
requestName: '',
|
||||||
requestType: 'http-request',
|
requestType: 'http-request',
|
||||||
requestUrl: '',
|
requestUrl: '',
|
||||||
requestMethod: 'GET'
|
requestMethod: 'GET',
|
||||||
|
curlCommand: ''
|
||||||
},
|
},
|
||||||
validationSchema: Yup.object({
|
validationSchema: Yup.object({
|
||||||
requestName: Yup.string()
|
requestName: Yup.string()
|
||||||
@ -61,6 +63,23 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
onClose();
|
onClose();
|
||||||
})
|
})
|
||||||
.catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request'));
|
.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 {
|
} else {
|
||||||
dispatch(
|
dispatch(
|
||||||
newHttpRequest({
|
newHttpRequest({
|
||||||
@ -124,9 +143,22 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
<label htmlFor="graphql-request" className="ml-1 cursor-pointer select-none">
|
<label htmlFor="graphql-request" className="ml-1 cursor-pointer select-none">
|
||||||
GraphQL
|
GraphQL
|
||||||
</label>
|
</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>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<label htmlFor="requestName" className="block font-semibold">
|
<label htmlFor="requestName" className="block font-semibold">
|
||||||
Name
|
Name
|
||||||
@ -148,38 +180,55 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
<div className="text-red-500">{formik.errors.requestName}</div>
|
<div className="text-red-500">{formik.errors.requestName}</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
{formik.values.requestType !== 'from-curl' ? (
|
||||||
|
<>
|
||||||
|
<div className="mt-4">
|
||||||
|
<label htmlFor="request-url" className="block font-semibold">
|
||||||
|
URL
|
||||||
|
</label>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="flex items-center mt-2 ">
|
||||||
<label htmlFor="request-url" className="block font-semibold">
|
<div className="flex items-center h-full method-selector-container">
|
||||||
URL
|
<HttpMethodSelector
|
||||||
</label>
|
method={formik.values.requestMethod}
|
||||||
|
onMethodSelect={(val) => formik.setFieldValue('requestMethod', val)}
|
||||||
<div className="flex items-center mt-2 ">
|
/>
|
||||||
<div className="flex items-center h-full method-selector-container">
|
</div>
|
||||||
<HttpMethodSelector
|
<div className="flex items-center flex-grow input-container h-full">
|
||||||
method={formik.values.requestMethod}
|
<input
|
||||||
onMethodSelect={(val) => formik.setFieldValue('requestMethod', val)}
|
id="request-url"
|
||||||
/>
|
type="text"
|
||||||
</div>
|
name="requestUrl"
|
||||||
<div className="flex items-center flex-grow input-container h-full">
|
className="px-3 w-full "
|
||||||
<input
|
autoComplete="off"
|
||||||
id="request-url"
|
autoCorrect="off"
|
||||||
type="text"
|
autoCapitalize="off"
|
||||||
name="requestUrl"
|
spellCheck="false"
|
||||||
className="px-3 w-full "
|
onChange={formik.handleChange}
|
||||||
autoComplete="off"
|
value={formik.values.requestUrl || ''}
|
||||||
autoCorrect="off"
|
/>
|
||||||
autoCapitalize="off"
|
</div>
|
||||||
spellCheck="false"
|
</div>
|
||||||
onChange={formik.handleChange}
|
{formik.touched.requestUrl && formik.errors.requestUrl ? (
|
||||||
value={formik.values.requestUrl || ''}
|
<div className="text-red-500">{formik.errors.requestUrl}</div>
|
||||||
/>
|
) : null}
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
{formik.touched.requestUrl && formik.errors.requestUrl ? (
|
)}
|
||||||
<div className="text-red-500">{formik.errors.requestUrl}</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</Modal>
|
</Modal>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
|
@ -568,7 +568,7 @@ export const moveItemToRootOfCollection = (collectionUid, draggedItemUid) => (di
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const newHttpRequest = (params) => (dispatch, getState) => {
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
@ -591,9 +591,9 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
|
|||||||
request: {
|
request: {
|
||||||
method: requestMethod,
|
method: requestMethod,
|
||||||
url: requestUrl,
|
url: requestUrl,
|
||||||
headers: [],
|
headers: headers ?? [],
|
||||||
params,
|
params,
|
||||||
body: {
|
body: body ?? {
|
||||||
mode: 'none',
|
mode: 'none',
|
||||||
json: null,
|
json: null,
|
||||||
text: null,
|
text: null,
|
||||||
|
65
packages/bruno-app/src/utils/curl/index.js
Normal file
65
packages/bruno-app/src/utils/curl/index.js
Normal file
@ -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'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user