mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-21 01:53:26 +00:00
Support multiple examples and multiple responses example (#2856)
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@gitbook/react-openapi': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Support multiple response media types and examples
|
||||||
@@ -218,6 +218,7 @@
|
|||||||
"@scalar/oas-utils": "^0.2.101",
|
"@scalar/oas-utils": "^0.2.101",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"flatted": "^3.2.9",
|
"flatted": "^3.2.9",
|
||||||
|
"json-xml-parse": "^1.3.0",
|
||||||
"react-aria": "^3.37.0",
|
"react-aria": "^3.37.0",
|
||||||
"react-aria-components": "^1.6.0",
|
"react-aria-components": "^1.6.0",
|
||||||
"usehooks-ts": "^3.1.0",
|
"usehooks-ts": "^3.1.0",
|
||||||
@@ -2410,6 +2411,8 @@
|
|||||||
|
|
||||||
"json-stringify-deterministic": ["json-stringify-deterministic@1.0.12", "", {}, "sha512-q3PN0lbUdv0pmurkBNdJH3pfFvOTL/Zp0lquqpvcjfKzt6Y0j49EPHAmVHCAS4Ceq/Y+PejWTzyiVpoY71+D6g=="],
|
"json-stringify-deterministic": ["json-stringify-deterministic@1.0.12", "", {}, "sha512-q3PN0lbUdv0pmurkBNdJH3pfFvOTL/Zp0lquqpvcjfKzt6Y0j49EPHAmVHCAS4Ceq/Y+PejWTzyiVpoY71+D6g=="],
|
||||||
|
|
||||||
|
"json-xml-parse": ["json-xml-parse@1.3.0", "", {}, "sha512-MVosauc/3W2wL4dd4yaJzH5oXw+HOUfptn0+d4+bFghMiJFop7MaqIwFXJNLiRnNYJNQ6L4o7B+53n5wcvoLFw=="],
|
||||||
|
|
||||||
"json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="],
|
"json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="],
|
||||||
|
|
||||||
"jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
|
"jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"@scalar/oas-utils": "^0.2.101",
|
"@scalar/oas-utils": "^0.2.101",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"flatted": "^3.2.9",
|
"flatted": "^3.2.9",
|
||||||
|
"json-xml-parse": "^1.3.0",
|
||||||
"react-aria-components": "^1.6.0",
|
"react-aria-components": "^1.6.0",
|
||||||
"react-aria": "^3.37.0",
|
"react-aria": "^3.37.0",
|
||||||
"usehooks-ts": "^3.1.0",
|
"usehooks-ts": "^3.1.0",
|
||||||
|
|||||||
@@ -58,7 +58,9 @@ export function OpenAPICodeSample(props: {
|
|||||||
(searchParams.size ? `?${searchParams.toString()}` : ''),
|
(searchParams.size ? `?${searchParams.toString()}` : ''),
|
||||||
method: data.method,
|
method: data.method,
|
||||||
body: requestBodyContent
|
body: requestBodyContent
|
||||||
? generateMediaTypeExample(requestBodyContent[1], { onlyRequired: true })
|
? generateMediaTypeExample(requestBodyContent[1], {
|
||||||
|
omitEmptyAndOptionalProperties: true,
|
||||||
|
})
|
||||||
: undefined,
|
: undefined,
|
||||||
headers: {
|
headers: {
|
||||||
...getSecurityHeaders(data.securities),
|
...getSecurityHeaders(data.securities),
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
|||||||
import { generateSchemaExample } from './generateSchemaExample';
|
import { generateSchemaExample } from './generateSchemaExample';
|
||||||
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
||||||
import { checkIsReference, createStateKey, resolveDescription } from './utils';
|
import { checkIsReference, createStateKey, resolveDescription } from './utils';
|
||||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
|
||||||
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
|
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
|
||||||
import { InteractiveSection } from './InteractiveSection';
|
import { InteractiveSection } from './InteractiveSection';
|
||||||
|
import { json2xml } from './json2xml';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display an example of the response content.
|
* Display an example of the response content.
|
||||||
@@ -38,84 +38,51 @@ export function OpenAPIResponseExample(props: {
|
|||||||
return Number(a) - Number(b);
|
return Number(a) - Number(b);
|
||||||
});
|
});
|
||||||
|
|
||||||
const examples = responses
|
const tabs = responses
|
||||||
.map(([key, value]) => {
|
.map(([key, responseObject]) => {
|
||||||
const responseObject = value;
|
const description = resolveDescription(responseObject);
|
||||||
const mediaTypeObject = (() => {
|
|
||||||
if (!responseObject.content) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const key = Object.keys(responseObject.content)[0];
|
|
||||||
return (
|
|
||||||
responseObject.content['application/json'] ??
|
|
||||||
(key ? responseObject.content[key] : null)
|
|
||||||
);
|
|
||||||
})();
|
|
||||||
|
|
||||||
if (!mediaTypeObject) {
|
if (checkIsReference(responseObject)) {
|
||||||
return {
|
return {
|
||||||
key: key,
|
key: key,
|
||||||
label: key,
|
label: key,
|
||||||
description: resolveDescription(responseObject),
|
description,
|
||||||
body: <OpenAPIEmptyResponseExample />,
|
body: (
|
||||||
|
<OpenAPIExample
|
||||||
|
example={getExampleFromReference(responseObject)}
|
||||||
|
context={context}
|
||||||
|
syntax="json"
|
||||||
|
/>
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const example = handleUnresolvedReference(
|
if (!responseObject.content || Object.keys(responseObject.content).length === 0) {
|
||||||
(() => {
|
return {
|
||||||
const { examples, example } = mediaTypeObject;
|
key: key,
|
||||||
if (examples) {
|
label: key,
|
||||||
const key = Object.keys(examples)[0];
|
description,
|
||||||
if (key) {
|
body: <OpenAPIEmptyResponseExample />,
|
||||||
// @TODO handle multiple examples
|
};
|
||||||
const firstExample = examples[key];
|
}
|
||||||
if (firstExample) {
|
|
||||||
return firstExample;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (example) {
|
|
||||||
return { value: example };
|
|
||||||
}
|
|
||||||
|
|
||||||
const schema = mediaTypeObject.schema;
|
|
||||||
if (!schema) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { value: generateSchemaExample(schema) };
|
|
||||||
})(),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: key,
|
key: key,
|
||||||
label: key,
|
label: key,
|
||||||
description: resolveDescription(responseObject),
|
description: resolveDescription(responseObject),
|
||||||
body: example?.value ? (
|
body: <OpenAPIResponse context={context} content={responseObject.content} />,
|
||||||
<context.CodeBlock
|
|
||||||
code={
|
|
||||||
typeof example.value === 'string'
|
|
||||||
? example.value
|
|
||||||
: stringifyOpenAPI(example.value, null, 2)
|
|
||||||
}
|
|
||||||
syntax="json"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<OpenAPIEmptyResponseExample />
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((val): val is { key: string; label: string; body: any; description: string } =>
|
.filter((val): val is { key: string; label: string; body: any; description: string } =>
|
||||||
Boolean(val),
|
Boolean(val),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (examples.length === 0) {
|
if (tabs.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OpenAPITabs stateKey={createStateKey('response-example')} items={examples}>
|
<OpenAPITabs stateKey={createStateKey('response-example')} items={tabs}>
|
||||||
<InteractiveSection header={<OpenAPITabsList />} className="openapi-response-example">
|
<InteractiveSection header={<OpenAPITabsList />} className="openapi-response-example">
|
||||||
<OpenAPITabsPanels />
|
<OpenAPITabsPanels />
|
||||||
</InteractiveSection>
|
</InteractiveSection>
|
||||||
@@ -123,6 +90,212 @@ export function OpenAPIResponseExample(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function OpenAPIResponse(props: {
|
||||||
|
context: OpenAPIContextProps;
|
||||||
|
content: {
|
||||||
|
[media: string]: OpenAPIV3.MediaTypeObject;
|
||||||
|
};
|
||||||
|
}) {
|
||||||
|
const { context, content } = props;
|
||||||
|
|
||||||
|
const entries = Object.entries(content);
|
||||||
|
const firstEntry = entries[0];
|
||||||
|
|
||||||
|
if (!firstEntry) {
|
||||||
|
throw new Error('One media type is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entries.length === 1) {
|
||||||
|
const [mediaType, mediaTypeObject] = firstEntry;
|
||||||
|
return (
|
||||||
|
<OpenAPIResponseMediaType
|
||||||
|
context={context}
|
||||||
|
mediaType={mediaType}
|
||||||
|
mediaTypeObject={mediaTypeObject}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabs = entries.map((entry) => {
|
||||||
|
const [mediaType, mediaTypeObject] = entry;
|
||||||
|
return {
|
||||||
|
key: mediaType,
|
||||||
|
label: mediaType,
|
||||||
|
body: (
|
||||||
|
<OpenAPIResponseMediaType
|
||||||
|
context={context}
|
||||||
|
mediaType={mediaType}
|
||||||
|
mediaTypeObject={mediaTypeObject}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OpenAPITabs stateKey={createStateKey('response-media-types')} items={tabs}>
|
||||||
|
<InteractiveSection
|
||||||
|
header={<OpenAPITabsList />}
|
||||||
|
className="openapi-response-media-types"
|
||||||
|
>
|
||||||
|
<OpenAPITabsPanels />
|
||||||
|
</InteractiveSection>
|
||||||
|
</OpenAPITabs>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function OpenAPIResponseMediaType(props: {
|
||||||
|
mediaTypeObject: OpenAPIV3.MediaTypeObject;
|
||||||
|
mediaType: string;
|
||||||
|
context: OpenAPIContextProps;
|
||||||
|
}) {
|
||||||
|
const { mediaTypeObject, mediaType } = props;
|
||||||
|
const examples = getExamplesFromMediaTypeObject({ mediaTypeObject, mediaType });
|
||||||
|
const syntax = getSyntaxFromMediaType(mediaType);
|
||||||
|
const firstExample = examples[0];
|
||||||
|
|
||||||
|
if (!firstExample) {
|
||||||
|
return <OpenAPIEmptyResponseExample />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (examples.length === 1) {
|
||||||
|
return (
|
||||||
|
<OpenAPIExample
|
||||||
|
example={firstExample.example}
|
||||||
|
context={props.context}
|
||||||
|
syntax={syntax}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabs = examples.map((example) => {
|
||||||
|
return {
|
||||||
|
key: example.key,
|
||||||
|
label: example.example.summary || example.key,
|
||||||
|
body: (
|
||||||
|
<OpenAPIExample
|
||||||
|
example={firstExample.example}
|
||||||
|
context={props.context}
|
||||||
|
syntax={syntax}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OpenAPITabs stateKey={createStateKey('response-media-type-examples')} items={tabs}>
|
||||||
|
<InteractiveSection
|
||||||
|
header={<OpenAPITabsList />}
|
||||||
|
className="openapi-response-media-type-examples"
|
||||||
|
>
|
||||||
|
<OpenAPITabsPanels />
|
||||||
|
</InteractiveSection>
|
||||||
|
</OpenAPITabs>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display an example.
|
||||||
|
*/
|
||||||
|
function OpenAPIExample(props: {
|
||||||
|
example: OpenAPIV3.ExampleObject;
|
||||||
|
context: OpenAPIContextProps;
|
||||||
|
syntax: string;
|
||||||
|
}) {
|
||||||
|
const { example, context, syntax } = props;
|
||||||
|
const code = stringifyExample({ example, xml: syntax === 'xml' });
|
||||||
|
|
||||||
|
if (code === null) {
|
||||||
|
return <OpenAPIEmptyResponseExample />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <context.CodeBlock code={code} syntax={syntax} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringifyExample(args: { example: OpenAPIV3.ExampleObject; xml: boolean }): string | null {
|
||||||
|
const { example, xml } = args;
|
||||||
|
|
||||||
|
if (!example.value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof example.value === 'string') {
|
||||||
|
return example.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xml) {
|
||||||
|
return json2xml(example.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(example.value, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the syntax from a media type.
|
||||||
|
*/
|
||||||
|
function getSyntaxFromMediaType(mediaType: string): string {
|
||||||
|
if (mediaType.includes('json')) {
|
||||||
|
return 'json';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaType === 'application/xml') {
|
||||||
|
return 'xml';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'text';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get examples from a media type object.
|
||||||
|
*/
|
||||||
|
function getExamplesFromMediaTypeObject(args: {
|
||||||
|
mediaType: string;
|
||||||
|
mediaTypeObject: OpenAPIV3.MediaTypeObject;
|
||||||
|
}): { key: string; example: OpenAPIV3.ExampleObject }[] {
|
||||||
|
const { mediaTypeObject, mediaType } = args;
|
||||||
|
if (mediaTypeObject.examples) {
|
||||||
|
return Object.entries(mediaTypeObject.examples).map(([key, example]) => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
example: checkIsReference(example) ? getExampleFromReference(example) : example,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaTypeObject.example) {
|
||||||
|
return [{ key: 'default', example: { value: mediaTypeObject.example } }];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaTypeObject.schema) {
|
||||||
|
if (mediaType === 'application/xml') {
|
||||||
|
// @TODO normally we should use the name of the schema but we don't have it
|
||||||
|
// fix it when we got the reference name
|
||||||
|
const root = mediaTypeObject.schema.xml?.name ?? 'object';
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'default',
|
||||||
|
example: {
|
||||||
|
value: {
|
||||||
|
[root]: generateSchemaExample(mediaTypeObject.schema, {
|
||||||
|
xml: mediaType === 'application/xml',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'default',
|
||||||
|
example: { value: generateSchemaExample(mediaTypeObject.schema) },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty response example.
|
||||||
|
*/
|
||||||
function OpenAPIEmptyResponseExample() {
|
function OpenAPIEmptyResponseExample() {
|
||||||
return (
|
return (
|
||||||
<pre className="openapi-response-example-empty">
|
<pre className="openapi-response-example-empty">
|
||||||
@@ -131,15 +304,9 @@ function OpenAPIEmptyResponseExample() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUnresolvedReference(
|
/**
|
||||||
input: OpenAPIV3.ExampleObject | null,
|
* Generate an example from a reference object.
|
||||||
): OpenAPIV3.ExampleObject | null {
|
*/
|
||||||
const isReference = checkIsReference(input?.value);
|
function getExampleFromReference(ref: OpenAPIV3.ReferenceObject): OpenAPIV3.ExampleObject {
|
||||||
|
return { summary: 'Unresolved reference', value: { $ref: ref.$ref } };
|
||||||
if (isReference) {
|
|
||||||
// If we find a reference that wasn't resolved or needed to be resolved externally, render out the URL
|
|
||||||
return { value: input.value.$ref };
|
|
||||||
}
|
|
||||||
|
|
||||||
return input;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// Bun Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`getUrlFromServerState indents correctly 1`] = `
|
||||||
|
"<?xml version="1.0"?>
|
||||||
|
<id>10</id>
|
||||||
|
<name>doggie</name>
|
||||||
|
<category>
|
||||||
|
<id>1</id>
|
||||||
|
<name>Dogs</name>
|
||||||
|
</category>
|
||||||
|
<photoUrls>string</photoUrls>
|
||||||
|
<tags>
|
||||||
|
<id>0</id>
|
||||||
|
<name>string</name>
|
||||||
|
</tags>
|
||||||
|
<status>available</status>
|
||||||
|
"
|
||||||
|
`;
|
||||||
@@ -3,18 +3,21 @@ import { getExampleFromSchema } from '@scalar/oas-utils/spec-getters';
|
|||||||
|
|
||||||
type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };
|
type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };
|
||||||
|
|
||||||
|
type ScalarGetExampleFromSchemaOptions = NonNullable<Parameters<typeof getExampleFromSchema>[1]>;
|
||||||
|
type GenerateSchemaExampleOptions = Pick<
|
||||||
|
ScalarGetExampleFromSchemaOptions,
|
||||||
|
'xml' | 'omitEmptyAndOptionalProperties' | 'mode'
|
||||||
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a JSON example from a schema
|
* Generate a JSON example from a schema
|
||||||
*/
|
*/
|
||||||
export function generateSchemaExample(
|
export function generateSchemaExample(
|
||||||
schema: OpenAPIV3.SchemaObject,
|
schema: OpenAPIV3.SchemaObject,
|
||||||
options: {
|
options?: GenerateSchemaExampleOptions,
|
||||||
onlyRequired?: boolean;
|
|
||||||
} = {},
|
|
||||||
): JSONValue | undefined {
|
): JSONValue | undefined {
|
||||||
return getExampleFromSchema(schema, {
|
return getExampleFromSchema(schema, {
|
||||||
emptyString: 'text',
|
emptyString: 'text',
|
||||||
omitEmptyAndOptionalProperties: options.onlyRequired,
|
|
||||||
variables: {
|
variables: {
|
||||||
'date-time': new Date().toISOString(),
|
'date-time': new Date().toISOString(),
|
||||||
date: new Date().toISOString().split('T')[0],
|
date: new Date().toISOString().split('T')[0],
|
||||||
@@ -28,6 +31,7 @@ export function generateSchemaExample(
|
|||||||
byte: 'Ynl0ZXM=',
|
byte: 'Ynl0ZXM=',
|
||||||
password: 'password',
|
password: 'password',
|
||||||
},
|
},
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,9 +40,7 @@ export function generateSchemaExample(
|
|||||||
*/
|
*/
|
||||||
export function generateMediaTypeExample(
|
export function generateMediaTypeExample(
|
||||||
mediaType: OpenAPIV3.MediaTypeObject,
|
mediaType: OpenAPIV3.MediaTypeObject,
|
||||||
options: {
|
options?: GenerateSchemaExampleOptions,
|
||||||
onlyRequired?: boolean;
|
|
||||||
} = {},
|
|
||||||
): JSONValue | undefined {
|
): JSONValue | undefined {
|
||||||
if (mediaType.example) {
|
if (mediaType.example) {
|
||||||
return mediaType.example;
|
return mediaType.example;
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { describe, expect, it } from 'bun:test';
|
||||||
|
|
||||||
|
import { json2xml } from './json2xml';
|
||||||
|
|
||||||
|
describe('getUrlFromServerState', () => {
|
||||||
|
it('transforms JSON to xml', () => {
|
||||||
|
const xml = json2xml({
|
||||||
|
foo: 'bar',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(xml).toBe('<?xml version="1.0"?>\n<foo>bar</foo>\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('wraps array items', () => {
|
||||||
|
const xml = json2xml({
|
||||||
|
urls: {
|
||||||
|
url: ['https://example.com', 'https://example.com'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(xml).toBe(
|
||||||
|
'<?xml version="1.0"?>\n<urls>\n\t<url>https://example.com</url>\n\t<url>https://example.com</url>\n</urls>\n',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('indents correctly', () => {
|
||||||
|
const xml = json2xml({
|
||||||
|
id: 10,
|
||||||
|
name: 'doggie',
|
||||||
|
category: {
|
||||||
|
id: 1,
|
||||||
|
name: 'Dogs',
|
||||||
|
},
|
||||||
|
photoUrls: ['string'],
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
name: 'string',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
status: 'available',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(xml).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { jsXml } from 'json-xml-parse';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function converts an object to XML.
|
||||||
|
*/
|
||||||
|
export function json2xml(data: Record<string, any>) {
|
||||||
|
return jsXml.toXmlString(data, { beautify: true });
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user