From f30b4f3a140f4a6219c417b905c81be456fee183 Mon Sep 17 00:00:00 2001 From: Paulina Lukasik Date: Mon, 18 Nov 2024 13:12:07 +0100 Subject: [PATCH 1/5] feat: Add support for application octet-stream --- .../RequestSamples/convertRequestToSample.ts | 3 ++ .../components/TryIt/Body/FormDataBody.tsx | 12 +++++++ .../TryIt/Body/request-body-utils.ts | 23 +++++++++--- .../src/components/TryIt/TryIt.tsx | 13 ++++--- .../src/components/TryIt/build-request.ts | 36 +++++++++++-------- 5 files changed, 65 insertions(+), 22 deletions(-) diff --git a/packages/elements-core/src/components/RequestSamples/convertRequestToSample.ts b/packages/elements-core/src/components/RequestSamples/convertRequestToSample.ts index 92661c07c..ef21c757b 100644 --- a/packages/elements-core/src/components/RequestSamples/convertRequestToSample.ts +++ b/packages/elements-core/src/components/RequestSamples/convertRequestToSample.ts @@ -18,6 +18,9 @@ export const convertRequestToSample = async ( } if (typeof converted === 'string') { converted = converted.replace(/%7B/g, '{').replace(/%7D/g, '}'); + if (request.postData?.mimeType === 'application/octet-stream') { + converted = converted.replace('--data', '--data-binary'); + } } return converted; diff --git a/packages/elements-core/src/components/TryIt/Body/FormDataBody.tsx b/packages/elements-core/src/components/TryIt/Body/FormDataBody.tsx index a2ef99819..3c7fc108a 100644 --- a/packages/elements-core/src/components/TryIt/Body/FormDataBody.tsx +++ b/packages/elements-core/src/components/TryIt/Body/FormDataBody.tsx @@ -89,6 +89,18 @@ export const FormDataBody: React.FC = ({ /> ); })} + {specification.mediaType === 'application/octet-stream' && ( + { + { + onChangeValues(newValue as any); + } + }} + /> + )} ); diff --git a/packages/elements-core/src/components/TryIt/Body/request-body-utils.ts b/packages/elements-core/src/components/TryIt/Body/request-body-utils.ts index d728aad0b..bfc20849b 100644 --- a/packages/elements-core/src/components/TryIt/Body/request-body-utils.ts +++ b/packages/elements-core/src/components/TryIt/Body/request-body-utils.ts @@ -23,6 +23,12 @@ function isMultipartContent(content: IMediaTypeContent) { return content.mediaType.toLowerCase() === 'multipart/form-data'; } +export const isBinaryContent = (content: IMediaTypeContent) => isApplicationOctetStream(content); + +function isApplicationOctetStream(content: IMediaTypeContent) { + return content.mediaType.toLowerCase() === 'application/octet-stream'; +} + export async function createRequestBody( mediaTypeContent: IMediaTypeContent | undefined, bodyParameterValues: BodyParameterValues | undefined, @@ -74,16 +80,17 @@ const requestBodyCreators: Record = { export const useBodyParameterState = (mediaTypeContent: IMediaTypeContent | undefined) => { const isFormDataBody = mediaTypeContent && isFormDataContent(mediaTypeContent); + const isOctetStreamBody = mediaTypeContent && isApplicationOctetStream(mediaTypeContent); const initialState = React.useMemo(() => { - if (!isFormDataBody) { + if (!isFormDataBody || isOctetStreamBody) { return {}; } const properties = mediaTypeContent?.schema?.properties ?? {}; const required = mediaTypeContent?.schema?.required; const parameters = mapSchemaPropertiesToParameters(properties, required); return initialParameterValues(parameters); - }, [isFormDataBody, mediaTypeContent]); + }, [isFormDataBody, isOctetStreamBody, mediaTypeContent]); const [bodyParameterValues, setBodyParameterValues] = React.useState(initialState); const [isAllowedEmptyValue, setAllowedEmptyValue] = React.useState({}); @@ -98,7 +105,15 @@ export const useBodyParameterState = (mediaTypeContent: IMediaTypeContent | unde setBodyParameterValues, isAllowedEmptyValue, setAllowedEmptyValue, - { isFormDataBody: true, bodySpecification: mediaTypeContent! }, + { isFormDataBody: true, isOctetStreamBody: false, bodySpecification: mediaTypeContent! }, + ] as const; + } else if (isOctetStreamBody) { + return [ + bodyParameterValues, + setBodyParameterValues, + isAllowedEmptyValue, + setAllowedEmptyValue, + { isFormDataBody: false, isOctetStreamBody: true, bodySpecification: mediaTypeContent! }, ] as const; } else { return [ @@ -106,7 +121,7 @@ export const useBodyParameterState = (mediaTypeContent: IMediaTypeContent | unde setBodyParameterValues, isAllowedEmptyValue, setAllowedEmptyValue, - { isFormDataBody: false, bodySpecification: undefined }, + { isFormDataBody: false, isOctetStreamBody: false, bodySpecification: undefined }, ] as const; } }; diff --git a/packages/elements-core/src/components/TryIt/TryIt.tsx b/packages/elements-core/src/components/TryIt/TryIt.tsx index 96c58ef59..147006ca3 100644 --- a/packages/elements-core/src/components/TryIt/TryIt.tsx +++ b/packages/elements-core/src/components/TryIt/TryIt.tsx @@ -11,7 +11,7 @@ import { extractCodeSamples, RequestSamples } from '../RequestSamples'; import { TryItAuth } from './Auth/Auth'; import { usePersistedSecuritySchemeWithValues } from './Auth/authentication-utils'; import { FormDataBody } from './Body/FormDataBody'; -import { BodyParameterValues, useBodyParameterState } from './Body/request-body-utils'; +import { BodyParameterValues, isBinaryContent, useBodyParameterState } from './Body/request-body-utils'; import { RequestBody } from './Body/RequestBody'; import { useTextRequestBodyState } from './Body/useTextRequestBodyState'; import { buildFetchRequest, buildHarRequest } from './build-request'; @@ -91,6 +91,7 @@ export const TryIt: React.FC = ({ const [validateParameters, setValidateParameters] = React.useState(false); const mediaTypeContent = httpOperation.request?.body?.contents?.[requestBodyIndex ?? 0]; + const isBinaryContentEl = mediaTypeContent ? isBinaryContent(mediaTypeContent) : false; const { allParameters, updateParameterValue, parameterValuesWithDefaults } = useRequestParameters(httpOperation); const [mockingOptions, setMockingOptions] = useMockingOptions(); @@ -145,7 +146,11 @@ export const TryIt: React.FC = ({ parameterValues: parameterValuesWithDefaults, serverVariableValues, httpOperation, - bodyInput: formDataState.isFormDataBody ? getValues() : textRequestBody, + bodyInput: formDataState.isFormDataBody + ? getValues() + : formDataState.isOctetStreamBody + ? bodyParameterValues + : textRequestBody, auth: operationAuthValue, ...(isMockingEnabled && { mockData: getMockData(mockUrl, httpOperation, mockingOptions) }), chosenServer, @@ -261,9 +266,9 @@ export const TryIt: React.FC = ({ )} - {formDataState.isFormDataBody ? ( + {formDataState.isFormDataBody || isBinaryContentEl ? ( { - if (value instanceof File) { + + if (shouldIncludeBody) { + if (bodyInput instanceof File) { + postData = { + mimeType: 'application/octet-stream', + text: `@${bodyInput.name}`, + }; + } else if (typeof bodyInput === 'object') { + postData = { + mimeType, + params: Object.entries(bodyInput).map(([name, value]) => { + if (value instanceof File) { + return { + name, + fileName: value.name, + contentType: value.type, + }; + } return { name, - fileName: value.name, - contentType: value.type, + value, }; - } - return { - name, - value, - }; - }), - }; + }), + }; + } } return { From 6ecc1d11252a7f5d10ed0aa622661add95cfbcf2 Mon Sep 17 00:00:00 2001 From: Paulina Lukasik Date: Tue, 19 Nov 2024 14:16:11 +0100 Subject: [PATCH 2/5] feat: Improve types and add unit tests --- .../application-octet-stream-post.ts | 39 ++++++++++++++ .../convertRequestToSample.spec.ts.snap | 7 +++ .../__tests__/convertRequestToSample.spec.ts | 24 +++++++++ .../src/components/TryIt/Body/BinaryBody.tsx | 54 +++++++++++++++++++ .../components/TryIt/Body/FormDataBody.tsx | 12 ----- .../TryIt/Body/__tests__/BinaryBody.test.tsx | 32 +++++++++++ .../TryIt/Body/request-body-utils.ts | 14 ++--- .../src/components/TryIt/TryIt.spec.tsx | 45 ++++++++++++++++ .../src/components/TryIt/TryIt.tsx | 27 +++++++--- .../src/components/TryIt/build-request.ts | 49 +++++++++-------- 10 files changed, 256 insertions(+), 47 deletions(-) create mode 100644 packages/elements-core/src/__fixtures__/operations/application-octet-stream-post.ts create mode 100644 packages/elements-core/src/components/TryIt/Body/BinaryBody.tsx create mode 100644 packages/elements-core/src/components/TryIt/Body/__tests__/BinaryBody.test.tsx diff --git a/packages/elements-core/src/__fixtures__/operations/application-octet-stream-post.ts b/packages/elements-core/src/__fixtures__/operations/application-octet-stream-post.ts new file mode 100644 index 000000000..8d3de2126 --- /dev/null +++ b/packages/elements-core/src/__fixtures__/operations/application-octet-stream-post.ts @@ -0,0 +1,39 @@ +import { IHttpOperation } from '@stoplight/types'; + +export const httpOperation: IHttpOperation = { + id: '?http-operation-id?', + iid: 'POST_todos', + method: 'post', + path: '/todos', + summary: 'Upload File', + responses: [ + { + id: '?http-response-200?', + code: '200', + }, + ], + servers: [ + { + id: '?http-server-todos.stoplight.io?', + url: 'https://todos.stoplight.io', + }, + ], + request: { + body: { + id: '?http-request-body?', + contents: [ + { + id: '?http-media-0?', + mediaType: 'application/octet-stream', + schema: { + type: 'string', + format: 'binary', + $schema: 'http://json-schema.org/draft-07/schema#', + }, + }, + ], + }, + }, +}; + +export default httpOperation; diff --git a/packages/elements-core/src/components/RequestSamples/__tests__/__snapshots__/convertRequestToSample.spec.ts.snap b/packages/elements-core/src/components/RequestSamples/__tests__/__snapshots__/convertRequestToSample.spec.ts.snap index afb28b294..f4ba394f3 100644 --- a/packages/elements-core/src/components/RequestSamples/__tests__/__snapshots__/convertRequestToSample.spec.ts.snap +++ b/packages/elements-core/src/components/RequestSamples/__tests__/__snapshots__/convertRequestToSample.spec.ts.snap @@ -1,5 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`convertRequestToSample converts an application/octet-stream request to a sample 1`] = ` +"curl --request POST \\\\ + --url https://todos.stoplight.io/todos/todoId \\\\ + --header 'Content-Type: application/octet-stream' \\\\ + --data-binary 'binary data'" +`; + exports[`given c, convertRequestToSample converts a request to a sample 1`] = ` "CURL *hnd = curl_easy_init(); diff --git a/packages/elements-core/src/components/RequestSamples/__tests__/convertRequestToSample.spec.ts b/packages/elements-core/src/components/RequestSamples/__tests__/convertRequestToSample.spec.ts index 1f1ee5ba7..3e6ae293e 100644 --- a/packages/elements-core/src/components/RequestSamples/__tests__/convertRequestToSample.spec.ts +++ b/packages/elements-core/src/components/RequestSamples/__tests__/convertRequestToSample.spec.ts @@ -67,3 +67,27 @@ test.each(languages)('given %s, convertRequestToSample converts a request to a s expect(convertRequestToSample(language, library, har)).resolves.toMatchSnapshot(); }); + +test('convertRequestToSample converts an application/octet-stream request to a sample', async () => { + const har = { + method: 'POST', + url: 'https://todos.stoplight.io/todos/todoId', + httpVersion: 'HTTP/1.1', + cookies: [], + headers: [ + { + name: 'Content-Type', + value: 'application/octet-stream', + }, + ], + queryString: [], + postData: { + mimeType: 'application/octet-stream', + text: 'binary data', + }, + headersSize: -1, + bodySize: -1, + }; + + await expect(convertRequestToSample('shell', 'curl', har)).resolves.toMatchSnapshot(); +}); diff --git a/packages/elements-core/src/components/TryIt/Body/BinaryBody.tsx b/packages/elements-core/src/components/TryIt/Body/BinaryBody.tsx new file mode 100644 index 000000000..9871df3b1 --- /dev/null +++ b/packages/elements-core/src/components/TryIt/Body/BinaryBody.tsx @@ -0,0 +1,54 @@ +import { SchemaTree } from '@stoplight/json-schema-tree'; +import { Choice, useChoices } from '@stoplight/json-schema-viewer'; +import { Panel } from '@stoplight/mosaic'; +import { IMediaTypeContent } from '@stoplight/types'; +import * as React from 'react'; + +import { FileUploadParameterEditor } from '../Parameters/FileUploadParameterEditors'; +import { OneOfMenu } from './FormDataBody'; +import { BodyParameterValues } from './request-body-utils'; + +export interface BinaryBodyProps { + specification?: IMediaTypeContent; + values: BodyParameterValues; + onChangeValues: (newValues: BodyParameterValues) => void; +} + +export const BinaryBody: React.FC = ({ specification, values, onChangeValues }) => { + const schema: any = React.useMemo(() => { + const schema = specification?.schema ?? {}; + const tree = new SchemaTree(schema, { mergeAllOf: true, refResolver: null }); + tree.populate(); + return tree.root.children[0]; + }, [specification]); + + const { selectedChoice, choices, setSelectedChoice } = useChoices(schema); + + const onSchemaChange = (choice: Choice) => { + // Erase existing values; the old and new schemas may have nothing in common. + onChangeValues({}); + setSelectedChoice(choice); + }; + + return ( + + } + > + Body + + + { + { + newValue && onChangeValues({ file: newValue }); + } + }} + /> + + + ); +}; diff --git a/packages/elements-core/src/components/TryIt/Body/FormDataBody.tsx b/packages/elements-core/src/components/TryIt/Body/FormDataBody.tsx index 3c7fc108a..a2ef99819 100644 --- a/packages/elements-core/src/components/TryIt/Body/FormDataBody.tsx +++ b/packages/elements-core/src/components/TryIt/Body/FormDataBody.tsx @@ -89,18 +89,6 @@ export const FormDataBody: React.FC = ({ /> ); })} - {specification.mediaType === 'application/octet-stream' && ( - { - { - onChangeValues(newValue as any); - } - }} - /> - )} ); diff --git a/packages/elements-core/src/components/TryIt/Body/__tests__/BinaryBody.test.tsx b/packages/elements-core/src/components/TryIt/Body/__tests__/BinaryBody.test.tsx new file mode 100644 index 000000000..9d2ce024e --- /dev/null +++ b/packages/elements-core/src/components/TryIt/Body/__tests__/BinaryBody.test.tsx @@ -0,0 +1,32 @@ +import '@testing-library/jest-dom'; + +import { render } from '@testing-library/react'; +import React from 'react'; + +import { BinaryBody, BinaryBodyProps } from '../BinaryBody'; + +describe('BinaryBody', () => { + it('renders filr input when the form is application/octet-stream', () => { + // Arrange + const props: BinaryBodyProps = { + specification: { + id: '493afac014fa8', + mediaType: 'application/octet-stream', + encodings: [], + schema: { + type: 'string', + format: 'binary', + $schema: 'http://json-schema.org/draft-07/schema#', + }, + }, + values: {}, + onChangeValues: () => {}, + }; + + // Act + const { getByLabelText } = render(); + + // Assert + expect(getByLabelText('file')).toBeInTheDocument(); + }); +}); diff --git a/packages/elements-core/src/components/TryIt/Body/request-body-utils.ts b/packages/elements-core/src/components/TryIt/Body/request-body-utils.ts index bfc20849b..f1d6748de 100644 --- a/packages/elements-core/src/components/TryIt/Body/request-body-utils.ts +++ b/packages/elements-core/src/components/TryIt/Body/request-body-utils.ts @@ -80,17 +80,17 @@ const requestBodyCreators: Record = { export const useBodyParameterState = (mediaTypeContent: IMediaTypeContent | undefined) => { const isFormDataBody = mediaTypeContent && isFormDataContent(mediaTypeContent); - const isOctetStreamBody = mediaTypeContent && isApplicationOctetStream(mediaTypeContent); + const isBinaryBody = mediaTypeContent && isBinaryContent(mediaTypeContent); const initialState = React.useMemo(() => { - if (!isFormDataBody || isOctetStreamBody) { + if (!isFormDataBody || isBinaryBody) { return {}; } const properties = mediaTypeContent?.schema?.properties ?? {}; const required = mediaTypeContent?.schema?.required; const parameters = mapSchemaPropertiesToParameters(properties, required); return initialParameterValues(parameters); - }, [isFormDataBody, isOctetStreamBody, mediaTypeContent]); + }, [isFormDataBody, isBinaryBody, mediaTypeContent]); const [bodyParameterValues, setBodyParameterValues] = React.useState(initialState); const [isAllowedEmptyValue, setAllowedEmptyValue] = React.useState({}); @@ -105,15 +105,15 @@ export const useBodyParameterState = (mediaTypeContent: IMediaTypeContent | unde setBodyParameterValues, isAllowedEmptyValue, setAllowedEmptyValue, - { isFormDataBody: true, isOctetStreamBody: false, bodySpecification: mediaTypeContent! }, + { isFormDataBody: true, isBinaryBody: false, bodySpecification: mediaTypeContent! }, ] as const; - } else if (isOctetStreamBody) { + } else if (isBinaryBody) { return [ bodyParameterValues, setBodyParameterValues, isAllowedEmptyValue, setAllowedEmptyValue, - { isFormDataBody: false, isOctetStreamBody: true, bodySpecification: mediaTypeContent! }, + { isFormDataBody: false, isBinaryBody: true, bodySpecification: mediaTypeContent! }, ] as const; } else { return [ @@ -121,7 +121,7 @@ export const useBodyParameterState = (mediaTypeContent: IMediaTypeContent | unde setBodyParameterValues, isAllowedEmptyValue, setAllowedEmptyValue, - { isFormDataBody: false, isOctetStreamBody: false, bodySpecification: undefined }, + { isFormDataBody: false, isBinaryBody: false, bodySpecification: undefined }, ] as const; } }; diff --git a/packages/elements-core/src/components/TryIt/TryIt.spec.tsx b/packages/elements-core/src/components/TryIt/TryIt.spec.tsx index 162d8cc64..372172f5f 100644 --- a/packages/elements-core/src/components/TryIt/TryIt.spec.tsx +++ b/packages/elements-core/src/components/TryIt/TryIt.spec.tsx @@ -7,6 +7,7 @@ import userEvent from '@testing-library/user-event'; import fetchMock from 'jest-fetch-mock'; import * as React from 'react'; +import { httpOperation as octetStreamOperation } from '../../__fixtures__/operations/application-octet-stream-post'; import { httpOperation as base64FileUpload } from '../../__fixtures__/operations/base64-file-upload'; import { examplesRequestBody, singleExampleRequestBody } from '../../__fixtures__/operations/examples-request-body'; import { headWithRequestBody } from '../../__fixtures__/operations/head-todos'; @@ -533,6 +534,50 @@ describe('TryIt', () => { }); }); + describe('Binary body', () => { + it('shows panel when there is file input', () => { + render(); + + let parametersHeader = screen.queryByText('Body'); + expect(parametersHeader).toBeInTheDocument(); + }); + + it('displays file input correctly', () => { + render(); + + const fileField = screen.getByRole('textbox', { name: 'file' }) as HTMLInputElement; + + expect(fileField.placeholder).toMatch(/pick a file/i); + }); + + it('builds correct application/octet-stream request and send file in the body', async () => { + render(); + + userEvent.upload(screen.getByLabelText('Upload'), new File(['something'], 'some-file')); + + clickSend(); + await waitFor(() => expect(fetchMock).toHaveBeenCalled()); + + const request = fetchMock.mock.calls[0]; + const requestBody = request[1]!.body as File; + const headers = new Headers(fetchMock.mock.calls[0][1]!.headers); + + expect(requestBody).toBeInstanceOf(File); + expect(requestBody.name).toBe('some-file'); + expect(headers.get('Content-Type')).toBe('application/octet-stream'); + }); + + it('allows to send empty value', async () => { + render(); + + clickSend(); + await waitFor(() => expect(fetchMock).toHaveBeenCalled()); + + const body = fetchMock.mock.calls[0][1]!.body as FormData; + expect(body).toBeUndefined(); + }); + }); + describe('Text Request Body', () => { describe('is attached', () => { it('to operation with PATCH method', async () => { diff --git a/packages/elements-core/src/components/TryIt/TryIt.tsx b/packages/elements-core/src/components/TryIt/TryIt.tsx index 954f6e36b..1d31e2678 100644 --- a/packages/elements-core/src/components/TryIt/TryIt.tsx +++ b/packages/elements-core/src/components/TryIt/TryIt.tsx @@ -10,8 +10,9 @@ import { getServersToDisplay, getServerVariables } from '../../utils/http-spec/I import { extractCodeSamples, RequestSamples } from '../RequestSamples'; import { TryItAuth } from './Auth/Auth'; import { usePersistedSecuritySchemeWithValues } from './Auth/authentication-utils'; +import { BinaryBody } from './Body/BinaryBody'; import { FormDataBody } from './Body/FormDataBody'; -import { BodyParameterValues, isBinaryContent, useBodyParameterState } from './Body/request-body-utils'; +import { BodyParameterValues, useBodyParameterState } from './Body/request-body-utils'; import { RequestBody } from './Body/RequestBody'; import { useTextRequestBodyState } from './Body/useTextRequestBodyState'; import { buildFetchRequest, buildHarRequest } from './build-request'; @@ -97,7 +98,6 @@ export const TryIt: React.FC = ({ const [validateParameters, setValidateParameters] = React.useState(false); const mediaTypeContent = httpOperation.request?.body?.contents?.[requestBodyIndex ?? 0]; - const isBinaryContentEl = mediaTypeContent ? isBinaryContent(mediaTypeContent) : false; const { allParameters, updateParameterValue, parameterValuesWithDefaults } = useRequestParameters(httpOperation); const [mockingOptions, setMockingOptions] = useMockingOptions(); @@ -135,6 +135,8 @@ export const TryIt: React.FC = ({ return previousValue; }, {}); + const getBinaryValue = () => bodyParameterValues.file; + React.useEffect(() => { const currentUrl = chosenServer?.url; @@ -157,8 +159,8 @@ export const TryIt: React.FC = ({ httpOperation, bodyInput: formDataState.isFormDataBody ? getValues() - : formDataState.isOctetStreamBody - ? bodyParameterValues + : formDataState.isBinaryBody + ? getBinaryValue() : textRequestBody, auth: operationAuthValue, ...(isMockingEnabled && { mockData: getMockData(mockUrl, httpOperation, mockingOptions) }), @@ -203,12 +205,17 @@ export const TryIt: React.FC = ({ try { setLoading(true); const mockData = isMockingEnabled ? getMockData(mockUrl, httpOperation, mockingOptions) : undefined; + const request = await buildFetchRequest({ parameterValues: parameterValuesWithDefaults, serverVariableValues, httpOperation, mediaTypeContent, - bodyInput: formDataState.isFormDataBody ? getValues() : textRequestBody, + bodyInput: formDataState.isFormDataBody + ? getValues() + : formDataState.isBinaryBody + ? getBinaryValue() + : textRequestBody, mockData, auth: operationAuthValue, chosenServer, @@ -275,14 +282,20 @@ export const TryIt: React.FC = ({ )} - {formDataState.isFormDataBody || isBinaryContentEl ? ( + {formDataState.isFormDataBody ? ( + ) : formDataState.isBinaryBody ? ( + ) : mediaTypeContent ? ( ; serverVariableValues: Dictionary; - bodyInput?: BodyParameterValues | string; + bodyInput?: BodyParameterValues | string | File; mockData?: MockData; auth?: HttpSecuritySchemeWithValues[]; chosenServer?: IServer | null; @@ -163,7 +163,12 @@ export async function buildFetchRequest({ const urlObject = new URL(serverUrl + expandedPath); urlObject.search = new URLSearchParams(queryParamsWithAuth.map(nameAndValueObjectToPair)).toString(); - const body = typeof bodyInput === 'object' ? await createRequestBody(mediaTypeContent, bodyInput) : bodyInput; + const body = + bodyInput instanceof File + ? bodyInput + : typeof bodyInput === 'object' + ? await createRequestBody(mediaTypeContent, bodyInput) + : bodyInput; const acceptedMimeTypes = getAcceptedMimeTypes(httpOperation); const headers = { @@ -292,28 +297,30 @@ export async function buildHarRequest({ } if (shouldIncludeBody) { - if (bodyInput instanceof File) { - postData = { - mimeType: 'application/octet-stream', - text: `@${bodyInput.name}`, - }; - } else if (typeof bodyInput === 'object') { - postData = { - mimeType, - params: Object.entries(bodyInput).map(([name, value]) => { - if (value instanceof File) { + if (typeof bodyInput === 'object') { + if (mimeType === 'application/octet-stream' && bodyInput instanceof File) { + postData = { + mimeType, + text: `@${bodyInput.name}`, + }; + } else { + postData = { + mimeType, + params: Object.entries(bodyInput).map(([name, value]) => { + if (value instanceof File) { + return { + name, + fileName: value.name, + contentType: value.type, + }; + } return { name, - fileName: value.name, - contentType: value.type, + value, }; - } - return { - name, - value, - }; - }), - }; + }), + }; + } } } From a8b65147b6a4494e3030e1d43458adb83600a842 Mon Sep 17 00:00:00 2001 From: Paulina Lukasik Date: Tue, 19 Nov 2024 14:38:44 +0100 Subject: [PATCH 3/5] feat: fix lint errors and typo --- .../src/components/TryIt/Body/BinaryBody.tsx | 4 +--- .../components/TryIt/Body/__tests__/BinaryBody.test.tsx | 5 +---- packages/elements-core/src/components/TryIt/TryIt.tsx | 8 ++++---- .../elements-core/src/components/TryIt/build-request.ts | 4 +--- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/packages/elements-core/src/components/TryIt/Body/BinaryBody.tsx b/packages/elements-core/src/components/TryIt/Body/BinaryBody.tsx index 9871df3b1..81a3fa10f 100644 --- a/packages/elements-core/src/components/TryIt/Body/BinaryBody.tsx +++ b/packages/elements-core/src/components/TryIt/Body/BinaryBody.tsx @@ -43,9 +43,7 @@ export const BinaryBody: React.FC = ({ specification, values, o parameter={{ name: 'file' }} value={values.file instanceof File ? values.file : undefined} onChange={newValue => { - { - newValue && onChangeValues({ file: newValue }); - } + newValue && onChangeValues({ file: newValue }); }} /> diff --git a/packages/elements-core/src/components/TryIt/Body/__tests__/BinaryBody.test.tsx b/packages/elements-core/src/components/TryIt/Body/__tests__/BinaryBody.test.tsx index 9d2ce024e..0e8aa7f97 100644 --- a/packages/elements-core/src/components/TryIt/Body/__tests__/BinaryBody.test.tsx +++ b/packages/elements-core/src/components/TryIt/Body/__tests__/BinaryBody.test.tsx @@ -6,8 +6,7 @@ import React from 'react'; import { BinaryBody, BinaryBodyProps } from '../BinaryBody'; describe('BinaryBody', () => { - it('renders filr input when the form is application/octet-stream', () => { - // Arrange + it('renders file input when the form is application/octet-stream', () => { const props: BinaryBodyProps = { specification: { id: '493afac014fa8', @@ -23,10 +22,8 @@ describe('BinaryBody', () => { onChangeValues: () => {}, }; - // Act const { getByLabelText } = render(); - // Assert expect(getByLabelText('file')).toBeInTheDocument(); }); }); diff --git a/packages/elements-core/src/components/TryIt/TryIt.tsx b/packages/elements-core/src/components/TryIt/TryIt.tsx index 1d31e2678..56e8595e5 100644 --- a/packages/elements-core/src/components/TryIt/TryIt.tsx +++ b/packages/elements-core/src/components/TryIt/TryIt.tsx @@ -160,8 +160,8 @@ export const TryIt: React.FC = ({ bodyInput: formDataState.isFormDataBody ? getValues() : formDataState.isBinaryBody - ? getBinaryValue() - : textRequestBody, + ? getBinaryValue() + : textRequestBody, auth: operationAuthValue, ...(isMockingEnabled && { mockData: getMockData(mockUrl, httpOperation, mockingOptions) }), chosenServer, @@ -214,8 +214,8 @@ export const TryIt: React.FC = ({ bodyInput: formDataState.isFormDataBody ? getValues() : formDataState.isBinaryBody - ? getBinaryValue() - : textRequestBody, + ? getBinaryValue() + : textRequestBody, mockData, auth: operationAuthValue, chosenServer, diff --git a/packages/elements-core/src/components/TryIt/build-request.ts b/packages/elements-core/src/components/TryIt/build-request.ts index fd7f74953..84ce2dd39 100644 --- a/packages/elements-core/src/components/TryIt/build-request.ts +++ b/packages/elements-core/src/components/TryIt/build-request.ts @@ -164,9 +164,7 @@ export async function buildFetchRequest({ urlObject.search = new URLSearchParams(queryParamsWithAuth.map(nameAndValueObjectToPair)).toString(); const body = - bodyInput instanceof File - ? bodyInput - : typeof bodyInput === 'object' + typeof bodyInput === 'object' && !(bodyInput instanceof File) ? await createRequestBody(mediaTypeContent, bodyInput) : bodyInput; From ca6f1a7cfa937b813ed985ac42e1a28b1667284b Mon Sep 17 00:00:00 2001 From: Paulina Lukasik Date: Tue, 19 Nov 2024 15:44:00 +0100 Subject: [PATCH 4/5] feat: fix on change file value --- packages/elements-core/src/components/TryIt/Body/BinaryBody.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/elements-core/src/components/TryIt/Body/BinaryBody.tsx b/packages/elements-core/src/components/TryIt/Body/BinaryBody.tsx index 81a3fa10f..8aefb84e1 100644 --- a/packages/elements-core/src/components/TryIt/Body/BinaryBody.tsx +++ b/packages/elements-core/src/components/TryIt/Body/BinaryBody.tsx @@ -43,7 +43,7 @@ export const BinaryBody: React.FC = ({ specification, values, o parameter={{ name: 'file' }} value={values.file instanceof File ? values.file : undefined} onChange={newValue => { - newValue && onChangeValues({ file: newValue }); + newValue ? onChangeValues({ file: newValue }) : onChangeValues({}); }} /> From dc39825c466630de3522bf5a62eded1b93455fb6 Mon Sep 17 00:00:00 2001 From: Paulina Lukasik Date: Fri, 22 Nov 2024 13:22:08 +0100 Subject: [PATCH 5/5] feat: remove regex in convert request to sample --- .../convertRequestToSample.spec.ts.snap | 7 ------ .../__tests__/convertRequestToSample.spec.ts | 24 ------------------- .../RequestSamples/convertRequestToSample.ts | 3 --- 3 files changed, 34 deletions(-) diff --git a/packages/elements-core/src/components/RequestSamples/__tests__/__snapshots__/convertRequestToSample.spec.ts.snap b/packages/elements-core/src/components/RequestSamples/__tests__/__snapshots__/convertRequestToSample.spec.ts.snap index f4ba394f3..afb28b294 100644 --- a/packages/elements-core/src/components/RequestSamples/__tests__/__snapshots__/convertRequestToSample.spec.ts.snap +++ b/packages/elements-core/src/components/RequestSamples/__tests__/__snapshots__/convertRequestToSample.spec.ts.snap @@ -1,12 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`convertRequestToSample converts an application/octet-stream request to a sample 1`] = ` -"curl --request POST \\\\ - --url https://todos.stoplight.io/todos/todoId \\\\ - --header 'Content-Type: application/octet-stream' \\\\ - --data-binary 'binary data'" -`; - exports[`given c, convertRequestToSample converts a request to a sample 1`] = ` "CURL *hnd = curl_easy_init(); diff --git a/packages/elements-core/src/components/RequestSamples/__tests__/convertRequestToSample.spec.ts b/packages/elements-core/src/components/RequestSamples/__tests__/convertRequestToSample.spec.ts index 3e6ae293e..1f1ee5ba7 100644 --- a/packages/elements-core/src/components/RequestSamples/__tests__/convertRequestToSample.spec.ts +++ b/packages/elements-core/src/components/RequestSamples/__tests__/convertRequestToSample.spec.ts @@ -67,27 +67,3 @@ test.each(languages)('given %s, convertRequestToSample converts a request to a s expect(convertRequestToSample(language, library, har)).resolves.toMatchSnapshot(); }); - -test('convertRequestToSample converts an application/octet-stream request to a sample', async () => { - const har = { - method: 'POST', - url: 'https://todos.stoplight.io/todos/todoId', - httpVersion: 'HTTP/1.1', - cookies: [], - headers: [ - { - name: 'Content-Type', - value: 'application/octet-stream', - }, - ], - queryString: [], - postData: { - mimeType: 'application/octet-stream', - text: 'binary data', - }, - headersSize: -1, - bodySize: -1, - }; - - await expect(convertRequestToSample('shell', 'curl', har)).resolves.toMatchSnapshot(); -}); diff --git a/packages/elements-core/src/components/RequestSamples/convertRequestToSample.ts b/packages/elements-core/src/components/RequestSamples/convertRequestToSample.ts index ef21c757b..92661c07c 100644 --- a/packages/elements-core/src/components/RequestSamples/convertRequestToSample.ts +++ b/packages/elements-core/src/components/RequestSamples/convertRequestToSample.ts @@ -18,9 +18,6 @@ export const convertRequestToSample = async ( } if (typeof converted === 'string') { converted = converted.replace(/%7B/g, '{').replace(/%7D/g, '}'); - if (request.postData?.mimeType === 'application/octet-stream') { - converted = converted.replace('--data', '--data-binary'); - } } return converted;