diff --git a/demo/examples/tests/examples.yaml b/demo/examples/tests/examples.yaml new file mode 100644 index 000000000..be999ac07 --- /dev/null +++ b/demo/examples/tests/examples.yaml @@ -0,0 +1,455 @@ +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/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 diff --git a/packages/docusaurus-plugin-openapi-docs/src/openapi/createSchemaExample.ts b/packages/docusaurus-plugin-openapi-docs/src/openapi/createSchemaExample.ts index ea0ddcf0c..f8a6b2690 100644 --- a/packages/docusaurus-plugin-openapi-docs/src/openapi/createSchemaExample.ts +++ b/packages/docusaurus-plugin-openapi-docs/src/openapi/createSchemaExample.ts @@ -56,9 +56,9 @@ const primitives: Primitives = { }, }; -type ExampleType = "request" | "response"; +export type ExampleType = "request" | "response"; -interface ExampleContext { +export interface ExampleContext { type: ExampleType; } diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/BaseSchema/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/BaseSchema/index.tsx new file mode 100644 index 000000000..3758af6fc --- /dev/null +++ b/packages/docusaurus-theme-openapi-docs/src/theme/BaseSchema/index.tsx @@ -0,0 +1,159 @@ +/* ============================================================================ + * 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 BrowserOnly from "@docusaurus/BrowserOnly"; +import Details from "@theme/Details"; +import { + ExampleFromSchema, + MimeExample, + MimeExamples, + SchemaExample, + SchemaExamples, +} from "@theme/Examples"; +import Markdown from "@theme/Markdown"; +import MimeTabs from "@theme/MimeTabs"; +import SchemaNode from "@theme/Schema"; +import SchemaTabs from "@theme/SchemaTabs"; +import SkeletonLoader from "@theme/SkeletonLoader"; +import TabItem from "@theme/TabItem"; +import { MediaTypeObject } from "docusaurus-plugin-openapi-docs/lib/openapi/types"; + +interface Props { + style?: React.CSSProperties; + title: string; + body: { + content?: { + [key: string]: MediaTypeObject; + }; + description?: string; + required?: string[] | boolean; + }; + schemaType: "request" | "response"; +} + +const BaseSchemaComponent: React.FC = ({ + title, + body, + style, + schemaType, +}) => { + if ( + body === undefined || + body.content === undefined || + Object.keys(body).length === 0 || + Object.keys(body.content).length === 0 + ) { + return null; + } + + const mimeTypes = Object.keys(body.content); + if (mimeTypes && mimeTypes.length) { + return ( + + {mimeTypes.map((mimeType: any) => { + const mimeExamples = body.content![mimeType].examples; + const mimeExample = body.content![mimeType].example; + const schemaExamples = body.content![mimeType].schema?.examples; + const schemaExample = body.content![mimeType].schema?.example; + const firstBody = body.content![mimeType].schema; + + if ( + firstBody === undefined || + (firstBody.properties && + Object.keys(firstBody.properties).length === 0) + ) { + return null; + } + + if (firstBody) { + const tabTitle = "Schema"; + return ( + // @ts-ignore + + + {/* @ts-ignore */} + +
+ + + {title} + {body.required && ( + + required + + )} + + + + } + > +
+ {body.description && ( +
+ {body.description} +
+ )} +
+
    + +
