mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-16 15:45:13 +00:00
Update OpenAPI code examples to support multiple content-type (#2843)
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@gitbook/react-openapi': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Update OpenAPI code examples to support multiple content-type
|
||||||
@@ -57,11 +57,7 @@ export function OpenAPICodeSample(props: {
|
|||||||
data.path +
|
data.path +
|
||||||
(searchParams.size ? `?${searchParams.toString()}` : ''),
|
(searchParams.size ? `?${searchParams.toString()}` : ''),
|
||||||
method: data.method,
|
method: data.method,
|
||||||
body: requestBodyContent
|
body: requestBodyContent ? generateMediaTypeExample(requestBodyContent[1]) : undefined,
|
||||||
? generateMediaTypeExample(requestBodyContent[1], {
|
|
||||||
omitEmptyAndOptionalProperties: true,
|
|
||||||
})
|
|
||||||
: undefined,
|
|
||||||
headers: {
|
headers: {
|
||||||
...getSecurityHeaders(data.securities),
|
...getSecurityHeaders(data.securities),
|
||||||
...headersObject,
|
...headersObject,
|
||||||
|
|||||||
@@ -260,12 +260,7 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
|
|||||||
) : null}
|
) : null}
|
||||||
{shouldDisplayExample(schema) ? (
|
{shouldDisplayExample(schema) ? (
|
||||||
<div className="openapi-schema-example">
|
<div className="openapi-schema-example">
|
||||||
Example:{' '}
|
Example: <code>{formatExample(schema.example)}</code>
|
||||||
<code>
|
|
||||||
{typeof schema.example === 'string'
|
|
||||||
? schema.example
|
|
||||||
: stringifyOpenAPI(schema.example)}
|
|
||||||
</code>
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{schema.pattern ? (
|
{schema.pattern ? (
|
||||||
@@ -434,3 +429,16 @@ function getDisclosureLabel(schema: OpenAPIV3.SchemaObject): string | undefined
|
|||||||
|
|
||||||
return schema.title;
|
return schema.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatExample(example: any): string {
|
||||||
|
if (typeof example === 'string') {
|
||||||
|
return example
|
||||||
|
.replace(/\n/g, ' ') // Replace newlines with spaces
|
||||||
|
.replace(/\s+/g, ' ') // Collapse multiple spaces/newlines into a single space
|
||||||
|
.replace(/([\{\}:,])\s+/g, '$1 ') // Ensure a space after {, }, :, and ,
|
||||||
|
.replace(/\s+([\{\}:,])/g, ' $1') // Ensure a space before {, }, :, and ,
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringifyOpenAPI(example);
|
||||||
|
}
|
||||||
|
|||||||
@@ -74,11 +74,17 @@ export function OpenAPITabs(
|
|||||||
const tabFromState = syncedTabs.get(stateKey);
|
const tabFromState = syncedTabs.get(stateKey);
|
||||||
|
|
||||||
if (!items.some((item) => item.key === tabFromState?.key)) {
|
if (!items.some((item) => item.key === tabFromState?.key)) {
|
||||||
return;
|
return setSelectedTab(defaultTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tabFromState && tabFromState?.key !== selectedTab?.key) {
|
if (tabFromState && tabFromState?.key !== selectedTab?.key) {
|
||||||
setSelectedTab(tabFromState);
|
const tabFromItems = items.find((item) => item.key === tabFromState.key);
|
||||||
|
|
||||||
|
if (!tabFromItems) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedTab(tabFromItems);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isVisible, stateKey, syncedTabs, selectedTabKey]);
|
}, [isVisible, stateKey, syncedTabs, selectedTabKey]);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { it, expect } from 'bun:test';
|
import { it, expect, describe } from 'bun:test';
|
||||||
import { parseHostAndPath } from './code-samples';
|
import { codeSampleGenerators, CodeSampleInput, parseHostAndPath } from './code-samples';
|
||||||
|
|
||||||
it('should parse host and path on url strings properly', () => {
|
it('should parse host and path on url strings properly', () => {
|
||||||
const testUrls = [
|
const testUrls = [
|
||||||
@@ -49,3 +49,595 @@ it('should parse host and path on url strings properly', () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('curL code sample generator', () => {
|
||||||
|
const generator = codeSampleGenerators.find((g) => g.id === 'curl');
|
||||||
|
|
||||||
|
expect(generator).toBeDefined();
|
||||||
|
|
||||||
|
it('should format application/x-www-form-urlencoded body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
key: 'value',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
"curl -L \\\n --url 'https://example.com/path' \\\n --header 'Content-Type: application/x-www-form-urlencoded' \\\n --data 'key=value'",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format application/json body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
key: 'value',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
"curl -L \\\n --url 'https://example.com/path' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"key\": \"value\"\n }'",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format application/xml body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/xml',
|
||||||
|
},
|
||||||
|
body: '<key>value</key>',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
"curl -L \\\n --url 'https://example.com/path' \\\n --header 'Content-Type: application/xml' \\\n --data-binary $'<key>value</key>'",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format application/graphql body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/graphql',
|
||||||
|
},
|
||||||
|
body: '{ key }',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
"curl -L \\\n --url 'https://example.com/path' \\\n --header 'Content-Type: application/json' \\\n --data '\"{ key }\"'",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format text/csv body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/csv',
|
||||||
|
},
|
||||||
|
body: 'key,value',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
"curl -L \\\n --url 'https://example.com/path' \\\n --header 'Content-Type: text/csv' \\\n --data-binary $'key,value'",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format application/pdf body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/pdf',
|
||||||
|
},
|
||||||
|
body: 'file',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
"curl -L \\\n --url 'https://example.com/path' \\\n --header 'Content-Type: application/pdf' \\\n --data-binary '@file'",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format text/plain body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain',
|
||||||
|
},
|
||||||
|
body: 'value',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
"curl -L \\\n --url 'https://example.com/path' \\\n --header 'Content-Type: text/plain' \\\n --data 'value'",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format multipart/form-data body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
key: 'value',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
"curl -L \\\n --url 'https://example.com/path' \\\n --header 'Content-Type: multipart/form-data' \\\n --form 'key=value'",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('javascript code sample generator', () => {
|
||||||
|
const generator = codeSampleGenerators.find((g) => g.id === 'javascript');
|
||||||
|
|
||||||
|
expect(generator).toBeDefined();
|
||||||
|
|
||||||
|
it('should format application/x-www-form-urlencoded body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
key: 'value',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'const params = new URLSearchParams();\n\nparams.append("key", "value");\n\nconst response = await fetch(\'https://example.com/path\', {\n method: \'GET\',\n headers: {\n "Content-Type": "application/x-www-form-urlencoded"\n },\n body: params.toString()\n});\n\nconst data = await response.json();',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format application/json body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
key: 'value',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'const response = await fetch(\'https://example.com/path\', {\n method: \'GET\',\n headers: {\n "Content-Type": "application/json"\n },\n body: JSON.stringify({\n "key": "value"\n })\n});\n\nconst data = await response.json();',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format application/xml body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/xml',
|
||||||
|
},
|
||||||
|
body: '<key>value</key>',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'const xml = `\n <key>value</key>`;\n\nconst response = await fetch(\'https://example.com/path\', {\n method: \'GET\',\n headers: {\n "Content-Type": "application/xml"\n },\n body: xml\n});\n\nconst data = await response.json();',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format application/graphql body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/graphql',
|
||||||
|
},
|
||||||
|
body: '{ key }',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'const query = `\n { key }`;\n\nconst response = await fetch(\'https://example.com/path\', {\n method: \'GET\',\n headers: {\n "Content-Type": "application/graphql"\n },\n body: JSON.stringify(query)\n});\n\nconst data = await response.json();',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format text/csv body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/csv',
|
||||||
|
},
|
||||||
|
body: 'key,value',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'const csv = `\n key,value`;\n\nconst response = await fetch(\'https://example.com/path\', {\n method: \'GET\',\n headers: {\n "Content-Type": "text/csv"\n },\n body: csv\n});\n\nconst data = await response.json();',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format application/pdf body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/pdf',
|
||||||
|
},
|
||||||
|
body: 'file',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'const formData = new FormData();\n\nformData.append("file", "file");\n\nconst response = await fetch(\'https://example.com/path\', {\n method: \'GET\',\n headers: {\n "Content-Type": "application/pdf"\n },\n body: formData\n});\n\nconst data = await response.json();',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format text/plain body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain',
|
||||||
|
},
|
||||||
|
body: 'value',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'const response = await fetch(\'https://example.com/path\', {\n method: \'GET\',\n headers: {\n "Content-Type": "text/plain"\n },\n body: "value"\n});\n\nconst data = await response.json();',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format multipart/form-data body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
key: 'value',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'const formData = new FormData();\n\nformData.append("key", "value");\n\nconst response = await fetch(\'https://example.com/path\', {\n method: \'GET\',\n headers: {\n "Content-Type": "multipart/form-data"\n },\n body: formData\n});\n\nconst data = await response.json();',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('python code sample generator', () => {
|
||||||
|
const generator = codeSampleGenerators.find((g) => g.id === 'python');
|
||||||
|
|
||||||
|
expect(generator).toBeDefined();
|
||||||
|
|
||||||
|
it('should format application/x-www-form-urlencoded body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
key: 'value',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"application/x-www-form-urlencoded"},\n data={"key":"value"}\n)\n\ndata = response.json()',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format application/json body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
key: 'value',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"application/json"},\n data={"key":"value"}\n)\n\ndata = response.json()',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format application/xml body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/xml',
|
||||||
|
},
|
||||||
|
body: '<key>value</key>',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"application/xml"},\n data="<key>value</key>"\n)\n\ndata = response.json()',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format application/graphql body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/graphql',
|
||||||
|
},
|
||||||
|
body: '{ key }',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"application/graphql"},\n data="{ key }"\n)\n\ndata = response.json()',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format text/csv body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/csv',
|
||||||
|
},
|
||||||
|
body: 'key,value',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"text/csv"},\n data="key,value"\n)\n\ndata = response.json()',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format application/pdf body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/pdf',
|
||||||
|
},
|
||||||
|
body: 'file',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'import requests\n\nfiles = {\n "file": "file",\n}\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"application/pdf"},\n files=files\n)\n\ndata = response.json()',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format text/plain body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain',
|
||||||
|
},
|
||||||
|
body: 'value',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"text/plain"},\n data="value"\n)\n\ndata = response.json()',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format multipart/form-data body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
key: 'value',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'import requests\n\nfiles = {\n "key": "value",\n}\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"multipart/form-data"},\n files=files\n)\n\ndata = response.json()',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('http code sample generator', () => {
|
||||||
|
const generator = codeSampleGenerators.find((g) => g.id === 'http');
|
||||||
|
|
||||||
|
expect(generator).toBeDefined();
|
||||||
|
|
||||||
|
it('should format application/x-www-form-urlencoded body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
key: 'value',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'GET /path HTTP/1.1\nHost: example.com\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 15\nAccept: */*\n\n"key=value"',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format application/json body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
key: 'value',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'GET /path HTTP/1.1\nHost: example.com\nContent-Type: application/json\nContent-Length: 15\nAccept: */*\n\n{\n "key": "value"\n}',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format application/xml body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/xml',
|
||||||
|
},
|
||||||
|
body: '<key>value</key>',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'GET /path HTTP/1.1\nHost: example.com\nContent-Type: application/xml\nContent-Length: 18\nAccept: */*\n\n"<key>value</key>"',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format application/graphql body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/graphql',
|
||||||
|
},
|
||||||
|
body: '{ key }',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'GET /path HTTP/1.1\nHost: example.com\nContent-Type: application/graphql\nContent-Length: 9\nAccept: */*\n\n"{ key }"',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format text/csv body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/csv',
|
||||||
|
},
|
||||||
|
body: 'key,value',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'GET /path HTTP/1.1\nHost: example.com\nContent-Type: text/csv\nContent-Length: 11\nAccept: */*\n\n"key,value"',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format application/pdf body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/pdf',
|
||||||
|
},
|
||||||
|
body: 'file',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'GET /path HTTP/1.1\nHost: example.com\nContent-Type: application/pdf\nContent-Length: 6\nAccept: */*\n\n"file"',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format text/plain body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain',
|
||||||
|
},
|
||||||
|
body: 'value',
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'GET /path HTTP/1.1\nHost: example.com\nContent-Type: text/plain\nContent-Length: 7\nAccept: */*\n\n"value"',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format multipart/form-data body properly', () => {
|
||||||
|
const input: CodeSampleInput = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://example.com/path',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
key: 'value',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = generator!.generate(input);
|
||||||
|
|
||||||
|
expect(output).toBe(
|
||||||
|
'GET /path HTTP/1.1\nHost: example.com\nContent-Type: multipart/form-data\nContent-Length: 15\nAccept: */*\n\n{\n "key": "value"\n}',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
import {
|
||||||
|
isFormData,
|
||||||
|
isPDF,
|
||||||
|
isFormUrlEncoded,
|
||||||
|
isText,
|
||||||
|
isXML,
|
||||||
|
isCSV,
|
||||||
|
isGraphQL,
|
||||||
|
isPlainObject,
|
||||||
|
} from './contentTypeChecks';
|
||||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||||
|
|
||||||
export interface CodeSampleInput {
|
export interface CodeSampleInput {
|
||||||
@@ -30,14 +40,27 @@ export const codeSampleGenerators: CodeSampleGenerator[] = [
|
|||||||
|
|
||||||
lines.push(`--url '${url}'`);
|
lines.push(`--url '${url}'`);
|
||||||
|
|
||||||
if (headers) {
|
if (body) {
|
||||||
|
const bodyContent = BodyGenerators.getCurlBody(body, headers);
|
||||||
|
|
||||||
|
if (bodyContent) {
|
||||||
|
body = bodyContent.body;
|
||||||
|
headers = bodyContent.headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers && Object.keys(headers).length > 0) {
|
||||||
Object.entries(headers).forEach(([key, value]) => {
|
Object.entries(headers).forEach(([key, value]) => {
|
||||||
lines.push(`--header '${key}: ${value}'`);
|
lines.push(`--header '${key}: ${value}'`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (body && Object.keys(body).length > 0) {
|
if (body) {
|
||||||
lines.push(`--data '${stringifyOpenAPI(body)}'`);
|
if (Array.isArray(body)) {
|
||||||
|
lines.push(...body);
|
||||||
|
} else {
|
||||||
|
lines.push(body);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.map((line, index) => (index > 0 ? indent(line, 2) : line)).join(separator);
|
return lines.map((line, index) => (index > 0 ? indent(line, 2) : line)).join(separator);
|
||||||
@@ -50,18 +73,29 @@ export const codeSampleGenerators: CodeSampleGenerator[] = [
|
|||||||
generate: ({ method, url, headers, body }) => {
|
generate: ({ method, url, headers, body }) => {
|
||||||
let code = '';
|
let code = '';
|
||||||
|
|
||||||
|
if (body) {
|
||||||
|
const lines = BodyGenerators.getJavaScriptBody(body, headers);
|
||||||
|
|
||||||
|
if (lines) {
|
||||||
|
// add the generated code to the top
|
||||||
|
code += lines.code;
|
||||||
|
body = lines.body;
|
||||||
|
headers = lines.headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
code += `const response = await fetch('${url}', {
|
code += `const response = await fetch('${url}', {
|
||||||
method: '${method.toUpperCase()}',\n`;
|
method: '${method.toUpperCase()}',\n`;
|
||||||
|
|
||||||
if (headers) {
|
if (headers && Object.keys(headers).length > 0) {
|
||||||
code += indent(`headers: ${stringifyOpenAPI(headers, null, 2)},\n`, 4);
|
code += indent(`headers: ${stringifyOpenAPI(headers, null, 2)},\n`, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (body) {
|
if (body) {
|
||||||
code += indent(`body: JSON.stringify(${stringifyOpenAPI(body, null, 2)}),\n`, 4);
|
code += indent(`body: ${body}\n`, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
code += `});\n`;
|
code += `});\n\n`;
|
||||||
code += `const data = await response.json();`;
|
code += `const data = await response.json();`;
|
||||||
|
|
||||||
return code;
|
return code;
|
||||||
@@ -73,15 +107,34 @@ export const codeSampleGenerators: CodeSampleGenerator[] = [
|
|||||||
syntax: 'python',
|
syntax: 'python',
|
||||||
generate: ({ method, url, headers, body }) => {
|
generate: ({ method, url, headers, body }) => {
|
||||||
let code = 'import requests\n\n';
|
let code = 'import requests\n\n';
|
||||||
|
|
||||||
|
if (body) {
|
||||||
|
const lines = BodyGenerators.getPythonBody(body, headers);
|
||||||
|
|
||||||
|
// add the generated code to the top
|
||||||
|
if (lines) {
|
||||||
|
code += lines.code;
|
||||||
|
body = lines.body;
|
||||||
|
headers = lines.headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
code += `response = requests.${method.toLowerCase()}(\n`;
|
code += `response = requests.${method.toLowerCase()}(\n`;
|
||||||
code += indent(`"${url}",\n`, 4);
|
code += indent(`"${url}",\n`, 4);
|
||||||
if (headers) {
|
|
||||||
|
if (headers && Object.keys(headers).length > 0) {
|
||||||
code += indent(`headers=${stringifyOpenAPI(headers)},\n`, 4);
|
code += indent(`headers=${stringifyOpenAPI(headers)},\n`, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (body) {
|
if (body) {
|
||||||
code += indent(`json=${stringifyOpenAPI(body)}\n`, 4);
|
if (body === 'files') {
|
||||||
|
code += indent(`files=${body}\n`, 4);
|
||||||
|
} else {
|
||||||
|
code += indent(`data=${stringifyOpenAPI(body)}\n`, 4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
code += ')\n';
|
|
||||||
|
code += ')\n\n';
|
||||||
code += `data = response.json()`;
|
code += `data = response.json()`;
|
||||||
return code;
|
return code;
|
||||||
},
|
},
|
||||||
@@ -99,6 +152,12 @@ export const codeSampleGenerators: CodeSampleGenerator[] = [
|
|||||||
// handle unicode chars with a text encoder
|
// handle unicode chars with a text encoder
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
|
|
||||||
|
const bodyString = BodyGenerators.getHTTPBody(body, headers);
|
||||||
|
|
||||||
|
if (bodyString) {
|
||||||
|
body = bodyString;
|
||||||
|
}
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
...headers,
|
...headers,
|
||||||
'Content-Length': encoder.encode(bodyContent).length.toString(),
|
'Content-Length': encoder.encode(bodyContent).length.toString(),
|
||||||
@@ -117,7 +176,7 @@ export const codeSampleGenerators: CodeSampleGenerator[] = [
|
|||||||
.join('\n') + '\n'
|
.join('\n') + '\n'
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
const bodyString = body ? `\n${stringifyOpenAPI(body, null, 2)}` : '';
|
const bodyString = body ? `\n${body}` : '';
|
||||||
|
|
||||||
const httpRequest = `${method.toUpperCase()} ${decodeURI(path)} HTTP/1.1
|
const httpRequest = `${method.toUpperCase()} ${decodeURI(path)} HTTP/1.1
|
||||||
Host: ${host}
|
Host: ${host}
|
||||||
@@ -157,3 +216,165 @@ export function parseHostAndPath(url: string) {
|
|||||||
return { host, path };
|
return { host, path };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Body Generators
|
||||||
|
const BodyGenerators = {
|
||||||
|
getCurlBody(body: any, headers?: Record<string, string>) {
|
||||||
|
if (!body || !headers) return undefined;
|
||||||
|
|
||||||
|
// Copy headers to avoid mutating the original object
|
||||||
|
const headersCopy = { ...headers };
|
||||||
|
const contentType: string = headersCopy['Content-Type'] || '';
|
||||||
|
|
||||||
|
if (isFormData(contentType)) {
|
||||||
|
body = isPlainObject(body)
|
||||||
|
? Object.entries(body).map(([key, value]) => `--form '${key}=${String(value)}'`)
|
||||||
|
: `--form 'file=@${body}'`;
|
||||||
|
} else if (isFormUrlEncoded(contentType)) {
|
||||||
|
body = isPlainObject(body)
|
||||||
|
? `--data '${Object.entries(body)
|
||||||
|
.map(([key, value]) => `${key}=${String(value)}`)
|
||||||
|
.join('&')}'`
|
||||||
|
: String(body);
|
||||||
|
} else if (isText(contentType)) {
|
||||||
|
body = `--data '${String(body).replace(/"/g, '')}'`;
|
||||||
|
} else if (isXML(contentType) || isCSV(contentType)) {
|
||||||
|
// We use --data-binary to avoid cURL converting newlines to \r\n
|
||||||
|
body = `--data-binary $'${stringifyOpenAPI(body).replace(/"/g, '')}'`;
|
||||||
|
} else if (isGraphQL(contentType)) {
|
||||||
|
body = `--data '${stringifyOpenAPI(body)}'`;
|
||||||
|
// Set Content-Type to application/json for GraphQL, recommended by GraphQL spec
|
||||||
|
headersCopy['Content-Type'] = 'application/json';
|
||||||
|
} else if (isPDF(contentType)) {
|
||||||
|
// We use --data-binary to avoid cURL converting newlines to \r\n
|
||||||
|
body = `--data-binary '@${String(body)}'`;
|
||||||
|
} else {
|
||||||
|
body = `--data '${stringifyOpenAPI(body, null, 2)}'`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
body,
|
||||||
|
headers: headersCopy,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getJavaScriptBody: (body: any, headers?: Record<string, string>) => {
|
||||||
|
if (!body || !headers) return;
|
||||||
|
|
||||||
|
let code = '';
|
||||||
|
|
||||||
|
// Copy headers to avoid mutating the original object
|
||||||
|
const headersCopy = { ...headers };
|
||||||
|
const contentType: string = headersCopy['Content-Type'] || '';
|
||||||
|
|
||||||
|
// Use FormData for file uploads
|
||||||
|
if (isFormData(contentType)) {
|
||||||
|
code += 'const formData = new FormData();\n\n';
|
||||||
|
if (isPlainObject(body)) {
|
||||||
|
Object.entries(body).forEach(([key, value]) => {
|
||||||
|
code += `formData.append("${key}", "${String(value)}");\n`;
|
||||||
|
});
|
||||||
|
} else if (typeof body === 'string') {
|
||||||
|
code += `formData.append("file", "${body}");\n`;
|
||||||
|
}
|
||||||
|
code += '\n';
|
||||||
|
body = 'formData';
|
||||||
|
} else if (isFormUrlEncoded(contentType)) {
|
||||||
|
// Use URLSearchParams for form-urlencoded data
|
||||||
|
code += 'const params = new URLSearchParams();\n\n';
|
||||||
|
if (isPlainObject(body)) {
|
||||||
|
Object.entries(body).forEach(([key, value]) => {
|
||||||
|
code += `params.append("${key}", "${String(value)}");\n`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
code += '\n';
|
||||||
|
body = 'params.toString()';
|
||||||
|
} else if (isGraphQL(contentType)) {
|
||||||
|
if (isPlainObject(body)) {
|
||||||
|
Object.entries(body).forEach(([key, value]) => {
|
||||||
|
code += `const ${key} = \`\n${indent(String(value), 4)}\`;\n\n`;
|
||||||
|
});
|
||||||
|
body = `JSON.stringify({ ${Object.keys(body).join(', ')} })`;
|
||||||
|
// Set Content-Type to application/json for GraphQL, recommended by GraphQL spec
|
||||||
|
headersCopy['Content-Type'] = 'application/json';
|
||||||
|
} else {
|
||||||
|
code += `const query = \`\n${indent(String(body), 4)}\`;\n\n`;
|
||||||
|
body = 'JSON.stringify(query)';
|
||||||
|
}
|
||||||
|
} else if (isCSV(contentType)) {
|
||||||
|
code += 'const csv = `\n';
|
||||||
|
code += indent(String(body), 4);
|
||||||
|
code += '`;\n\n';
|
||||||
|
body = 'csv';
|
||||||
|
} else if (isPDF(contentType)) {
|
||||||
|
// Use FormData to upload PDF files
|
||||||
|
code += 'const formData = new FormData();\n\n';
|
||||||
|
code += `formData.append("file", "${body}");\n\n`;
|
||||||
|
body = 'formData';
|
||||||
|
} else if (isXML(contentType)) {
|
||||||
|
code += 'const xml = `\n';
|
||||||
|
code += indent(String(body), 4);
|
||||||
|
code += '`;\n\n';
|
||||||
|
body = 'xml';
|
||||||
|
} else if (isText(contentType)) {
|
||||||
|
body = stringifyOpenAPI(body, null, 2);
|
||||||
|
} else {
|
||||||
|
body = `JSON.stringify(${stringifyOpenAPI(body, null, 2)})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { body, code, headers: headersCopy };
|
||||||
|
},
|
||||||
|
getPythonBody: (body: any, headers?: Record<string, string>) => {
|
||||||
|
if (!body || !headers) return;
|
||||||
|
let code = '';
|
||||||
|
const contentType: string = headers['Content-Type'] || '';
|
||||||
|
|
||||||
|
if (isFormData(contentType)) {
|
||||||
|
code += 'files = {\n';
|
||||||
|
if (isPlainObject(body)) {
|
||||||
|
Object.entries(body).forEach(([key, value]) => {
|
||||||
|
code += indent(`"${key}": "${String(value)}",`, 4) + '\n';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
code += '}\n\n';
|
||||||
|
body = 'files';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPDF(contentType)) {
|
||||||
|
code += 'files = {\n';
|
||||||
|
code += indent(`"file": "${body}",`, 4) + '\n';
|
||||||
|
code += '}\n\n';
|
||||||
|
body = 'files';
|
||||||
|
}
|
||||||
|
|
||||||
|
return { body, code, headers };
|
||||||
|
},
|
||||||
|
getHTTPBody: (body: any, headers?: Record<string, string>) => {
|
||||||
|
if (!body || !headers) return undefined;
|
||||||
|
|
||||||
|
const contentType: string = headers['Content-Type'] || '';
|
||||||
|
|
||||||
|
const typeHandlers = {
|
||||||
|
pdf: () => `${stringifyOpenAPI(body, null, 2)}`,
|
||||||
|
formUrlEncoded: () => {
|
||||||
|
const encoded = isPlainObject(body)
|
||||||
|
? Object.entries(body)
|
||||||
|
.map(([key, value]) => `${key}=${String(value)}`)
|
||||||
|
.join('&')
|
||||||
|
: String(body);
|
||||||
|
return `"${encoded}"`;
|
||||||
|
},
|
||||||
|
text: () => `"${String(body)}"`,
|
||||||
|
xmlOrCsv: () => `"${stringifyOpenAPI(body).replace(/"/g, '')}"`,
|
||||||
|
default: () => `${stringifyOpenAPI(body, null, 2)}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isPDF(contentType)) return typeHandlers.pdf();
|
||||||
|
if (isFormUrlEncoded(contentType)) return typeHandlers.formUrlEncoded();
|
||||||
|
if (isText(contentType)) return typeHandlers.text();
|
||||||
|
if (isXML(contentType) || isCSV(contentType)) {
|
||||||
|
return typeHandlers.xmlOrCsv();
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeHandlers.default();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
export function isJSON(contentType?: string): boolean {
|
||||||
|
return contentType?.toLowerCase().includes('application/json') || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isXML(contentType?: string): boolean {
|
||||||
|
return contentType?.toLowerCase().includes('application/xml') || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isGraphQL(contentType?: string): boolean {
|
||||||
|
return contentType?.toLowerCase().includes('application/graphql') || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isCSV(contentType?: string): boolean {
|
||||||
|
return contentType?.toLowerCase().includes('text/csv') || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPDF(contentType?: string): boolean {
|
||||||
|
return contentType?.toLowerCase().includes('application/pdf') || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isText(contentType?: string): boolean {
|
||||||
|
return contentType?.toLowerCase().includes('text/plain') || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFormUrlEncoded(contentType?: string): boolean {
|
||||||
|
return contentType?.toLowerCase().includes('application/x-www-form-urlencoded') || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFormData(contentType?: string): boolean {
|
||||||
|
return !!contentType && contentType.toLowerCase().includes('multipart/form-data');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPlainObject(value: unknown): boolean {
|
||||||
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user