diff --git a/demo/examples/tests/examples.yaml b/demo/examples/tests/examples.yaml new file mode 100644 index 000000000..d7f68a314 --- /dev/null +++ b/demo/examples/tests/examples.yaml @@ -0,0 +1,503 @@ +openapi: 3.1.0 +info: + title: Examples Demo API + description: Demonstrates various examples schema combinations. + version: 1.0.0 + license: + name: MIT + url: https://opensource.org/licenses/MIT +security: [] +servers: + - url: http://test.local:8080 + description: Local server +tags: + - name: examples + description: examples +paths: + /requestParameters/example: + get: + tags: + - examples + summary: example in request parameters + description: "description of request parameters example" + parameters: + - name: name + description: name example + in: query + schema: + type: string + example: "John Doe" + - name: age + description: age example + in: query + schema: + type: number + example: 25 + responses: + "204": + description: no content + + /requestParameters/examples: + get: + tags: + - examples + summary: examples in request parameters + description: "description of request parameters examples" + parameters: + - name: name + description: name example + in: query + schema: + type: string + examples: + example1: + summary: "name example 1" + description: "name example 1 description" + value: "John Doe" + example2: + summary: "name example 2" + description: "name example 2 description" + value: "Jane Smith" + responses: + "204": + description: no content + + /requestParameters/schema/example: + get: + tags: + - examples + summary: example in request parameters schema + description: "description of request parameters schema example" + parameters: + - name: name + description: name example + in: query + schema: + type: string + example: "John Doe" + - name: age + description: age example + in: query + schema: + type: number + example: 25 + responses: + "204": + description: no content + + /requestParameters/schema/examples: + get: + tags: + - examples + summary: examples in request parameters schema + description: "description of request parameters schema examples" + parameters: + - name: name + description: name example + in: query + schema: + type: string + examples: + - "John Doe" + - "Jane Smith" + - name: age + description: age example + in: query + schema: + type: number + examples: + - 25 + - 30 + responses: + "204": + description: no content + + /requestBody/mediaTypeObject/example: + post: + tags: + - examples + summary: example of media type object in requestBody + description: "description of requestBody media type object example" + requestBody: + description: "description of requestBody" + content: + application/json: + schema: + type: object + properties: + name: + type: string + age: + type: number + isStudent: + type: boolean + example: + name: "John Doe" + age: 25 + isStudent: false + responses: + "204": + description: no content + + /requestBody/mediaTypeObject/examples: + post: + tags: + - examples + summary: examples of media type object in requestBody + description: "description of requestBody media type object examples" + requestBody: + description: "description of requestBody" + content: + application/json: + schema: + type: object + properties: + name: + type: string + age: + type: number + isStudent: + type: boolean + examples: + example1: + value: + name: "John Doe" + age: 25 + isStudent: false + example2: + value: + name: "Jane Smith" + age: 30 + isStudent: true + responses: + "204": + description: no content + + /requestBody/schema/example: + post: + tags: + - examples + summary: example of schema in requestBody + description: "description of requestBody schema example" + requestBody: + description: "description of requestBody" + content: + application/json: + schema: + type: object + properties: + name: + type: string + age: + type: number + isStudent: + type: boolean + example: + name: "John Doe" + age: 25 + isStudent: false + responses: + "204": + description: no content + + /requestBody/schema/examples: + post: + tags: + - examples + summary: examples of schema in requestBody + description: "description of requestBody schema examples" + requestBody: + description: "description of requestBody" + content: + application/json: + schema: + type: object + properties: + name: + type: string + age: + type: number + isStudent: + type: boolean + examples: + - name: "John Doe" + age: 25 + isStudent: false + - name: "Jane Smith" + age: 30 + isStudent: true + responses: + "204": + description: no content + + /requestBody/schema/properties/example: + post: + tags: + - examples + summary: example of properties in requestBody schema + description: "description of requestBody schema properties example" + requestBody: + description: "description of requestBody" + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + example: "John Doe" + age: + type: number + example: 25 + isStudent: + type: boolean + example: false + responses: + "204": + description: no content + + /requestBody/schema/properties/examples: + post: + tags: + - examples + summary: examples of properties in requestBody schema + description: "description of requestBody schema properties examples" + requestBody: + description: "description of requestBody" + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + examples: + - "John Doe" + - "Jane Smith" + age: + type: number + examples: + - 25 + - 30 + isStudent: + type: boolean + examples: + - true + - false + responses: + "204": + description: no content + + /response/mediaTypeObject/example: + get: + tags: + - examples + summary: example of media type object in response + description: "description of response media type object example" + responses: + "200": + description: successful response + content: + application/json: + schema: + type: object + properties: + name: + type: string + age: + type: number + isStudent: + type: boolean + example: + name: "John Doe" + age: 25 + isStudent: false + + /response/mediaTypeObject/examples: + get: + tags: + - examples + summary: examples of media type object in response + description: "description of response media type object examples" + responses: + "200": + description: successful response + content: + application/json: + schema: + type: object + properties: + name: + type: string + age: + type: number + isStudent: + type: boolean + examples: + example1: + value: + name: "John Doe" + age: 25 + isStudent: false + example2: + value: + name: "Jane Smith" + age: 30 + isStudent: true + + /response/mediaTypeObject/noSchema: + get: + tags: + - examples + summary: no schema in response + description: "description of response media type object no schema" + responses: + "200": + description: successful response + content: + application/json: + + /response/schema/example: + get: + tags: + - examples + summary: example of schema in response + description: "description of response schema example" + responses: + "200": + description: successful response + content: + application/json: + schema: + type: object + properties: + name: + type: string + age: + type: number + isStudent: + type: boolean + example: + name: "John Doe" + age: 25 + isStudent: false + + /response/schema/examples: + get: + tags: + - examples + summary: examples of schema in response + description: "description of response schema examples" + responses: + "200": + description: successful response + content: + application/json: + schema: + type: object + properties: + name: + type: string + age: + type: number + isStudent: + type: boolean + examples: + - name: "John Doe" + age: 25 + isStudent: false + - name: "Jane Smith" + age: 30 + isStudent: true + + /response/schema/properties/example: + get: + tags: + - examples + summary: example of schema properties in response + description: "description of response schema properties example" + responses: + "200": + description: successful response + content: + application/json: + schema: + type: object + properties: + name: + type: string + example: "John Doe" + age: + type: number + example: 25 + isStudent: + type: boolean + example: false + + /response/schema/properties/examples: + get: + tags: + - examples + summary: examples of schema properties in response + description: "description of response schema properties examples" + responses: + "200": + description: successful response + content: + application/json: + schema: + type: object + properties: + name: + type: string + examples: + - "John Doe" + - "Jane Smith" + age: + type: number + examples: + - 25 + - 30 + isStudent: + type: boolean + examples: + - true + - false + + /response/schema/properties/multipleTypes/examples: + get: + tags: + - examples + summary: examples of schema properties with multiple types in response + description: "description of response schema properties with multiple types examples" + responses: + "200": + description: successful response + content: + application/json: + schema: + type: object + properties: + name: + type: + - string + - "null" + examples: + - "John Doe" + - "Jane Smith" + age: + type: + - number + - "null" + examples: + - 25 + - 30 + isStudent: + type: + - boolean + - "null" + examples: + - true + - false diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/Example/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/Example/index.tsx new file mode 100644 index 000000000..235486687 --- /dev/null +++ b/packages/docusaurus-theme-openapi-docs/src/theme/Example/index.tsx @@ -0,0 +1,133 @@ +/* ============================================================================ + * Copyright (c) Palo Alto Networks + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * ========================================================================== */ + +import React from "react"; + +import { ExampleObject } from "@theme/ParamsItem"; +import SchemaTabs from "@theme/SchemaTabs"; +import TabItem from "@theme/TabItem"; + +/** + * Format example value + * + * @param example + * @returns + */ +const formatExample = (example: any) => { + if (typeof example === "object" && example !== null) { + return JSON.stringify(example); + } + return String(example); +}; + +/** + * Render string examples + * + * @param examples + * @returns + */ +export function renderStringExamples( + examples: string[] | undefined +): React.JSX.Element | undefined { + if (examples && examples.length > 0) { + // If there's only one example, display it without tabs + if (examples.length === 1) { + return ( +
+ Example: + + {formatExample(examples[0])} + +
+ ); + } + + // Multiple examples - use tabs + return ( +
+ Examples: + + {examples.map((example, index) => ( + // @ts-ignore + +

+ Example: + {formatExample(example)} +

+
+ ))} +
+
+ ); + } + return undefined; +} + +export const renderExamplesRecord = ( + examples: Record +) => { + const exampleEntries = Object.entries(examples); + // If there's only one example, display it without tabs + if (exampleEntries.length === 1) { + const firstExample = exampleEntries[0][1]; + if (!firstExample) { + return undefined; + } + return ( +
+ Example: + + {formatExample(firstExample.value)} + +
+ ); + } + + return ( + <> + Examples: + + {exampleEntries.map(([exampleName, exampleProperties]) => + renderExampleObject(exampleName, exampleProperties) + )} + + + ); +}; + +/** + * Render example object + * + * @param exampleName + * @param exampleProperties + * @returns + */ +const renderExampleObject = ( + exampleName: string, + exampleProperties: ExampleObject +) => { + return ( + // @ts-ignore + + {exampleProperties.summary &&

{exampleProperties.summary}

} + {exampleProperties.description && ( +

+ Description: + {exampleProperties.description} +

+ )} +

+ Example: + {formatExample(exampleProperties.value)} +

+
+ ); +}; diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ParamsItem/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/ParamsItem/index.tsx index 863508e3e..8921df95e 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/ParamsItem/index.tsx +++ b/packages/docusaurus-theme-openapi-docs/src/theme/ParamsItem/index.tsx @@ -7,6 +7,7 @@ import React from "react"; +import { renderExamplesRecord, renderStringExamples } from "@theme/Example"; import Markdown from "@theme/Markdown"; import SchemaTabs from "@theme/SchemaTabs"; import TabItem from "@theme/TabItem"; @@ -28,7 +29,7 @@ export interface Props { param: { description: string; example: any; - examples: Record; + examples: Record | undefined; name: string; required: boolean; deprecated: boolean; @@ -53,19 +54,14 @@ ${enumDescriptions }; function ParamsItem({ param, ...rest }: Props) { - const { - description, - example, - examples, - name, - required, - deprecated, - enumDescriptions, - } = param; + const { description, name, required, deprecated, enumDescriptions } = param; let schema = param.schema; let defaultValue: string | undefined; + let examples = param.examples || (schema?.examples as any[] | undefined); + let example = param.example || schema?.example; + if (!schema) { schema = { type: "any" }; } @@ -140,35 +136,18 @@ function ParamsItem({ param, ...rest }: Props) { const renderExample = guard(toString(example), (example) => (
Example: - {example} + {example}
)); const renderExamples = guard(examples, (examples) => { - const exampleEntries = Object.entries(examples); - return ( - <> - Examples: - - {exampleEntries.map(([exampleName, exampleProperties]) => ( - // @ts-ignore - - {exampleProperties.summary &&

{exampleProperties.summary}

} - {exampleProperties.description && ( -

- Description: - {exampleProperties.description} -

- )} -

- Example: - {exampleProperties.value} -

-
- ))} -
- - ); + if (!examples) { + return undefined; + } + if (Array.isArray(examples)) { + return renderStringExamples(examples); + } + return renderExamplesRecord(examples); }); return ( diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ResponseSchema/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/ResponseSchema/index.tsx index ac4a64954..9e0cddce3 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/ResponseSchema/index.tsx +++ b/packages/docusaurus-theme-openapi-docs/src/theme/ResponseSchema/index.tsx @@ -54,76 +54,78 @@ const ResponseSchemaComponent: React.FC = ({ return ( {mimeTypes.map((mimeType: any) => { - const responseExamples = body.content![mimeType].examples; - const responseExample = body.content![mimeType].example; - const firstBody: any = - body.content![mimeType].schema ?? body.content![mimeType]; + const mediaTypeObject = body.content?.[mimeType]; + const responseExamples = mediaTypeObject?.examples; + const responseExample = mediaTypeObject?.example; + const firstBody = mediaTypeObject?.schema; if ( - firstBody === undefined && - responseExample === undefined && - responseExamples === undefined + !firstBody || + (firstBody.properties && + Object.keys(firstBody.properties).length === 0) ) { - return undefined; - } - - if (firstBody) { return ( // @ts-ignore - - {/* @ts-ignore */} - -
- - - {title} - {body.required === true && ( - - required - - )} - - - - } - > -
- {body.description && ( -
- {body.description} -
- )} -
-
    - -
-
-
- {firstBody && - ExampleFromSchema({ - schema: firstBody, - mimeType: mimeType, - })} - - {responseExamples && - ResponseExamples({ responseExamples, mimeType })} - - {responseExample && - ResponseExample({ responseExample, mimeType })} -
+
No schema
); } - return undefined; + + return ( + // @ts-ignore + + + {/* @ts-ignore */} + +
+ + + {title} + {body.required === true && ( + + required + + )} + + + + } + > +
+ {body.description && ( +
+ {body.description} +
+ )} +
+
    + +
+
+
+ {firstBody && + ExampleFromSchema({ + schema: firstBody, + mimeType: mimeType, + })} + + {responseExamples && + ResponseExamples({ responseExamples, mimeType })} + + {responseExample && + ResponseExample({ responseExample, mimeType })} +
+
+ ); })}
); diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/SchemaItem/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/SchemaItem/index.tsx index a77d1f3ee..de0a7e7b1 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/SchemaItem/index.tsx +++ b/packages/docusaurus-theme-openapi-docs/src/theme/SchemaItem/index.tsx @@ -7,6 +7,7 @@ import React, { ReactNode } from "react"; +import { renderStringExamples } from "@theme/Example"; import Markdown from "@theme/Markdown"; import clsx from "clsx"; @@ -63,6 +64,7 @@ export default function SchemaItem(props: Props) { let schemaDescription; let defaultValue: string | undefined; let example: string | undefined; + let examples: string[] | undefined; let nullable; let enumDescriptions: [string, string][] = []; let constValue: string | undefined; @@ -73,6 +75,7 @@ export default function SchemaItem(props: Props) { enumDescriptions = transformEnumDescriptions(schema["x-enumDescriptions"]); defaultValue = schema.default; example = schema.example; + examples = schema.examples; nullable = schema.nullable || (Array.isArray(schema.type) && schema.type.includes("null")); // support JSON Schema nullable @@ -213,6 +216,7 @@ export default function SchemaItem(props: Props) { {renderConstValue()} {renderDefaultValue()} {renderExample()} + {renderStringExamples(examples)} {collapsibleSchemaContent ?? collapsibleSchemaContent} );