+
+
+ {firstBody && + ExampleFromSchema({ + schema: firstBody, + mimeType, + context: { type: schemaType }, + })} + + {mimeExamples && + MimeExamples({ examples: mimeExamples, mimeType })} + + {mimeExample && + MimeExample({ example: mimeExample, mimeType })} + + {schemaExamples && + SchemaExamples({ examples: schemaExamples, mimeType })} + + {schemaExample && + SchemaExample({ example: schemaExample, mimeType })} +
+
+ ); + } + return null; + })} +
+ ); + } + return null; +}; + +const BaseSchema: React.FC = (props) => { + return ( + }> + {() => { + return ; + }} + + ); +}; + +export default BaseSchema; diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ResponseExamples/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/Examples/index.tsx similarity index 56% rename from packages/docusaurus-theme-openapi-docs/src/theme/ResponseExamples/index.tsx rename to packages/docusaurus-theme-openapi-docs/src/theme/Examples/index.tsx index c0df5864e..5d8d779b7 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/ResponseExamples/index.tsx +++ b/packages/docusaurus-theme-openapi-docs/src/theme/Examples/index.tsx @@ -10,7 +10,10 @@ import React from "react"; import CodeSamples from "@theme/CodeSamples"; import Markdown from "@theme/Markdown"; import TabItem from "@theme/TabItem"; -import { sampleResponseFromSchema } from "docusaurus-plugin-openapi-docs/lib/openapi/createResponseExample"; +import { + sampleFromSchema, + ExampleContext, +} from "docusaurus-plugin-openapi-docs/lib/openapi/createSchemaExample"; import format from "xml-formatter"; export function json2xml(o: Record, tab: string): string { @@ -50,23 +53,56 @@ export function json2xml(o: Record, tab: string): string { return tab ? xml.replace(/\t/g, tab) : xml.replace(/\t|\n/g, ""); } -interface ResponseExamplesProps { - responseExamples: any; +export function getLanguageFromMimeType(mimeType: string): string { + let language = "shell"; + if (mimeType.endsWith("json")) language = "json"; + if (mimeType.endsWith("xml")) language = "xml"; + return language; +} + +export interface MimeExampleProps { + example: any; + mimeType: string; +} + +export const MimeExample: React.FC = ({ + example, + mimeType, +}) => { + const language = getLanguageFromMimeType(mimeType); + + const isObject = typeof example === "object"; + const exampleContent = isObject ? JSON.stringify(example, null, 2) : example; + + return ( + // @ts-ignore + + {example.summary && ( + + {example.summary} + + )} + + + ); +}; + +export interface MimeExamplesProps { + examples: any; mimeType: string; } -export const ResponseExamples: React.FC = ({ - responseExamples, + +export const MimeExamples: React.FC = ({ + examples, mimeType, }): any => { - let language = "shell"; - if (mimeType.endsWith("json")) language = "json"; - if (mimeType.endsWith("xml")) language = "xml"; + const language = getLanguageFromMimeType(mimeType); - // Map response examples to an array of TabItem elements - const examplesArray = Object.entries(responseExamples).map( + // Map examples to an array of TabItem elements + const examplesArray = Object.entries(examples).map( ([exampleName, exampleValue]: any) => { const isObject = typeof exampleValue.value === "object"; - const responseExample = isObject + const exampleContent = isObject ? JSON.stringify(exampleValue.value, null, 2) : exampleValue.value; @@ -78,7 +114,7 @@ export const ResponseExamples: React.FC = ({ {exampleValue.summary} )} - + ); } @@ -87,34 +123,26 @@ export const ResponseExamples: React.FC = ({ return examplesArray; }; -interface ResponseExampleProps { - responseExample: any; +export interface SchemaExampleProps { + example: any; mimeType: string; } -export const ResponseExample: React.FC = ({ - responseExample, +export const SchemaExample: React.FC = ({ + example, mimeType, }) => { - let language = "shell"; - if (mimeType.endsWith("json")) { - language = "json"; - } - if (mimeType.endsWith("xml")) { - language = "xml"; - } + const language = getLanguageFromMimeType(mimeType); - const isObject = typeof responseExample === "object"; - const exampleContent = isObject - ? JSON.stringify(responseExample, null, 2) - : responseExample; + const isObject = typeof example === "object"; + const exampleContent = isObject ? JSON.stringify(example, null, 2) : example; return ( // @ts-ignore - {responseExample.summary && ( + {example.summary && ( - {responseExample.summary} + {example.summary} )} @@ -122,35 +150,67 @@ export const ResponseExample: React.FC = ({ ); }; -interface ExampleFromSchemaProps { +export interface SchemaExamplesProps { + examples: any[]; + mimeType: string; +} + +export const SchemaExamples: React.FC = ({ + examples, + mimeType, +}) => { + const language = getLanguageFromMimeType(mimeType); + + // Map examples to an array of TabItem elements + const examplesArray = examples.map((example: any, i: number) => { + const exampleName = `Example ${i + 1}`; + const isObject = typeof example === "object"; + const exampleContent = isObject + ? JSON.stringify(example, null, 2) + : example; + + return ( + // @ts-ignore + + + + ); + }); + + return examplesArray; +}; + +export interface ExampleFromSchemaProps { schema: any; mimeType: string; + context: ExampleContext; } export const ExampleFromSchema: React.FC = ({ schema, mimeType, + context, }) => { - const responseExample = sampleResponseFromSchema(schema); + const example = sampleFromSchema(schema, context); if (mimeType.endsWith("xml")) { - let responseExampleObject; + let exampleObject; try { - responseExampleObject = JSON.parse(JSON.stringify(responseExample)); + exampleObject = JSON.parse(JSON.stringify(example)); } catch { return null; } - if (typeof responseExampleObject === "object") { + if (typeof exampleObject === "object") { let xmlExample; try { - xmlExample = format(json2xml(responseExampleObject, ""), { + xmlExample = format(json2xml(exampleObject, ""), { indentation: " ", lineSeparator: "\n", collapseContent: true, }); } catch { - const xmlExampleWithRoot = { root: responseExampleObject }; + const xmlExampleWithRoot = { root: exampleObject }; try { xmlExample = format(json2xml(xmlExampleWithRoot, ""), { indentation: " ", @@ -158,7 +218,7 @@ export const ExampleFromSchema: React.FC = ({ collapseContent: true, }); } catch { - xmlExample = json2xml(responseExampleObject, ""); + xmlExample = json2xml(exampleObject, ""); } } return ( @@ -170,15 +230,12 @@ export const ExampleFromSchema: React.FC = ({ } } - if ( - typeof responseExample === "object" || - typeof responseExample === "string" - ) { + if (typeof example === "object" || typeof example === "string") { return ( // @ts-ignore diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/Markdown/Details/_Details.scss b/packages/docusaurus-theme-openapi-docs/src/theme/Markdown/Details/_Details.scss index 96e992073..41a8f8b92 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/Markdown/Details/_Details.scss +++ b/packages/docusaurus-theme-openapi-docs/src/theme/Markdown/Details/_Details.scss @@ -24,11 +24,7 @@ /* Top-Level Details Caret Styling */ .openapi-left-panel__container > .openapi-markdown__details > summary::before, .openapi-markdown__details.mime > summary::before { - top: 0.1rem; -} - -.openapi-markdown__details.response > summary::before { - top: 0.25rem; /* TODO: figure out why this is necessary */ + top: 0.25rem; } /* End of Top-Level Details Caret Styling */ diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/RequestSchema/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/RequestSchema/index.tsx index 9eeceb4bd..00ec65e24 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/RequestSchema/index.tsx +++ b/packages/docusaurus-theme-openapi-docs/src/theme/RequestSchema/index.tsx @@ -7,13 +7,7 @@ import React from "react"; -import BrowserOnly from "@docusaurus/BrowserOnly"; -import Details from "@theme/Details"; -import Markdown from "@theme/Markdown"; -import MimeTabs from "@theme/MimeTabs"; // Assume these components exist -import SchemaNode from "@theme/Schema"; -import SkeletonLoader from "@theme/SkeletonLoader"; -import TabItem from "@theme/TabItem"; +import BaseSchema from "@theme/BaseSchema"; import { MediaTypeObject } from "docusaurus-plugin-openapi-docs/lib/openapi/types"; interface Props { @@ -28,130 +22,8 @@ interface Props { }; } -const RequestSchemaComponent: React.FC = ({ title, body, style }) => { - if ( - body === undefined || - body.content === undefined || - Object.keys(body).length === 0 || - Object.keys(body.content).length === 0 - ) { - return null; - } - - const mimeTypes = Object.keys(body.content); - - if (mimeTypes.length > 1) { - return ( - - {mimeTypes.map((mimeType) => { - const firstBody = body.content![mimeType].schema; - if ( - firstBody === undefined || - (firstBody.properties && - Object.keys(firstBody.properties).length === 0) - ) { - return null; - } - return ( - // @ts-ignore - -
- -

- {title} - {body.required === true && ( - - required - - )} -

-
- - } - > -
- {body.description && ( -
- {body.description} -
- )} -
-
    - -
-
-
- ); - })} -
- ); - } - - const randomFirstKey = mimeTypes[0]; - const firstBody = - body.content[randomFirstKey].schema ?? body.content![randomFirstKey]; - - if (firstBody === undefined) { - return null; - } - - return ( - - {/* @ts-ignore */} - -
- -

- {title} - {firstBody.type === "array" && ( - array - )} - {body.required && ( - - required - - )} -

-
- - } - > -
- {body.description && ( -
- {body.description} -
- )} -
-
    - -
