} className="openapi-response-example">
@@ -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 (
+
+ );
+ }
+
+ const tabs = entries.map((entry) => {
+ const [mediaType, mediaTypeObject] = entry;
+ return {
+ key: mediaType,
+ label: mediaType,
+ body: (
+
+ ),
+ };
+ });
+
+ return (
+
+ }
+ className="openapi-response-media-types"
+ >
+
+
+
+ );
+}
+
+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 ;
+ }
+
+ if (examples.length === 1) {
+ return (
+
+ );
+ }
+
+ const tabs = examples.map((example) => {
+ return {
+ key: example.key,
+ label: example.example.summary || example.key,
+ body: (
+
+ ),
+ };
+ });
+
+ return (
+
+ }
+ className="openapi-response-media-type-examples"
+ >
+
+
+
+ );
+}
+
+/**
+ * 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 ;
+ }
+
+ return ;
+}
+
+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() {
return (
@@ -131,15 +304,9 @@ function OpenAPIEmptyResponseExample() {
);
}
-function handleUnresolvedReference(
- input: OpenAPIV3.ExampleObject | null,
-): OpenAPIV3.ExampleObject | null {
- const isReference = checkIsReference(input?.value);
-
- 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;
+/**
+ * Generate an example from a reference object.
+ */
+function getExampleFromReference(ref: OpenAPIV3.ReferenceObject): OpenAPIV3.ExampleObject {
+ return { summary: 'Unresolved reference', value: { $ref: ref.$ref } };
}
diff --git a/packages/react-openapi/src/__snapshots__/json2xml.test.ts.snap b/packages/react-openapi/src/__snapshots__/json2xml.test.ts.snap
new file mode 100644
index 000000000..d7ac3e47c
--- /dev/null
+++ b/packages/react-openapi/src/__snapshots__/json2xml.test.ts.snap
@@ -0,0 +1,18 @@
+// Bun Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`getUrlFromServerState indents correctly 1`] = `
+"
+10
+doggie
+
+ 1
+ Dogs
+
+string
+
+ 0
+ string
+
+available
+"
+`;
diff --git a/packages/react-openapi/src/generateSchemaExample.ts b/packages/react-openapi/src/generateSchemaExample.ts
index 9d9c26acd..c00cb55d3 100644
--- a/packages/react-openapi/src/generateSchemaExample.ts
+++ b/packages/react-openapi/src/generateSchemaExample.ts
@@ -3,18 +3,21 @@ import { getExampleFromSchema } from '@scalar/oas-utils/spec-getters';
type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };
+type ScalarGetExampleFromSchemaOptions = NonNullable[1]>;
+type GenerateSchemaExampleOptions = Pick<
+ ScalarGetExampleFromSchemaOptions,
+ 'xml' | 'omitEmptyAndOptionalProperties' | 'mode'
+>;
+
/**
* Generate a JSON example from a schema
*/
export function generateSchemaExample(
schema: OpenAPIV3.SchemaObject,
- options: {
- onlyRequired?: boolean;
- } = {},
+ options?: GenerateSchemaExampleOptions,
): JSONValue | undefined {
return getExampleFromSchema(schema, {
emptyString: 'text',
- omitEmptyAndOptionalProperties: options.onlyRequired,
variables: {
'date-time': new Date().toISOString(),
date: new Date().toISOString().split('T')[0],
@@ -28,6 +31,7 @@ export function generateSchemaExample(
byte: 'Ynl0ZXM=',
password: 'password',
},
+ ...options,
});
}
@@ -36,9 +40,7 @@ export function generateSchemaExample(
*/
export function generateMediaTypeExample(
mediaType: OpenAPIV3.MediaTypeObject,
- options: {
- onlyRequired?: boolean;
- } = {},
+ options?: GenerateSchemaExampleOptions,
): JSONValue | undefined {
if (mediaType.example) {
return mediaType.example;
diff --git a/packages/react-openapi/src/json2xml.test.ts b/packages/react-openapi/src/json2xml.test.ts
new file mode 100644
index 000000000..ac2147fd7
--- /dev/null
+++ b/packages/react-openapi/src/json2xml.test.ts
@@ -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('\nbar\n');
+ });
+
+ it('wraps array items', () => {
+ const xml = json2xml({
+ urls: {
+ url: ['https://example.com', 'https://example.com'],
+ },
+ });
+
+ expect(xml).toBe(
+ '\n\n\thttps://example.com\n\thttps://example.com\n\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();
+ });
+});
diff --git a/packages/react-openapi/src/json2xml.ts b/packages/react-openapi/src/json2xml.ts
new file mode 100644
index 000000000..44501251e
--- /dev/null
+++ b/packages/react-openapi/src/json2xml.ts
@@ -0,0 +1,8 @@
+import { jsXml } from 'json-xml-parse';
+
+/**
+ * This function converts an object to XML.
+ */
+export function json2xml(data: Record) {
+ return jsXml.toXmlString(data, { beautify: true });
+}