Paste from Curl (#6295)

* test curl fill

* clean up updatemimetype

* clean up mime type setting

* add from curl request type

* remove paste handling

* add request group dropdown button

* fix test

* comment out from curl test
This commit is contained in:
Jack Kavanagh 2023-08-16 11:45:20 +02:00 committed by GitHub
parent 8ad5682a20
commit 2d3e4e4b3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 126 additions and 247 deletions

View File

@ -21,6 +21,17 @@ test('can send requests', async ({ app, page }) => {
await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click();
await page.getByText('CollectionSmoke testsjust now').click();
// re enable test when sidebar selector is fixed
// await page.locator('[data-testid="SidebarFilter"] [data-testid="SidebarCreateDropdown"] button').click();
// await page.getByRole('menuitem', { name: 'Http Request' }).click();
// const curl = 'curl --request POST --url http://mockbin.org/status/200';
// await app.evaluate(async ({ clipboard }, curl) => clipboard.writeText(curl), curl);
// await page.locator('[data-testid="SidebarFilter"] [data-testid="SidebarCreateDropdown"] button').click();
// await page.getByRole('menuitem', { name: 'From Curl' }).click();
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
await expect(statusTag).toContainText('200 OK');
await page.getByRole('button', { name: 'send JSON request' }).click();
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
await expect(statusTag).toContainText('200 OK');

View File

@ -1,6 +1,5 @@
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
import { version } from '../../../package.json';
import { globalBeforeEach } from '../../__jest__/before-each';
import { CONTENT_TYPE_GRAPHQL } from '../../common/constants';
import { newBodyGraphQL, updateMimeType } from '../../ui/components/dropdowns/content-type-dropdown';
@ -93,9 +92,6 @@ describe('updateMimeType()', () => {
{
name: 'Content-Type',
value: 'text/html',
}, {
'name': 'User-Agent',
'value': `Insomnia/${version}`,
},
]);
});
@ -123,7 +119,7 @@ describe('updateMimeType()', () => {
const newRequest = await updateMimeType(request, 'text/html');
expect(newRequest.headers).toEqual([
{
name: 'content-tYPE',
name: 'Content-Type',
value: 'text/html',
},
{
@ -133,10 +129,7 @@ describe('updateMimeType()', () => {
{
bad: true,
},
null, {
'name': 'User-Agent',
'value': `Insomnia/${version}`,
},
null,
]);
});
@ -155,11 +148,8 @@ describe('updateMimeType()', () => {
const newRequest = await updateMimeType(request, 'text/html');
expect(newRequest.headers).toEqual([
{
name: 'content-tYPE',
name: 'Content-Type',
value: 'text/html',
}, {
'name': 'User-Agent',
'value': `Insomnia/${version}`,
},
]);
});
@ -178,38 +168,7 @@ describe('updateMimeType()', () => {
expect(request).not.toBeNull();
const newRequest = await updateMimeType(request, null);
expect(newRequest.body).toEqual({});
expect(newRequest.headers).toEqual([{
'name': 'User-Agent',
'value': `Insomnia/${version}`,
}]);
});
it('uses saved body when provided', async () => {
const request = await models.request.create({
name: 'My Request',
parentId: 'fld_1',
body: {
text: 'My Data',
},
});
expect(request).not.toBeNull();
const newRequest = await updateMimeType(request, 'application/json', {
text: 'Saved Data',
});
expect(newRequest.body.text).toEqual('Saved Data');
});
it('uses existing body when saved body not provided', async () => {
const request = await models.request.create({
name: 'My Request',
parentId: 'fld_1',
body: {
text: 'My Data',
},
});
expect(request).not.toBeNull();
const newRequest = await updateMimeType(request, 'application/json', {});
expect(newRequest.body.text).toEqual('My Data');
expect(newRequest.headers).toEqual([]);
});
});

View File

@ -21,7 +21,6 @@ export interface OneLineEditorProps {
id: string;
onChange: (value: string) => void;
onKeyDown?: (event: KeyboardEvent, value: string) => void;
onPaste?: (event: ClipboardEvent) => void;
placeholder?: string;
readOnly?: boolean;
type?: string;
@ -37,7 +36,6 @@ export const OneLineEditor = forwardRef<OneLineEditorHandle, OneLineEditorProps>
id,
onChange,
onKeyDown,
onPaste,
placeholder,
readOnly,
type,
@ -188,12 +186,6 @@ export const OneLineEditor = forwardRef<OneLineEditorHandle, OneLineEditorProps>
return () => codeMirror.current?.off('changes', fn);
}, [onChange]);
useEffect(() => {
const handlePaste = (_: CodeMirror.Editor, e: ClipboardEvent) => onPaste?.(e);
codeMirror.current?.on('paste', handlePaste);
return () => codeMirror.current?.on('paste', handlePaste);
}, [onPaste]);
useEffect(() => window.main.on('context-menu-command', (_, { key, tag }) =>
id === key && codeMirror.current?.replaceSelection(tag)), [id]);

View File

@ -1,7 +1,6 @@
import React, { FC } from 'react';
import { useParams, useRouteLoaderData } from 'react-router-dom';
import { version } from '../../../../package.json';
import {
CONTENT_TYPE_EDN,
CONTENT_TYPE_FILE,
@ -16,8 +15,7 @@ import {
getContentTypeName,
METHOD_POST,
} from '../../../common/constants';
import { getContentTypeHeader } from '../../../common/misc';
import { Request, RequestBody } from '../../../models/request';
import { Request, RequestBody, RequestHeader, RequestParameter } from '../../../models/request';
import { deconstructQueryStringToParams } from '../../../utils/url/querystring';
import { SegmentEvent } from '../../analytics';
import { useRequestPatcher } from '../../hooks/use-request';
@ -217,104 +215,41 @@ export function newBodyGraphQL(rawBody: string): RequestBody {
}
}
}
export const updateMimeType = (
request: Request,
mimeType: string | null,
savedBody: RequestBody = {},
) => {
let headers = request.headers ? [...request.headers] : [];
const contentTypeHeader = getContentTypeHeader(headers);
// GraphQL uses JSON content-type
const contentTypeHeaderValue = mimeType === CONTENT_TYPE_GRAPHQL ? CONTENT_TYPE_JSON : mimeType;
// GraphQL must be POST
if (mimeType === CONTENT_TYPE_GRAPHQL) {
request.method = METHOD_POST;
}
// Check if we are converting to/from variants of XML or JSON
let leaveContentTypeAlone = false;
if (contentTypeHeader && mimeType) {
const current = contentTypeHeader.value;
if (current.includes('xml') && mimeType.includes('xml')) {
leaveContentTypeAlone = true;
} else if (current.includes('json') && mimeType.includes('json')) {
leaveContentTypeAlone = true;
}
}
const hasBody = typeof mimeType === 'string';
if (!hasBody) {
headers = headers.filter(h => h !== contentTypeHeader);
} else if (mimeType === CONTENT_TYPE_OTHER) {
// Leave headers alone
} else if (mimeType && contentTypeHeader && !leaveContentTypeAlone) {
contentTypeHeader.value = contentTypeHeaderValue || '';
} else if (mimeType && !contentTypeHeader) {
headers.push({
name: 'Content-Type',
value: contentTypeHeaderValue || '',
});
}
if (!headers.find(h => h?.name?.toLowerCase() === 'user-agent')) {
headers.push({
name: 'User-Agent',
value: `Insomnia/${version}`,
});
}
const oldBody = Object.keys(savedBody).length === 0 ? request.body : savedBody;
if (mimeType === CONTENT_TYPE_FORM_URLENCODED) {
): { body: RequestBody; headers: RequestHeader[]; params?: RequestParameter[]; method?: string } => {
const withoutContentType = request.headers.filter(h => h?.name?.toLowerCase() !== 'content-type');
// 'No body' selected
if (typeof mimeType !== 'string') {
return {
body: {
mimeType: CONTENT_TYPE_FORM_URLENCODED,
params: oldBody.params || (oldBody.text ? deconstructQueryStringToParams(oldBody.text) : []),
},
headers,
body: {},
headers: withoutContentType,
};
}
if (mimeType === CONTENT_TYPE_FORM_DATA) {
// Form Data
if (mimeType === CONTENT_TYPE_GRAPHQL) {
return {
body: oldBody.params
? {
mimeType: CONTENT_TYPE_FORM_DATA,
params: oldBody.params || [],
} : {
mimeType: CONTENT_TYPE_FORM_DATA,
params: oldBody.text ? deconstructQueryStringToParams(oldBody.text) : [],
},
headers,
body: newBodyGraphQL(request.body.text || ''),
headers: [{ name: 'Content-Type', value: CONTENT_TYPE_JSON }, ...withoutContentType],
method: METHOD_POST,
};
}
if (mimeType === CONTENT_TYPE_FORM_URLENCODED || mimeType === CONTENT_TYPE_FORM_DATA) {
const params = request.body.params || deconstructQueryStringToParams(request.body.text);
return {
body: { mimeType, params },
headers: [{ name: 'Content-Type', value: mimeType || '' }, ...withoutContentType],
};
}
if (mimeType === CONTENT_TYPE_FILE) {
return {
body: {
mimeType: CONTENT_TYPE_FILE,
fileName: '',
},
headers,
body: { mimeType, fileName: '' },
headers: [{ name: 'Content-Type', value: mimeType || '' }, ...withoutContentType],
};
}
if (mimeType === CONTENT_TYPE_GRAPHQL) {
if (contentTypeHeader) {
contentTypeHeader.value = CONTENT_TYPE_JSON;
}
return {
body: newBodyGraphQL(oldBody.text || ''),
headers,
};
}
if (typeof mimeType !== 'string') {
return {
body: {},
headers,
};
}
// Raw Content-Type (ex: application/json)
return {
body: typeof mimeType !== 'string' ? {
text: oldBody.text || '',
} : {
mimeType: mimeType.split(';')[0],
text: oldBody.text || '',
},
headers,
body: { mimeType: mimeType.split(';')[0], text: request.body.text || '' },
headers: [{ name: 'Content-Type', value: mimeType || '' }, ...withoutContentType],
};
};

View File

@ -184,7 +184,6 @@ export const RequestActionsDropdown = forwardRef<DropdownHandle, Props>(({
icon="copy"
label="Copy as cURL"
onClick={copyAsCurl}
withPrompt
/>
)}
</DropdownItem>

View File

@ -43,7 +43,7 @@ export const RequestGroupActionsDropdown = forwardRef<RequestGroupActionsDropdow
const { organizationId, projectId, workspaceId } = useParams() as { organizationId: string; projectId: string; workspaceId: string };
const create = useCallback((requestType: CreateRequestType) =>
requestFetcher.submit({ requestType, parentId: requestGroup._id },
requestFetcher.submit({ requestType, parentId: requestGroup._id, clipboardText: window.clipboard.readText() },
{
encType: 'application/json',
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/debug/request/new`,
@ -202,7 +202,16 @@ export const RequestGroupActionsDropdown = forwardRef<RequestGroupActionsDropdow
onClick={() => create('WebSocket')}
/>
</DropdownItem>
<DropdownSection>
<DropdownItem aria-label='From Curl'>
<ItemContent
icon="plus-circle"
label="From Curl"
onClick={() => create('From Curl')}
/>
</DropdownItem>
</DropdownSection>
<DropdownSection>
<DropdownItem aria-label='New Folder'>
<ItemContent
icon="folder"
@ -211,6 +220,7 @@ export const RequestGroupActionsDropdown = forwardRef<RequestGroupActionsDropdow
onClick={createGroup}
/>
</DropdownItem>
</DropdownSection>
<DropdownSection aria-label='Actions Section'>
<DropdownItem aria-label='Duplicate'>

View File

@ -8,7 +8,7 @@ import * as models from '../../../models';
import { queryAllWorkspaceUrls } from '../../../models/helpers/query-all-workspace-urls';
import type { Settings } from '../../../models/settings';
import { deconstructQueryStringToParams, extractQueryStringFromUrl } from '../../../utils/url/querystring';
import { useRequestPatcher, useSettingsPatcher } from '../../hooks/use-request';
import { useSettingsPatcher } from '../../hooks/use-request';
import { useActiveRequestSyncVCSVersion, useGitVCSVersion } from '../../hooks/use-vcs-version';
import { RequestLoaderData } from '../../routes/request';
import { WorkspaceLoaderData } from '../../routes/workspace';
@ -67,7 +67,6 @@ export const RequestPane: FC<Props> = ({
}) => {
const { activeRequest, activeRequestMeta } = useRouteLoaderData('request/:requestId') as RequestLoaderData;
const { workspaceId, requestId } = useParams() as { organizationId: string; projectId: string; workspaceId: string; requestId: string };
const patchRequest = useRequestPatcher();
const patchSettings = useSettingsPatcher();
const [isRequestSettingsModalOpen, setIsRequestSettingsModalOpen] = useState(false);
@ -131,7 +130,6 @@ export const RequestPane: FC<Props> = ({
key={requestId}
ref={requestUrlBarRef}
uniquenessKey={uniqueKey}
onUrlChange={url => patchRequest(requestId, { url })}
handleAutocompleteUrls={() => queryAllWorkspaceUrls(workspaceId, models.request.type, requestId)}
nunjucksPowerUserMode={settings.nunjucksPowerUserMode}
setLoading={setLoading}

View File

@ -4,12 +4,10 @@ import { useFetcher, useParams, useRouteLoaderData } from 'react-router-dom';
import { useInterval } from 'react-use';
import styled from 'styled-components';
import { database } from '../../common/database';
import { RENDER_PURPOSE_SEND } from '../../common/render';
import * as models from '../../models';
import { isEventStreamRequest, isRequest } from '../../models/request';
import { isEventStreamRequest } from '../../models/request';
import { fetchRequestData, tryToInterpolateRequest, tryToTransformRequestWithPlugins } from '../../network/network';
import { convert } from '../../utils/importers/convert';
import { tryToInterpolateRequestOrShowRenderErrorModal } from '../../utils/try-interpolate';
import { buildQueryStringFromParams, joinUrlAndQueryString } from '../../utils/url/querystring';
import { SegmentEvent } from '../analytics';
@ -40,7 +38,6 @@ const StyledDropdownButton = styled(DropdownButton)({
interface Props {
handleAutocompleteUrls: () => Promise<string[]>;
nunjucksPowerUserMode: boolean;
onUrlChange: (url: string) => void;
uniquenessKey: string;
setLoading: (l: boolean) => void;
}
@ -51,7 +48,6 @@ export interface RequestUrlBarHandle {
export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(({
handleAutocompleteUrls,
onUrlChange,
uniquenessKey,
setLoading,
}, ref) => {
@ -194,56 +190,6 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(({
},
});
const lastPastedTextRef = useRef('');
const handleImport = useCallback(async (text: string) => {
// Allow user to paste any import file into the url. If it results in
// only one item, it will overwrite the current request.
try {
const { data } = await convert(text);
const { resources } = data;
const r = resources[0];
if (r && r._type === 'request' && activeRequest && isRequest(activeRequest)) {
// Only pull fields that we want to update
return database.update({
...activeRequest,
modified: Date.now(),
url: r.url,
method: r.method,
headers: r.headers,
body: r.body,
authentication: r.authentication,
parameters: r.parameters,
}, true); // Pass true to indicate that this is an import
}
} catch (error) {
// Import failed, that's alright
console.error(error);
}
return null;
}, [activeRequest]);
const handleUrlChange = useCallback(async (url: string) => {
const pastedText = lastPastedTextRef.current;
// If no pasted text in the queue, just fire the regular change handler
if (!pastedText) {
onUrlChange(url);
return;
}
// Reset pasted text cache
lastPastedTextRef.current = '';
// Attempt to import the pasted text
const importedRequest = await handleImport(pastedText);
// Update depending on whether something was imported
if (!importedRequest) {
onUrlChange(url);
}
}, [handleImport, onUrlChange]);
const handleUrlPaste = useCallback((event: ClipboardEvent) => {
// NOTE: We're not actually doing the import here to avoid races with onChange
lastPastedTextRef.current = event.clipboardData?.getData('text/plain') || '';
}, []);
const handleSendDropdownHide = useCallback(() => {
buttonRef.current?.blur();
}, []);
@ -263,12 +209,11 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(({
id="request-url-bar"
key={uniquenessKey}
ref={inputRef}
onPaste={handleUrlPaste}
type="text"
getAutocompleteConstants={handleAutocompleteUrls}
placeholder="https://api.myproduct.com/v1/users"
defaultValue={url}
onChange={handleUrlChange}
onChange={url => patchRequest(requestId, { url })}
onKeyDown={createKeybindingsHandler({
'Enter': () => sendOrConnect(),
})}

View File

@ -103,7 +103,6 @@ export const ImportExport: FC<Props> = ({ hideSettingsModal }) => {
Create Run Button
</Link>
</div>
<p className="italic faint">* Tip: You can also paste Curl commands into the URL bar</p>
</div>
{isImportModalOpen && (
<ImportModal

View File

@ -4,7 +4,7 @@ import { useRouteLoaderData } from 'react-router-dom';
import { CreateRequestType } from '../../hooks/use-request';
import { RootLoaderData } from '../../routes/root';
import { Dropdown, DropdownButton, DropdownItem, ItemContent } from '../base/dropdown';
import { Dropdown, DropdownButton, DropdownItem, DropdownSection, ItemContent } from '../base/dropdown';
import { showPrompt } from '../modals';
export const SidebarCreateDropdown = () => {
@ -15,7 +15,7 @@ export const SidebarCreateDropdown = () => {
const requestFetcher = useFetcher();
const { organizationId, projectId, workspaceId } = useParams() as { organizationId: string; projectId: string; workspaceId: string };
const create = useCallback((requestType: CreateRequestType) =>
requestFetcher.submit({ requestType, parentId: workspaceId },
requestFetcher.submit({ requestType, parentId: workspaceId, clipboardText: window.clipboard.readText() },
{
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/debug/request/new`,
method: 'post',
@ -91,7 +91,16 @@ export const SidebarCreateDropdown = () => {
onClick={() => create('WebSocket')}
/>
</DropdownItem>
<DropdownSection>
<DropdownItem aria-label='From Curl'>
<ItemContent
icon="plus-circle"
label="From Curl"
onClick={() => create('From Curl')}
/>
</DropdownItem>
</DropdownSection>
<DropdownSection>
<DropdownItem aria-label='New Folder'>
<ItemContent
icon="folder"
@ -100,6 +109,7 @@ export const SidebarCreateDropdown = () => {
onClick={createGroup}
/>
</DropdownItem>
</DropdownSection>
</Dropdown>
);
};

View File

@ -82,4 +82,4 @@ export const useWorkspaceMetaPatcher = () => {
};
};
export type CreateRequestType = 'HTTP' | 'gRPC' | 'GraphQL' | 'WebSocket' | 'Event Stream';
export type CreateRequestType = 'HTTP' | 'gRPC' | 'GraphQL' | 'WebSocket' | 'Event Stream' | 'From Curl';

View File

@ -5,6 +5,7 @@ import * as contentDisposition from 'content-disposition';
import { extension as mimeExtension } from 'mime-types';
import { ActionFunction, LoaderFunction, redirect } from 'react-router-dom';
import { version } from '../../../package.json';
import { CONTENT_TYPE_EVENT_STREAM, CONTENT_TYPE_GRAPHQL, CONTENT_TYPE_JSON, METHOD_GET, METHOD_POST } from '../../common/constants';
import { ChangeBufferEvent, database } from '../../common/database';
import { getContentDispositionHeader } from '../../common/misc';
@ -16,16 +17,18 @@ import { CookieJar } from '../../models/cookie-jar';
import { GrpcRequest, isGrpcRequestId } from '../../models/grpc-request';
import { GrpcRequestMeta } from '../../models/grpc-request-meta';
import * as requestOperations from '../../models/helpers/request-operations';
import { isEventStreamRequest, isRequest, Request, RequestAuthentication, RequestHeader } from '../../models/request';
import { isEventStreamRequest, isRequest, Request, RequestAuthentication, RequestBody, RequestHeader, RequestParameter } from '../../models/request';
import { isRequestMeta, RequestMeta } from '../../models/request-meta';
import { RequestVersion } from '../../models/request-version';
import { Response } from '../../models/response';
import { isWebSocketRequestId, WebSocketRequest } from '../../models/websocket-request';
import { WebSocketResponse } from '../../models/websocket-response';
import { fetchRequestData, responseTransform, sendCurlAndWriteTimeline, tryToInterpolateRequest } from '../../network/network';
import { convert } from '../../utils/importers/convert';
import { invariant } from '../../utils/invariant';
import { SegmentEvent } from '../analytics';
import { updateMimeType } from '../components/dropdowns/content-type-dropdown';
import { CreateRequestType } from '../hooks/use-request';
export interface WebSocketRequestLoaderData {
activeRequest: WebSocketRequest;
@ -93,7 +96,7 @@ export const loader: LoaderFunction = async ({ params }): Promise<RequestLoaderD
export const createRequestAction: ActionFunction = async ({ request, params }) => {
const { organizationId, projectId, workspaceId } = params;
invariant(typeof workspaceId === 'string', 'Workspace ID is required');
const { requestType, parentId } = await request.json();
const { requestType, parentId, clipboardText } = await request.json() as { requestType: CreateRequestType; parentId?: string; clipboardText?: string };
let activeRequestId;
if (requestType === 'HTTP') {
@ -101,6 +104,7 @@ export const createRequestAction: ActionFunction = async ({ request, params }) =
parentId: parentId || workspaceId,
method: METHOD_GET,
name: 'New Request',
headers: [{ name: 'User-Agent', value: `Insomnia/${version}` }],
}))._id;
}
if (requestType === 'gRPC') {
@ -114,10 +118,8 @@ export const createRequestAction: ActionFunction = async ({ request, params }) =
parentId: parentId || workspaceId,
method: METHOD_POST,
headers: [
{
name: 'Content-Type',
value: CONTENT_TYPE_JSON,
},
{ name: 'User-Agent', value: `Insomnia/${version}` },
{ name: 'Content-Type', value: CONTENT_TYPE_JSON },
],
body: {
mimeType: CONTENT_TYPE_GRAPHQL,
@ -132,10 +134,8 @@ export const createRequestAction: ActionFunction = async ({ request, params }) =
method: METHOD_GET,
url: '',
headers: [
{
name: 'Accept',
value: CONTENT_TYPE_EVENT_STREAM,
},
{ name: 'User-Agent', value: `Insomnia/${version}` },
{ name: 'Accept', value: CONTENT_TYPE_EVENT_STREAM },
],
name: 'New Event Stream',
}))._id;
@ -144,8 +144,33 @@ export const createRequestAction: ActionFunction = async ({ request, params }) =
activeRequestId = (await models.webSocketRequest.create({
parentId: parentId || workspaceId,
name: 'New WebSocket Request',
headers: [{ name: 'User-Agent', value: `Insomnia/${version}` }],
}))._id;
}
if (requestType === 'From Curl') {
// TODO: if no clipboard text show modal
if (!clipboardText) {
return null;
}
try {
const { data } = await convert(clipboardText);
const { resources } = data;
const r = resources[0];
activeRequestId = (await models.request.create({
parentId: parentId || workspaceId,
url: r.url,
method: r.method,
headers: r.headers,
body: r.body as RequestBody,
authentication: r.authentication,
parameters: r.parameters as RequestParameter[],
}))._id;
} catch (error) {
console.error(error);
return null;
}
}
invariant(typeof activeRequestId === 'string', 'Request ID is required');
models.stats.incrementCreatedRequests();
window.main.trackSegmentEvent({ event: SegmentEvent.requestCreate, properties: { requestType } });
@ -155,21 +180,17 @@ export const createRequestAction: ActionFunction = async ({ request, params }) =
export const updateRequestAction: ActionFunction = async ({ request, params }) => {
const { requestId } = params;
invariant(typeof requestId === 'string', 'Request ID is required');
let req = await requestOperations.getById(requestId);
const req = await requestOperations.getById(requestId);
invariant(req, 'Request not found');
let patch = await request.json();
const patch = await request.json();
// TODO: if gRPC, we should also copy the protofile to the destination workspace - INS-267
if (isRequest(req) && patch.body) {
const mimeType = patch.body?.mimeType as string | null;
const requestMeta = await models.requestMeta.getOrCreateByParentId(requestId);
const savedRequestBody = !mimeType ? (req.body || {}) : {};
await models.requestMeta.update(requestMeta, { savedRequestBody });
// TODO: make this less hacky, update expects latest req not patch
req = await requestOperations.update(req, patch);
patch = updateMimeType(req, mimeType, requestMeta.savedRequestBody);
const isMimeTypeChanged = isRequest(req) && patch.body && patch.body.mimeType !== req.body.mimeType;
if (isMimeTypeChanged) {
await requestOperations.update(req, { ...patch, ...updateMimeType(req, patch.body?.mimeType) });
return null;
}
requestOperations.update(req, patch);
await requestOperations.update(req, patch);
return null;
};

View File

@ -113,7 +113,7 @@ export const buildQueryStringFromParams = (
* @returns {{name: string, value: string}[]}
*/
export const deconstructQueryStringToParams = (
qs: string,
qs?: string,
/** allow empty names and values */
strict?: boolean,