-
-
-
- ); -}; - const RequestSchema: React.FC = (props) => { - return ( - }> - {() => { - return ; - }} - - ); + return ; }; export default RequestSchema; 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..023385765 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/ResponseSchema/index.tsx +++ b/packages/docusaurus-theme-openapi-docs/src/theme/ResponseSchema/index.tsx @@ -7,19 +7,7 @@ import React from "react"; -import BrowserOnly from "@docusaurus/BrowserOnly"; -import Details from "@theme/Details"; -import Markdown from "@theme/Markdown"; -import MimeTabs from "@theme/MimeTabs"; // Assume these components exist -import { - ExampleFromSchema, - ResponseExample, - ResponseExamples, -} from "@theme/ResponseExamples"; -import SchemaNode from "@theme/Schema"; -import SchemaTabs from "@theme/SchemaTabs"; -import SkeletonLoader from "@theme/SkeletonLoader"; -import TabItem from "@theme/TabItem"; +import BaseSchema from "@theme/BaseSchema"; import { MediaTypeObject } from "docusaurus-plugin-openapi-docs/lib/openapi/types"; interface Props { @@ -34,111 +22,8 @@ interface Props { }; } -const ResponseSchemaComponent: React.FC = ({ - title, - body, - style, -}): any => { - if ( - body === undefined || - body.content === undefined || - Object.keys(body).length === 0 || - Object.keys(body.content).length === 0 - ) { - return null; - } - - // Get all MIME types, including vendor-specific - const mimeTypes = Object.keys(body.content); - if (mimeTypes && mimeTypes.length) { - 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]; - - if ( - firstBody === undefined && - responseExample === undefined && - responseExamples === undefined - ) { - 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 })} -
-
- ); - } - return undefined; - })} -
- ); - } - return undefined; -}; - const ResponseSchema: React.FC = (props) => { - return ( - }> - {() => { - return ; - }} - - ); + return ; }; export default ResponseSchema;