From 1784940b2f0d8d1a8322539f790c79e44d59fb31 Mon Sep 17 00:00:00 2001 From: lmieulet Date: Fri, 8 Mar 2024 14:54:34 +0100 Subject: [PATCH 1/2] Improve types, doc, allow spec on ApiSchema, fix showExample --- package.json | 5 +- packages/docusaurus-plugin-redoc/src/index.ts | 18 +- .../src/theme/ApiDocMdx/ApiDocMdx.tsx | 4 +- .../src/theme/ApiSchema/ApiSchema.tsx | 14 +- .../src/theme/Redoc/Redoc.tsx | 13 +- .../src/theme/Redoc/ServerRedoc.tsx | 2 +- .../src/theme/useSpecData.ts | 18 - .../src/types/common.ts | 42 +- .../src/utils/useSpec.ts | 8 +- .../src/utils/useSpecData.ts | 29 + .../src/utils/useSpecOptions.ts | 2 +- website/docs/guides/build-time-rendering.md | 2 +- website/docs/guides/component-api-schema.mdx | 137 ++ website/docs/guides/component-redoc.mdx | 65 + website/docs/guides/migrating-to-v1.md | 2 +- website/docs/guides/multiple-apis.md | 2 +- website/docs/guides/schema-imports.mdx | 94 - .../single-json-file/api-with-examples.json | 1720 +++++++++++++++++ 18 files changed, 2021 insertions(+), 156 deletions(-) delete mode 100644 packages/docusaurus-theme-redoc/src/theme/useSpecData.ts create mode 100644 packages/docusaurus-theme-redoc/src/utils/useSpecData.ts create mode 100644 website/docs/guides/component-api-schema.mdx create mode 100644 website/docs/guides/component-redoc.mdx delete mode 100644 website/docs/guides/schema-imports.mdx create mode 100644 website/openapi/single-json-file/api-with-examples.json diff --git a/package.json b/package.json index a2d2385b..50ec7589 100644 --- a/package.json +++ b/package.json @@ -79,5 +79,8 @@ "printWidth": 80, "tabWidth": 2 }, - "packageManager": "yarn@4.0.1" + "packageManager": "yarn@4.0.1", + "volta": { + "node": "18.12.0" + } } diff --git a/packages/docusaurus-plugin-redoc/src/index.ts b/packages/docusaurus-plugin-redoc/src/index.ts index b7d6991e..04a5a947 100644 --- a/packages/docusaurus-plugin-redoc/src/index.ts +++ b/packages/docusaurus-plugin-redoc/src/index.ts @@ -24,7 +24,7 @@ import { PluginDirectUsageOptions, DEFAULT_OPTIONS, } from './options'; -import type { SpecProps, ApiDocProps } from './types/common'; +import type { SpecPropsWithUrl, ApiDocProps } from './types/common'; import { loadSpecWithConfig } from './loadSpec'; import { loadRedoclyConfig } from './loadRedoclyConfig'; @@ -33,6 +33,10 @@ const version = require('../package.json').version; export { PluginOptions, PluginDirectUsageOptions, loadRedoclyConfig }; +function getIsExternalUrl(url = '') { + return ['http://', 'https://'].some((protocol) => url.startsWith(protocol)); +} + export default function redocPlugin( context: LoadContext, opts: PluginOptions, @@ -45,12 +49,13 @@ export default function redocPlugin( const { debug, spec, url: downloadUrl, config, themeId } = options; let url = downloadUrl; - const isSpecFile = fs.existsSync(spec); + const isExternalUrl = getIsExternalUrl(url); + const fileName = path.join( 'redocusaurus', `${options.id || 'api-spec'}.yaml`, ); - let filesToWatch: string[] = isSpecFile ? [path.resolve(spec)] : []; + let filesToWatch: string[] = !isExternalUrl ? [path.resolve(spec)] : []; if (debug) { console.error('[REDOCUSAURUS_PLUGIN] Opts Input:', opts); @@ -67,7 +72,7 @@ export default function redocPlugin( let bundledSpec: Document, problems: NormalizedProblem[]; - if (!isSpecFile) { + if (isExternalUrl) { // If spec is a remote url then add it as download url also as a default url = url || spec; if (debug) { @@ -123,10 +128,9 @@ export default function redocPlugin( throw new Error(`[Redocusaurus] Spec could not be parsed: ${spec}`); } - const data: SpecProps = { + const data: SpecPropsWithUrl = { url, themeId, - isSpecFile, // eslint-disable-next-line @typescript-eslint/no-explicit-any spec: content.converted as any, }; @@ -165,7 +169,7 @@ export default function redocPlugin( } }, async postBuild({ content }) { - if (!isSpecFile || downloadUrl) { + if (isExternalUrl || downloadUrl) { return; } // Create a static file from bundled spec diff --git a/packages/docusaurus-theme-redoc/src/theme/ApiDocMdx/ApiDocMdx.tsx b/packages/docusaurus-theme-redoc/src/theme/ApiDocMdx/ApiDocMdx.tsx index a6e69c17..d4f29394 100644 --- a/packages/docusaurus-theme-redoc/src/theme/ApiDocMdx/ApiDocMdx.tsx +++ b/packages/docusaurus-theme-redoc/src/theme/ApiDocMdx/ApiDocMdx.tsx @@ -1,10 +1,10 @@ import React, { useMemo } from 'react'; import Redoc from '@theme/Redoc'; import useSpecData from '@theme/useSpecData'; -import { MdxProps as Props } from '../../types/common'; +import { MdxProps } from '../../types/common'; import '../ApiSchema/styles.css'; -const ApiDocMdx: React.FC = ({ id }: Props): JSX.Element => { +const ApiDocMdx: React.FC = ({ id }: MdxProps): JSX.Element => { const specProps = useSpecData(id); const optionsOverrides = useMemo(() => { return { diff --git a/packages/docusaurus-theme-redoc/src/theme/ApiSchema/ApiSchema.tsx b/packages/docusaurus-theme-redoc/src/theme/ApiSchema/ApiSchema.tsx index 05882aa3..e90b1b73 100644 --- a/packages/docusaurus-theme-redoc/src/theme/ApiSchema/ApiSchema.tsx +++ b/packages/docusaurus-theme-redoc/src/theme/ApiSchema/ApiSchema.tsx @@ -3,20 +3,17 @@ import clsx from 'clsx'; import { ThemeProvider } from 'styled-components'; import '../../global'; import { SchemaDefinition } from 'redoc'; -import { useSpec } from '../../utils/useSpec'; -import { useSpecData } from '../useSpecData'; +import useSpec from '../../utils/useSpec'; import { ApiSchemaProps as Props } from '../../types/common'; import '../Redoc/styles.css'; import './styles.css'; const ApiSchema: React.FC = ({ - id, - example, + showExample, pointer, ...rest }: Props): JSX.Element => { - const specProps = useSpecData(id); - const { store } = useSpec(specProps); + const { store } = useSpec(rest); useEffect(() => { /** @@ -31,13 +28,14 @@ const ApiSchema: React.FC = ({ className={clsx([ 'redocusaurus', 'redocusaurus-schema', - example ? null : 'hide-example', + showExample ? null : 'hide-example', ])} > @@ -46,7 +44,7 @@ const ApiSchema: React.FC = ({ }; ApiSchema.defaultProps = { - example: false, + showExample: false, }; export default ApiSchema; diff --git a/packages/docusaurus-theme-redoc/src/theme/Redoc/Redoc.tsx b/packages/docusaurus-theme-redoc/src/theme/Redoc/Redoc.tsx index ba97a079..0d2149ba 100644 --- a/packages/docusaurus-theme-redoc/src/theme/Redoc/Redoc.tsx +++ b/packages/docusaurus-theme-redoc/src/theme/Redoc/Redoc.tsx @@ -1,9 +1,9 @@ import React from 'react'; import clsx from 'clsx'; import '../../global'; -import { RedocStandalone, RedocRawOptions } from 'redoc'; -import { SpecProps } from '../../types/common'; -import { useSpecOptions } from '../../utils/useSpecOptions'; +import { RedocStandalone } from 'redoc'; +import { RedocProps } from '../../types/common'; +import useSpecOptions from '../../utils/useSpecOptions'; import './styles.css'; import ServerRedoc from './ServerRedoc'; @@ -13,12 +13,7 @@ import ServerRedoc from './ServerRedoc'; * (c) 2024 Rohit Gohri * Released under the MIT License */ -function Redoc( - props: Partial & { - className?: string; - optionsOverrides?: RedocRawOptions; - }, -): JSX.Element { +function Redoc(props: RedocProps): JSX.Element { const { className, optionsOverrides, spec, url, themeId, isSpecFile } = props; const { options } = useSpecOptions(themeId, optionsOverrides); const isDevMode = process.env.NODE_ENV === 'development'; diff --git a/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerRedoc.tsx b/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerRedoc.tsx index ca940fd8..c15c55c8 100644 --- a/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerRedoc.tsx +++ b/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerRedoc.tsx @@ -3,7 +3,7 @@ import clsx from 'clsx'; import '../../global'; import { Redoc as RedocComponent, RedocRawOptions } from 'redoc'; import { SpecProps } from '../../types/common'; -import { useSpec } from '../../utils/useSpec'; +import useSpec from '../../utils/useSpec'; import { ServerStyles } from './Styles'; import './styles.css'; diff --git a/packages/docusaurus-theme-redoc/src/theme/useSpecData.ts b/packages/docusaurus-theme-redoc/src/theme/useSpecData.ts deleted file mode 100644 index e8ecd96e..00000000 --- a/packages/docusaurus-theme-redoc/src/theme/useSpecData.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { useAllPluginInstancesData } from '@docusaurus/useGlobalData'; -import { SpecProps } from '../types/common'; - -/** - * - * @param id ID of plugin data - * @returns Spec Data of ID or first one if ID is not provided - */ -export function useSpecData(id?: string): SpecProps { - const allData = useAllPluginInstancesData('docusaurus-plugin-redoc'); - const apiData = id - ? allData?.[id as string] - : Object.values(allData ?? {})?.[0]; - - return apiData as SpecProps; -} - -export default useSpecData; diff --git a/packages/docusaurus-theme-redoc/src/types/common.ts b/packages/docusaurus-theme-redoc/src/types/common.ts index db69f65a..3d7c3eca 100644 --- a/packages/docusaurus-theme-redoc/src/types/common.ts +++ b/packages/docusaurus-theme-redoc/src/types/common.ts @@ -1,17 +1,45 @@ import type { Props as LayoutProps } from '@theme/Layout'; -import type { ObjectDescriptionProps } from 'redoc'; +import type { ObjectDescriptionProps, RedocRawOptions } from 'redoc'; import type { OpenAPISpec } from 'redoc/typings/types'; export type ParsedSpec = OpenAPISpec; export interface SpecProps { + /** + * Spec to use, already loaded previously + */ spec: ParsedSpec; - url?: string; - isSpecFile?: boolean; + /** + * When spec not provided, load the spec from docusaurus config + * fallback to first configuration if not provided + */ + id?: string; + /** + * docusaurus theme to use + */ themeId?: string; } -export type RedocProps = SpecProps; +export type SpecPropsWithUrl = Omit & { + /** + * Absolute path to the spec file used + */ + url?: string; +}; + +export type RedocProps = SpecProps & { + /** + * FIXME - incorrect name - should be isExternalUrl + */ + isSpecFile?: boolean; + className?: string; + optionsOverrides?: RedocRawOptions; + /** + * External URL to load spec file from + * FIXME - incorrect name - should be externalUrl + */ + url?: string; +}; export interface MdxProps { /** @@ -20,20 +48,16 @@ export interface MdxProps { */ id?: string; } - export type ApiSchemaProps = Omit< ObjectDescriptionProps, 'parser' | 'options' | 'schemaRef' > & MdxProps & { - /** - * Show the example or not - */ - example?: boolean; /** * Ref to the schema */ pointer: ObjectDescriptionProps['schemaRef']; + spec: ParsedSpec; }; export type ApiDocProps = { diff --git a/packages/docusaurus-theme-redoc/src/utils/useSpec.ts b/packages/docusaurus-theme-redoc/src/utils/useSpec.ts index fc10853e..bf2385b1 100644 --- a/packages/docusaurus-theme-redoc/src/utils/useSpec.ts +++ b/packages/docusaurus-theme-redoc/src/utils/useSpec.ts @@ -5,7 +5,8 @@ import { useColorMode } from '@docusaurus/theme-common'; import '../global'; import { AppStore, RedocRawOptions } from 'redoc'; import { SpecProps } from '../types/common'; -import { useSpecOptions } from './useSpecOptions'; +import useSpecOptions from './useSpecOptions'; +import useSpecData from './useSpecData'; // the current store singleton in the app's instance let currentStore: AppStore | null = null; @@ -16,10 +17,11 @@ let currentStore: AppStore | null = null; * (c) 2024 Rohit Gohri * Released under the MIT License */ -export function useSpec( - { spec, url, themeId }: SpecProps, +export default function useSpec( + specInfo: SpecProps, optionsOverrides?: RedocRawOptions, ) { + const { spec, url, themeId } = useSpecData(specInfo); const specOptions = useSpecOptions(themeId, optionsOverrides); const fullUrl = useBaseUrl(url, { absolute: true }); const isBrowser = useIsBrowser(); diff --git a/packages/docusaurus-theme-redoc/src/utils/useSpecData.ts b/packages/docusaurus-theme-redoc/src/utils/useSpecData.ts new file mode 100644 index 00000000..4fb88f79 --- /dev/null +++ b/packages/docusaurus-theme-redoc/src/utils/useSpecData.ts @@ -0,0 +1,29 @@ +import { useAllPluginInstancesData } from '@docusaurus/useGlobalData'; +import type { OpenAPISpec } from 'redoc/typings/types'; +import { SpecProps, SpecPropsWithUrl } from '../types/common'; + +export type ParsedSpec = OpenAPISpec; + +/** + * Retrive the spec data to give to Redoc + * if providedSpec.spec is provided, use it + * otherwise use Spec Data using docusaurus config + * using providedSpec.id (or first one if not provided) + * + * @param providedSpec spec data + * @returns Spec Data of ID or first one if ID is not provided + */ +export default function useSpecData(providedSpec: SpecProps): SpecPropsWithUrl { + const allData = useAllPluginInstancesData('docusaurus-plugin-redoc'); + if (providedSpec.spec) { + // return provided spec when already defined + return providedSpec as SpecPropsWithUrl; + } else { + // retrieve spec from docusaurus conf + const id = providedSpec.id; + const apiData = id + ? allData?.[id as string] + : Object.values(allData ?? {})?.[0]; + return apiData as SpecPropsWithUrl; + } +} diff --git a/packages/docusaurus-theme-redoc/src/utils/useSpecOptions.ts b/packages/docusaurus-theme-redoc/src/utils/useSpecOptions.ts index a50c2e76..676f5f67 100644 --- a/packages/docusaurus-theme-redoc/src/utils/useSpecOptions.ts +++ b/packages/docusaurus-theme-redoc/src/utils/useSpecOptions.ts @@ -17,7 +17,7 @@ import { GlobalData } from '../types/options'; * (c) 2024 Rohit Gohri * Released under the MIT License */ -export function useSpecOptions( +export default function useSpecOptions( themeId: SpecProps['themeId'] = 'theme-redoc', optionsOverrides?: RedocRawOptions, ) { diff --git a/website/docs/guides/build-time-rendering.md b/website/docs/guides/build-time-rendering.md index 8dff201a..e6516dba 100644 --- a/website/docs/guides/build-time-rendering.md +++ b/website/docs/guides/build-time-rendering.md @@ -1,7 +1,7 @@ --- title: Build Time Rendering description: Parse the OpenAPI schema at build time and skip the loading screen -sidebar_position: 3 +sidebar_position: 4 --- :::warning diff --git a/website/docs/guides/component-api-schema.mdx b/website/docs/guides/component-api-schema.mdx new file mode 100644 index 00000000..10811d03 --- /dev/null +++ b/website/docs/guides/component-api-schema.mdx @@ -0,0 +1,137 @@ +--- +title: Component ApiSchema +sidebar_position: 2 +--- + +import ApiSchema from '@theme/ApiSchema'; +import openApi from '../../openapi/single-json-file/api-with-examples.json' + +# ApiSchema + +You can display model definitions from your API schema and render them in your Docusaurus Docs. You'll need to create an `.mdx` file and import the React Component. Read more [here about MDX in Docusaurus](https://docusaurus.io/docs/markdown-features/react). + +:::info File format +You cannot import a React component inside a `.md` file. +Change your file extension to `.mdx` before importing the React Component. +Read more [here about MDX in Docusaurus](https://docusaurus.io/docs/markdown-features/react). +::: + +## Import + +```tsx +import ApiSchema from '@theme/ApiSchema'; +``` + +## Props + +| Name | Type | Description | +|-------------|--------------|------------------------------------------------------------------------------------------------------| +| pointer | String | the redoc reference to display in [Redoc](https://redoc.ly/docs/resources/ref-guide/#pointer) format | +| showExample | boolean | (default: false) allow to display example | +| id | String | When spec not provided, load the spec from docusaurus config. Use first spec if not defined | +| spec | OpenAPI spec | A JSON content spec to use | +| themeId | String | redocusaurus theme to use - default to `theme-redoc` | + +## Example + + +### Basic example + +The `pointer` prop is passed on to [Redoc](https://redoc.ly/docs/resources/ref-guide/#pointer). +It displays here the first element of the redocusaurus configuration. + +```tsx +import ApiSchema from '@theme/ApiSchema'; + + +``` + + + +### Display example + +```tsx +import ApiSchema from '@theme/ApiSchema'; + + +``` + + + +### Multiple OpenAPI schemas example + +If you have multiple APIs loaded with redocusaurus, then it is recommended to add `id`s to the config so that you can refer them when loading schema models. + +```js title="docusaurus.config.js" +const config = { + presets: [ + '@docusaurus/preset-classic', + [ + 'redocusaurus', + { + specs: [ + { + id: 'using-single-yaml', + spec: 'openapi/single-file/openapi.yaml', + route: '/examples/using-single-yaml/', + }, + { + id: 'using-remote-url', + spec: 'https://redocly.github.io/redoc/openapi.yaml', + route: '/examples/using-remote-url/', + }, + ], + theme: { + /** + * Highlight color for docs + */ + primaryColor: '#1890ff', + /** + * Options to pass to redoc + * @see https://github.com/redocly/redoc#redoc-options-object + */ + options: { disableSearch: true }, + }, + }, + ], + ], + title: 'Redocusaurus', +}; +``` + +```tsx +import ApiSchema from '@theme/ApiSchema'; + + + +``` + +#### Results for ID `id="using-single-yaml"` + + + +#### Results for ID `id="using-remote-url"` + + + +### Webpack loader example + +You can provide a JSON spec to the component like this. Webpack will load the file directly, +you don't need to use docusaurus configuration. + +```tsx +import ApiSchema from '@theme/ApiSchema'; +import openApi from './api-with-examples.json' + + +``` + + + +:::info YAML support +You cannot load yaml file like this: +```tsx +import openApi from './api-with-examples.yaml' +``` +Without the right webpack configuration to handle such file format. +::: diff --git a/website/docs/guides/component-redoc.mdx b/website/docs/guides/component-redoc.mdx new file mode 100644 index 00000000..68264bba --- /dev/null +++ b/website/docs/guides/component-redoc.mdx @@ -0,0 +1,65 @@ +--- +title: Component Redoc +sidebar_position: 1 +--- + +import Redoc from '@theme/Redoc'; +import openApi from '../../openapi/single-json-file/api-with-examples.json' + +# Redoc + +You can display the whole OpenAPI documentation using a React component + +:::info File format +You cannot import a React component inside a `.md` file. +Change your file extension to `.mdx` before importing the React Component. +Read more [here about MDX in Docusaurus](https://docusaurus.io/docs/markdown-features/react). +::: + +## Import + +```tsx +import Redoc from '@theme/Redoc'; +``` + +## Props + +| Name | Type | Description | +|------|--------------|-------------------------------------| +| spec | OpenAPI spec | A JSON content spec to use | +| url | String | External URL to load spec file from | + +## Example + +### External URL example + +```tsx +import Redoc from '@theme/Redoc'; + + +``` + + + +### Webpack loader example + +You can provide a JSON spec to the component like this. Webpack will load the file directly, +you don't need to use docusaurus configuration. + +```tsx +import Redoc from '@theme/Redoc'; +import openApi from './api-with-examples.json' + + +``` + + + +:::info YAML support +You cannot load yaml file like this: +```tsx +import openApi from './api-with-examples.yaml' +``` +Without the right webpack configuration to handle such file format. +::: + diff --git a/website/docs/guides/migrating-to-v1.md b/website/docs/guides/migrating-to-v1.md index d3380fc5..68046436 100644 --- a/website/docs/guides/migrating-to-v1.md +++ b/website/docs/guides/migrating-to-v1.md @@ -1,6 +1,6 @@ --- title: Migrating to V1 -sidebar_position: 4 +sidebar_position: 5 --- ## Options Changed diff --git a/website/docs/guides/multiple-apis.md b/website/docs/guides/multiple-apis.md index b872f571..29e3224e 100644 --- a/website/docs/guides/multiple-apis.md +++ b/website/docs/guides/multiple-apis.md @@ -1,6 +1,6 @@ --- title: Showing Multiple APIs -sidebar_position: 2 +sidebar_position: 3 --- ## Nested View with MDX diff --git a/website/docs/guides/schema-imports.mdx b/website/docs/guides/schema-imports.mdx deleted file mode 100644 index 544707dc..00000000 --- a/website/docs/guides/schema-imports.mdx +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: Schema Imports -sidebar_position: 1 ---- - -import ApiSchema from '@theme/ApiSchema'; - -# Schema Imports - -You can import model definitions from your API schema and render them in your Docusaurus Docs. You'll need to create an `.mdx` file and import the React Component. Read more [here about MDX in Docusaurus](https://docusaurus.io/docs/markdown-features/react). - -# Import Schema Model in Docs - -The `pointer` prop is passed on to [Redoc](https://redoc.ly/docs/resources/ref-guide/#pointer). - -```tsx -import ApiSchema from '@theme/ApiSchema'; - -; -``` - -### Results - - - -## Import Schema Model (with example) in Docs - -```tsx -import ApiSchema from '@theme/ApiSchema'; - -; -``` - -### Results - - - -## Importing Schema Model with multiple OpenAPI schemas - -If you have multiple APIs loaded with redocusaurus, then it is recommended to add `id`s to the config so that you can refer them when loading schema models. - -```js title="docusaurus.config.js" -const config = { - presets: [ - '@docusaurus/preset-classic', - [ - 'redocusaurus', - { - specs: [ - { - id: 'using-single-yaml', - spec: 'openapi/single-file/openapi.yaml', - route: '/examples/using-single-yaml/', - }, - { - id: 'using-remote-url', - spec: 'https://redocly.github.io/redoc/openapi.yaml', - route: '/examples/using-remote-url/', - }, - ], - theme: { - /** - * Highlight color for docs - */ - primaryColor: '#1890ff', - /** - * Options to pass to redoc - * @see https://github.com/redocly/redoc#redoc-options-object - */ - options: { disableSearch: true }, - }, - }, - ], - ], - title: 'Redocusaurus', -}; -``` - -```tsx -import ApiSchema from '@theme/ApiSchema'; - -; -; -``` - -### Results - -#### For ID `id="using-single-yaml"` - - - -#### For ID `id="using-remote-url"` - - diff --git a/website/openapi/single-json-file/api-with-examples.json b/website/openapi/single-json-file/api-with-examples.json new file mode 100644 index 00000000..4e83fd3a --- /dev/null +++ b/website/openapi/single-json-file/api-with-examples.json @@ -0,0 +1,1720 @@ +{ + "openapi": "3.0.0", + "servers": [ + { + "url": "//petstore.swagger.io/v2", + "description": "Default server" + }, + { + "url": "//petstore.swagger.io/sandbox", + "description": "Sandbox server" + } + ], + "info": { + "description": "This is a sample server Petstore server.\nYou can find out more about Swagger at\n[http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).\nFor this sample, you can use the api key `special-key` to test the authorization filters.\n\n# Introduction\nThis API is documented in **OpenAPI format** and is based on\n[Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.\nIt was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)\ntool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard\nOpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/main/docs/redoc-vendor-extensions.md).\n\n# OpenAPI Specification\nThis API is documented in **OpenAPI format** and is based on\n[Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.\nIt was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)\ntool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard\nOpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/main/docs/redoc-vendor-extensions.md).\n\n# Cross-Origin Resource Sharing\nThis API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/).\nAnd that allows cross-domain communication from the browser.\nAll responses have a wildcard same-origin which makes them completely public and accessible to everyone, including any code on any site.\n\n# Authentication\n\nPetstore offers two forms of authentication:\n - API Key\n - OAuth2\nOAuth2 - an open protocol to allow secure authorization in a simple\nand standard method from web, mobile and desktop applications.\n\n\n", + "version": "1.0.0", + "title": "Swagger Petstore", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "email": "apiteam@swagger.io", + "url": "https://github.com/Redocly/redoc" + }, + "x-logo": { + "url": "https://redocly.github.io/redoc/petstore-logo.png", + "altText": "Petstore logo" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "externalDocs": { + "description": "Find out how to create Github repo for your OpenAPI spec.", + "url": "https://github.com/Rebilly/generator-openapi-repo" + }, + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets" + }, + { + "name": "store", + "description": "Access to Petstore orders" + }, + { + "name": "user", + "description": "Operations about user" + }, + { + "name": "pet_model", + "x-displayName": "The Pet Model", + "description": "\n" + }, + { + "name": "store_model", + "x-displayName": "The Order Model", + "description": "\n" + } + ], + "x-tagGroups": [ + { + "name": "General", + "tags": [ + "pet", + "store" + ] + }, + { + "name": "User Management", + "tags": [ + "user" + ] + }, + { + "name": "Models", + "tags": [ + "pet_model", + "store_model" + ] + } + ], + "security": [ + {} + ], + "paths": { + "/pet": { + "parameters": [ + { + "name": "Accept-Language", + "in": "header", + "description": "The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US", + "example": "en-US", + "required": false, + "schema": { + "type": "string", + "default": "en-AU" + } + }, + { + "name": "cookieParam", + "in": "cookie", + "description": "Some cookie", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "description": "Add new pet to the store inventory.", + "operationId": "addPet", + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "x-codeSamples": [ + { + "lang": "C#", + "source": "PetStore.v1.Pet pet = new PetStore.v1.Pet();\npet.setApiKey(\"your api key\");\npet.petType = PetStore.v1.Pet.TYPE_DOG;\npet.name = \"Rex\";\n// set other fields\nPetStoreResponse response = pet.create();\nif (response.statusCode == HttpStatusCode.Created)\n{\n // Successfully created\n}\nelse\n{\n // Something wrong -- check response for errors\n Console.WriteLine(response.getRawResponse());\n}\n" + }, + { + "lang": "PHP", + "source": "$form = new \\PetStore\\Entities\\Pet();\n$form->setPetType(\"Dog\");\n$form->setName(\"Rex\");\n// set other fields\ntry {\n $pet = $client->pets()->create($form);\n} catch (UnprocessableEntityException $e) {\n var_dump($e->getErrors());\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Pet" + } + }, + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet", + "description": "", + "operationId": "updatePet", + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "x-codeSamples": [ + { + "lang": "PHP", + "source": "$form = new \\PetStore\\Entities\\Pet();\n$form->setPetId(1);\n$form->setPetType(\"Dog\");\n$form->setName(\"Rex\");\n// set other fields\ntry {\n $pet = $client->pets()->update($form);\n} catch (UnprocessableEntityException $e) {\n var_dump($e->getErrors());\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Pet" + } + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pet" + ], + "summary": "Find pet by ID", + "description": "Returns a single pet", + "operationId": "getPetById", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "deprecated": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Updates a pet in the store with form data", + "description": "", + "operationId": "updatePetWithForm", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be updated", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "type": "object", + "properties": { + "name": { + "description": "Updated name of the pet", + "type": "string" + }, + "status": { + "description": "Updated status of the pet", + "type": "string" + } + } + } + } + } + } + }, + "delete": { + "tags": [ + "pet" + ], + "summary": "Deletes a pet", + "description": "", + "operationId": "deletePet", + "parameters": [ + { + "name": "api_key", + "in": "header", + "required": false, + "schema": { + "type": "string" + }, + "example": "Bearer " + }, + { + "name": "petId", + "in": "path", + "description": "Pet id to delete", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "400": { + "description": "Invalid pet value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}/uploadImage": { + "post": { + "tags": [ + "pet" + ], + "summary": "uploads an image", + "description": "", + "operationId": "uploadFile", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to update", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse" + } + } + } + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "/pet/findByStatus": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma separated strings", + "operationId": "findPetsByStatus", + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": true, + "style": "form", + "schema": { + "type": "array", + "minItems": 1, + "maxItems": 3, + "items": { + "type": "string", + "enum": [ + "available", + "pending", + "sold" + ], + "default": "available" + } + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid status value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByTags": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by tags", + "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId": "findPetsByTags", + "deprecated": true, + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": true, + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/xml": { + "schema": { + "type": "array", + "maxItems": 999, + "items": { + "maxItems": 111, + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid tag value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/store/inventory": { + "get": { + "tags": [ + "store" + ], + "summary": "Returns pet inventories by status", + "description": "Returns a map of status codes to quantities", + "operationId": "getInventory", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "minProperties": 2, + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/store/order": { + "post": { + "tags": [ + "store" + ], + "summary": "Place an order for a pet", + "description": "", + "operationId": "placeOrder", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "400": { + "description": "Invalid Order", + "content": { + "application/json": { + "example": { + "status": 400, + "message": "Invalid Order" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + }, + "description": "order placed for purchasing the pet", + "required": true + } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": [ + "store" + ], + "summary": "Find purchase order by ID", + "description": "For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions", + "operationId": "getOrderById", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of pet that needs to be fetched", + "required": true, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 1, + "maximum": 5 + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + }, + "delete": { + "tags": [ + "store" + ], + "summary": "Delete purchase order by ID", + "description": "For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors", + "operationId": "deleteOrder", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "schema": { + "type": "string", + "minimum": 1 + } + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + } + }, + "/store/subscribe": { + "post": { + "tags": [ + "store" + ], + "summary": "Subscribe to the Store events", + "description": "Add subscription for a store events", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "callbackUrl": { + "type": "string", + "format": "uri", + "description": "This URL will be called by the server when the desired event will occur", + "example": "https://myserver.com/send/callback/here" + }, + "eventName": { + "type": "string", + "description": "Event name for the subscription", + "enum": [ + "orderInProgress", + "orderShipped", + "orderDelivered" + ], + "example": "orderInProgress" + } + }, + "required": [ + "callbackUrl", + "eventName" + ] + } + } + } + }, + "responses": { + "201": { + "description": "Subscription added", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "subscriptionId": { + "type": "string", + "example": "AAA-123-BBB-456" + } + } + } + } + } + } + }, + "callbacks": { + "orderInProgress": { + "{$request.body#/callbackUrl}?event={$request.body#/eventName}": { + "servers": [ + { + "url": "//callback-url.path-level/v1", + "description": "Path level server 1" + }, + { + "url": "//callback-url.path-level/v2", + "description": "Path level server 2" + } + ], + "post": { + "summary": "Order in Progress (Summary)", + "description": "A callback triggered every time an Order is updated status to \"inProgress\" (Description)", + "externalDocs": { + "description": "Find out more", + "url": "https://more-details.com/demo" + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "orderId": { + "type": "string", + "example": "123" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "example": "2018-10-19T16:46:45Z" + }, + "status": { + "type": "string", + "example": "inProgress" + } + } + } + }, + "application/xml": { + "schema": { + "type": "object", + "properties": { + "orderId": { + "type": "string", + "example": "123" + } + } + }, + "example": "\n\n 123\n inProgress\n 2018-10-19T16:46:45Z\n\n" + } + } + }, + "responses": { + "200": { + "description": "Callback successfully processed and no retries will be performed", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "someProp": { + "type": "string", + "example": "123" + } + } + } + } + } + }, + "299": { + "description": "Response for cancelling subscription" + }, + "500": { + "description": "Callback processing failed and retries will be performed" + } + }, + "x-codeSamples": [ + { + "lang": "C#", + "source": "PetStore.v1.Pet pet = new PetStore.v1.Pet();\npet.setApiKey(\"your api key\");\npet.petType = PetStore.v1.Pet.TYPE_DOG;\npet.name = \"Rex\";\n// set other fields\nPetStoreResponse response = pet.create();\nif (response.statusCode == HttpStatusCode.Created)\n{\n // Successfully created\n}\nelse\n{\n // Something wrong -- check response for errors\n Console.WriteLine(response.getRawResponse());\n}\n" + }, + { + "lang": "PHP", + "source": "$form = new \\PetStore\\Entities\\Pet();\n$form->setPetType(\"Dog\");\n$form->setName(\"Rex\");\n// set other fields\ntry {\n $pet = $client->pets()->create($form);\n} catch (UnprocessableEntityException $e) {\n var_dump($e->getErrors());\n}\n" + } + ] + }, + "put": { + "description": "Order in Progress (Only Description)", + "servers": [ + { + "url": "//callback-url.operation-level/v1", + "description": "Operation level server 1 (Operation override)" + }, + { + "url": "//callback-url.operation-level/v2", + "description": "Operation level server 2 (Operation override)" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "orderId": { + "type": "string", + "example": "123" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "example": "2018-10-19T16:46:45Z" + }, + "status": { + "type": "string", + "example": "inProgress" + } + } + } + }, + "application/xml": { + "schema": { + "type": "object", + "properties": { + "orderId": { + "type": "string", + "example": "123" + } + } + }, + "example": "\n\n 123\n inProgress\n 2018-10-19T16:46:45Z\n\n" + } + } + }, + "responses": { + "200": { + "description": "Callback successfully processed and no retries will be performed", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "someProp": { + "type": "string", + "example": "123" + } + } + } + } + } + } + } + } + } + }, + "orderShipped": { + "{$request.body#/callbackUrl}?event={$request.body#/eventName}": { + "post": { + "description": "Very long description\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor\nincididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis\nnostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu\nfugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in\nculpa qui officia deserunt mollit anim id est laborum.\n", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "orderId": { + "type": "string", + "example": "123" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "example": "2018-10-19T16:46:45Z" + }, + "estimatedDeliveryDate": { + "type": "string", + "format": "date-time", + "example": "2018-11-11T16:00:00Z" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Callback successfully processed and no retries will be performed" + } + } + } + } + }, + "orderDelivered": { + "http://notificationServer.com?url={$request.body#/callbackUrl}&event={$request.body#/eventName}": { + "post": { + "deprecated": true, + "summary": "Order delivered", + "description": "A callback triggered every time an Order is delivered to the recipient", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "orderId": { + "type": "string", + "example": "123" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "example": "2018-10-19T16:46:45Z" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Callback successfully processed and no retries will be performed" + } + } + } + } + } + } + } + }, + "/user": { + "post": { + "tags": [ + "user" + ], + "summary": "Create user", + "description": "This can only be done by the logged in user.", + "operationId": "createUser", + "responses": { + "default": { + "description": "successful operation" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + }, + "description": "Created user object", + "required": true + } + } + }, + "/user/{username}": { + "get": { + "tags": [ + "user" + ], + "summary": "Get user by user name", + "description": "", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing. ", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "put": { + "tags": [ + "user" + ], + "summary": "Updated user", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "name that need to be deleted", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "400": { + "description": "Invalid user supplied" + }, + "404": { + "description": "User not found" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + }, + "description": "Updated user object", + "required": true + } + }, + "delete": { + "tags": [ + "user" + ], + "summary": "Delete user", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + } + }, + "/user/createWithArray": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithArrayInput", + "responses": { + "default": { + "description": "successful operation" + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/UserArray" + } + } + }, + "/user/createWithList": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithListInput", + "responses": { + "default": { + "description": "successful operation" + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/UserArray" + } + } + }, + "/user/login": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs user into the system", + "description": "", + "operationId": "loginUser", + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "headers": { + "X-Rate-Limit": { + "description": "calls per hour allowed by the user", + "schema": { + "type": "integer", + "format": "int32" + } + }, + "X-Expires-After": { + "description": "date in UTC when token expires", + "schema": { + "type": "string", + "format": "date-time" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "string" + }, + "examples": { + "response": { + "value": "OK" + } + } + }, + "application/xml": { + "schema": { + "type": "string" + }, + "examples": { + "response": { + "value": " OK " + } + } + }, + "text/plain": { + "examples": { + "response": { + "value": "OK" + } + } + } + } + }, + "400": { + "description": "Invalid username/password supplied" + } + } + } + }, + "/user/logout": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs out current logged in user session", + "description": "", + "operationId": "logoutUser", + "responses": { + "default": { + "description": "successful operation" + } + } + } + } + }, + "components": { + "schemas": { + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "Cat": { + "x-tags": [ + "pet" + ], + "description": "A representation of a cat", + "allOf": [ + { + "$ref": "#/components/schemas/Pet" + }, + { + "type": "object", + "properties": { + "huntingSkill": { + "type": "string", + "description": "The measured skill for hunting", + "default": "lazy", + "example": "adventurous", + "enum": [ + "clueless", + "lazy", + "adventurous", + "aggressive" + ] + } + }, + "required": [ + "huntingSkill" + ] + } + ] + }, + "Category": { + "type": "object", + "properties": { + "id": { + "description": "Category ID", + "allOf": [ + { + "$ref": "#/components/schemas/Id" + } + ] + }, + "name": { + "description": "Category name", + "type": "string", + "minLength": 1 + }, + "sub": { + "description": "Test Sub Category", + "type": "object", + "properties": { + "prop1": { + "type": "string", + "description": "Dumb Property" + } + } + } + }, + "xml": { + "name": "Category" + } + }, + "Dog": { + "description": "A representation of a dog", + "allOf": [ + { + "$ref": "#/components/schemas/Pet" + }, + { + "type": "object", + "properties": { + "packSize": { + "type": "integer", + "format": "int32", + "description": "The size of the pack the dog is from", + "default": 1, + "minimum": 1 + } + }, + "required": [ + "packSize" + ] + } + ] + }, + "HoneyBee": { + "description": "A representation of a honey bee", + "allOf": [ + { + "$ref": "#/components/schemas/Pet" + }, + { + "type": "object", + "properties": { + "honeyPerDay": { + "type": "number", + "description": "Average amount of honey produced per day in ounces", + "example": 3.14, + "multipleOf": 0.01 + } + }, + "required": [ + "honeyPerDay" + ] + } + ] + }, + "Id": { + "type": "integer", + "format": "int64", + "readOnly": true + }, + "Order": { + "type": "object", + "properties": { + "id": { + "description": "Order ID", + "allOf": [ + { + "$ref": "#/components/schemas/Id" + } + ] + }, + "petId": { + "description": "Pet ID", + "allOf": [ + { + "$ref": "#/components/schemas/Id" + } + ] + }, + "quantity": { + "type": "integer", + "format": "int32", + "minimum": 1, + "default": 1 + }, + "shipDate": { + "description": "Estimated ship date", + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "description": "Indicates whenever order was completed or not", + "type": "boolean", + "default": false, + "readOnly": true + }, + "requestId": { + "description": "Unique Request Id", + "type": "string", + "writeOnly": true + } + }, + "xml": { + "name": "Order" + } + }, + "Pet": { + "type": "object", + "required": [ + "name", + "photoUrls" + ], + "discriminator": { + "propertyName": "petType", + "mapping": { + "cat": "#/components/schemas/Cat", + "dog": "#/components/schemas/Dog", + "bee": "#/components/schemas/HoneyBee" + } + }, + "properties": { + "id": { + "externalDocs": { + "description": "Find more info here", + "url": "https://example.com" + }, + "description": "Pet ID", + "allOf": [ + { + "$ref": "#/components/schemas/Id" + } + ] + }, + "category": { + "description": "Categories this pet belongs to", + "allOf": [ + { + "$ref": "#/components/schemas/Category" + } + ] + }, + "name": { + "description": "The name given to a pet", + "type": "string", + "example": "Guru" + }, + "photoUrls": { + "description": "The list of URL to a cute photos featuring pet", + "type": "array", + "default": [], + "maxItems": 20, + "xml": { + "name": "photoUrl", + "wrapped": true + }, + "items": { + "type": "string", + "format": "url" + } + }, + "friend": { + "allOf": [ + { + "$ref": "#/components/schemas/Pet" + } + ] + }, + "tags": { + "description": "Tags attached to the pet", + "type": "array", + "minItems": 1, + "xml": { + "name": "tag", + "wrapped": true + }, + "items": { + "$ref": "#/components/schemas/Tag" + } + }, + "status": { + "type": "string", + "description": "Pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + }, + "petType": { + "description": "Type of a pet", + "type": "string" + } + }, + "xml": { + "name": "Pet" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "description": "Tag ID", + "allOf": [ + { + "$ref": "#/components/schemas/Id" + } + ] + }, + "name": { + "description": "Tag name", + "type": "string", + "minLength": 1 + } + }, + "xml": { + "name": "Tag" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "pet": { + "oneOf": [ + { + "$ref": "#/components/schemas/Pet" + }, + { + "$ref": "#/components/schemas/Tag" + } + ] + }, + "username": { + "description": "User supplied username", + "type": "string", + "minLength": 4, + "example": "John78" + }, + "firstName": { + "description": "User first name", + "type": "string", + "minLength": 1, + "example": "John" + }, + "lastName": { + "description": "User last name", + "type": "string", + "minLength": 1, + "example": "Smith" + }, + "email": { + "description": "User email address", + "type": "string", + "format": "email", + "example": "john.smith@example.com" + }, + "password": { + "type": "string", + "description": "User password, MUST contain a mix of upper and lower case letters, as well as digits", + "format": "password", + "minLength": 8, + "pattern": "/(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])/", + "example": "drowssaP123" + }, + "phone": { + "description": "User phone number in international format", + "type": "string", + "pattern": "/^\\+(?:[0-9]-?){6,14}[0-9]$/", + "example": "+1-202-555-0192" + }, + "userStatus": { + "description": "User status", + "type": "integer", + "format": "int32" + }, + "addresses": { + "type": "array", + "minItems": 0, + "maxLength": 10, + "items": [ + { + "type": "object", + "properties": { + "city": { + "type": "string", + "minLength": 0 + }, + "country": { + "type": "string", + "minLength": 0 + }, + "street": { + "description": "includes build/apartment number", + "type": "string", + "minLength": 0 + } + } + }, + { + "type": "number" + } + ], + "additionalItems": { + "type": "string" + } + } + }, + "xml": { + "name": "User" + } + } + }, + "requestBodies": { + "Pet": { + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "description": "My Pet", + "title": "Pettie" + }, + { + "$ref": "#/components/schemas/Pet" + } + ] + } + }, + "application/xml": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "hooray", + "default": [] + } + } + } + } + }, + "description": "Pet object that needs to be added to the store", + "required": true + }, + "UserArray": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "description": "List of user object", + "required": true + } + }, + "securitySchemes": { + "petstore_auth": { + "description": "Get access to data while protecting your account credentials.\nOAuth2 is also a safer and more secure way to give you access.\n", + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "http://petstore.swagger.io/api/oauth/dialog", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + }, + "api_key": { + "description": "For this sample, you can use the api key `special-key` to test the authorization filters.\n", + "type": "apiKey", + "name": "api_key", + "in": "header" + } + }, + "examples": { + "Order": { + "value": { + "quantity": 1, + "shipDate": "2018-10-19T16:46:45Z", + "status": "placed", + "complete": false + } + } + } + }, + "x-webhooks": { + "newPet": { + "post": { + "summary": "New pet", + "description": "Information about a new pet in the systems", + "operationId": "newPet", + "tags": [ + "pet" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status to indicate that the data was received successfully" + } + } + } + } + } +} From 438de6270cdc918c30d7b198c2c42088acd5ad8d Mon Sep 17 00:00:00 2001 From: lmieulet Date: Sat, 9 Mar 2024 14:38:53 +0100 Subject: [PATCH 2/2] No more export default + fix typings --- packages/docusaurus-plugin-redoc/src/index.ts | 4 +- .../src/theme/ApiSchema/ApiSchema.tsx | 18 ++- .../src/theme/Redoc/Redoc.tsx | 14 ++- .../src/theme/Redoc/ServerRedoc.tsx | 25 ++-- .../src/theme/Redoc/ServerStyles.tsx | 23 ++-- .../src/theme/Redoc/Styles.tsx | 8 +- .../src/theme/useSpec/index.ts | 3 + .../src/{utils => theme/useSpec}/useSpec.ts | 18 +-- .../src/theme/useSpecData/index.ts | 3 + .../useSpecData}/useSpecData.ts | 18 ++- .../src/theme/useSpecOptions/index.ts | 3 + .../useSpecOptions}/useSpecOptions.ts | 8 +- .../src/types/common.ts | 36 +----- .../src/types/modules.ts | 112 +++++++++++++----- website/docs/getting-started/Installation.md | 2 +- website/docs/guides/component-api-schema.mdx | 28 ++--- website/docs/guides/component-redoc.mdx | 6 +- ...es.json => using-single-json.openapi.json} | 0 18 files changed, 187 insertions(+), 142 deletions(-) create mode 100644 packages/docusaurus-theme-redoc/src/theme/useSpec/index.ts rename packages/docusaurus-theme-redoc/src/{utils => theme/useSpec}/useSpec.ts (82%) create mode 100644 packages/docusaurus-theme-redoc/src/theme/useSpecData/index.ts rename packages/docusaurus-theme-redoc/src/{utils => theme/useSpecData}/useSpecData.ts (72%) create mode 100644 packages/docusaurus-theme-redoc/src/theme/useSpecOptions/index.ts rename packages/docusaurus-theme-redoc/src/{utils => theme/useSpecOptions}/useSpecOptions.ts (92%) rename website/openapi/{single-json-file/api-with-examples.json => using-single-json.openapi.json} (100%) diff --git a/packages/docusaurus-plugin-redoc/src/index.ts b/packages/docusaurus-plugin-redoc/src/index.ts index 04a5a947..04564e14 100644 --- a/packages/docusaurus-plugin-redoc/src/index.ts +++ b/packages/docusaurus-plugin-redoc/src/index.ts @@ -24,7 +24,7 @@ import { PluginDirectUsageOptions, DEFAULT_OPTIONS, } from './options'; -import type { SpecPropsWithUrl, ApiDocProps } from './types/common'; +import type { SpecDataResult, ApiDocProps } from './types/common'; import { loadSpecWithConfig } from './loadSpec'; import { loadRedoclyConfig } from './loadRedoclyConfig'; @@ -128,7 +128,7 @@ export default function redocPlugin( throw new Error(`[Redocusaurus] Spec could not be parsed: ${spec}`); } - const data: SpecPropsWithUrl = { + const data: SpecDataResult = { url, themeId, // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/docusaurus-theme-redoc/src/theme/ApiSchema/ApiSchema.tsx b/packages/docusaurus-theme-redoc/src/theme/ApiSchema/ApiSchema.tsx index e90b1b73..a278a4bf 100644 --- a/packages/docusaurus-theme-redoc/src/theme/ApiSchema/ApiSchema.tsx +++ b/packages/docusaurus-theme-redoc/src/theme/ApiSchema/ApiSchema.tsx @@ -3,17 +3,25 @@ import clsx from 'clsx'; import { ThemeProvider } from 'styled-components'; import '../../global'; import { SchemaDefinition } from 'redoc'; -import useSpec from '../../utils/useSpec'; -import { ApiSchemaProps as Props } from '../../types/common'; +import useSpec from '@theme/useSpec'; import '../Redoc/styles.css'; import './styles.css'; -const ApiSchema: React.FC = ({ +const ApiSchema: React.FC = ({ showExample, pointer, + id, + spec, + optionsOverrides, ...rest -}: Props): JSX.Element => { - const { store } = useSpec(rest); +}: ApiSchemaProps): JSX.Element => { + const { store } = useSpec( + { + id, + spec, + }, + optionsOverrides, + ); useEffect(() => { /** diff --git a/packages/docusaurus-theme-redoc/src/theme/Redoc/Redoc.tsx b/packages/docusaurus-theme-redoc/src/theme/Redoc/Redoc.tsx index 0d2149ba..17bf7280 100644 --- a/packages/docusaurus-theme-redoc/src/theme/Redoc/Redoc.tsx +++ b/packages/docusaurus-theme-redoc/src/theme/Redoc/Redoc.tsx @@ -2,11 +2,14 @@ import React from 'react'; import clsx from 'clsx'; import '../../global'; import { RedocStandalone } from 'redoc'; -import { RedocProps } from '../../types/common'; -import useSpecOptions from '../../utils/useSpecOptions'; +import useSpecOptions from '@theme/useSpecOptions'; import './styles.css'; import ServerRedoc from './ServerRedoc'; +function getIsExternalUrl(url = '') { + return ['http://', 'https://'].some((protocol) => url.startsWith(protocol)); +} + /*! * Redocusaurus * https://redocusaurus.vercel.app/ @@ -14,11 +17,10 @@ import ServerRedoc from './ServerRedoc'; * Released under the MIT License */ function Redoc(props: RedocProps): JSX.Element { - const { className, optionsOverrides, spec, url, themeId, isSpecFile } = props; + const { className, optionsOverrides, url, themeId } = props; const { options } = useSpecOptions(themeId, optionsOverrides); - const isDevMode = process.env.NODE_ENV === 'development'; - if ((isDevMode && isSpecFile === false) || !spec) { + if (getIsExternalUrl(url)) { return (
@@ -26,7 +28,7 @@ function Redoc(props: RedocProps): JSX.Element { ); } - return ; + return ; } export default Redoc; diff --git a/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerRedoc.tsx b/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerRedoc.tsx index c15c55c8..8d81ee0a 100644 --- a/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerRedoc.tsx +++ b/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerRedoc.tsx @@ -1,9 +1,8 @@ import React from 'react'; import clsx from 'clsx'; import '../../global'; -import { Redoc as RedocComponent, RedocRawOptions } from 'redoc'; -import { SpecProps } from '../../types/common'; -import useSpec from '../../utils/useSpec'; +import { Redoc as RedocComponent } from 'redoc'; +import useSpec from '@theme/useSpec'; import { ServerStyles } from './Styles'; import './styles.css'; @@ -13,22 +12,22 @@ import './styles.css'; * (c) 2024 Rohit Gohri * Released under the MIT License */ -function ServerRedoc( - props: SpecProps & { - className?: string; - optionsOverrides?: RedocRawOptions; - }, -): JSX.Element { - const { className, optionsOverrides, ...specProps } = props; - const { store, darkThemeOptions, lightThemeOptions, hasLogo } = useSpec( - specProps, +function ServerRedoc(props: RedocProps): JSX.Element { + const { className, optionsOverrides, url, id, themeId } = props; + const { store, spec, darkThemeOptions, lightThemeOptions, hasLogo } = useSpec( + { + spec: props.spec, + themeId, + id, + }, optionsOverrides, ); return ( <> diff --git a/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerStyles.tsx b/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerStyles.tsx index 1969cdd5..bf1a3da2 100644 --- a/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerStyles.tsx +++ b/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerStyles.tsx @@ -2,6 +2,7 @@ import React from 'react'; import '../../global'; import useBaseUrl from '@docusaurus/useBaseUrl'; import { AppStore, Redoc, RedocRawOptions } from 'redoc'; +import type { OpenAPISpec } from 'redoc/typings/types'; // eslint-disable-next-line import/no-extraneous-dependencies import { renderToString } from 'react-dom/server'; import { ServerStyleSheet } from 'styled-components'; @@ -53,22 +54,26 @@ const prefixCssSelectors = function (rules: string, className: string) { const LIGHT_MODE_PREFIX = "html:not([data-theme='dark'])"; const DARK_MODE_PREFIX = "html([data-theme='dark'])"; +export type ServerStylesProps = { + spec: OpenAPISpec; + url?: string; + lightThemeOptions: RedocRawOptions; + darkThemeOptions: RedocRawOptions; +}; + export function ServerStyles({ - specProps, + spec, + url, lightThemeOptions, darkThemeOptions, -}: { - specProps: SpecProps; - lightThemeOptions: RedocRawOptions; - darkThemeOptions: RedocRawOptions; -}) { - const fullUrl = useBaseUrl(specProps.url, { absolute: true }); +}: ServerStylesProps) { + const fullUrl = useBaseUrl(url, { absolute: true }); const css = { light: '', dark: '', }; const lightSheet = new ServerStyleSheet(); - const lightStore = new AppStore(specProps.spec, fullUrl, lightThemeOptions); + const lightStore = new AppStore(spec, fullUrl, lightThemeOptions); renderToString( lightSheet.collectStyles(React.createElement(Redoc, { store: lightStore })), ); @@ -78,7 +83,7 @@ export function ServerStyles({ css.light = prefixCssSelectors(lightCss, LIGHT_MODE_PREFIX); const darkSheet = new ServerStyleSheet(); - const darkStore = new AppStore(specProps.spec, fullUrl, darkThemeOptions); + const darkStore = new AppStore(spec, fullUrl, darkThemeOptions); renderToString( darkSheet.collectStyles(React.createElement(Redoc, { store: darkStore })), ); diff --git a/packages/docusaurus-theme-redoc/src/theme/Redoc/Styles.tsx b/packages/docusaurus-theme-redoc/src/theme/Redoc/Styles.tsx index 7a3bf976..17a39d0c 100644 --- a/packages/docusaurus-theme-redoc/src/theme/Redoc/Styles.tsx +++ b/packages/docusaurus-theme-redoc/src/theme/Redoc/Styles.tsx @@ -1,15 +1,11 @@ import React from 'react'; import '../../global'; -import type { RedocRawOptions } from 'redoc'; +import type { ServerStylesProps } from './ServerStyles'; /** * Don't hydrate/replace server styles * @see https://github.com/facebook/react/issues/10923#issuecomment-338715787 */ -export function ServerStyles(_props: { - specProps: SpecProps; - lightThemeOptions: RedocRawOptions; - darkThemeOptions: RedocRawOptions; -}) { +export function ServerStyles(_props: ServerStylesProps) { return
; } diff --git a/packages/docusaurus-theme-redoc/src/theme/useSpec/index.ts b/packages/docusaurus-theme-redoc/src/theme/useSpec/index.ts new file mode 100644 index 00000000..30c5c48c --- /dev/null +++ b/packages/docusaurus-theme-redoc/src/theme/useSpec/index.ts @@ -0,0 +1,3 @@ +import { useSpec } from './useSpec'; + +export default useSpec; diff --git a/packages/docusaurus-theme-redoc/src/utils/useSpec.ts b/packages/docusaurus-theme-redoc/src/theme/useSpec/useSpec.ts similarity index 82% rename from packages/docusaurus-theme-redoc/src/utils/useSpec.ts rename to packages/docusaurus-theme-redoc/src/theme/useSpec/useSpec.ts index bf2385b1..a60f4e9d 100644 --- a/packages/docusaurus-theme-redoc/src/utils/useSpec.ts +++ b/packages/docusaurus-theme-redoc/src/theme/useSpec/useSpec.ts @@ -2,11 +2,10 @@ import { useMemo, useEffect } from 'react'; import useBaseUrl from '@docusaurus/useBaseUrl'; import useIsBrowser from '@docusaurus/useIsBrowser'; import { useColorMode } from '@docusaurus/theme-common'; -import '../global'; +import useSpecData from '@theme/useSpecData'; +import useSpecOptions from '@theme/useSpecOptions'; +import '../../global'; import { AppStore, RedocRawOptions } from 'redoc'; -import { SpecProps } from '../types/common'; -import useSpecOptions from './useSpecOptions'; -import useSpecData from './useSpecData'; // the current store singleton in the app's instance let currentStore: AppStore | null = null; @@ -17,11 +16,15 @@ let currentStore: AppStore | null = null; * (c) 2024 Rohit Gohri * Released under the MIT License */ -export default function useSpec( +export function useSpec( specInfo: SpecProps, optionsOverrides?: RedocRawOptions, -) { - const { spec, url, themeId } = useSpecData(specInfo); +): SpecResult { + const { spec, url, themeId } = useSpecData( + specInfo.id, + specInfo.spec, + specInfo.themeId, + ); const specOptions = useSpecOptions(themeId, optionsOverrides); const fullUrl = useBaseUrl(url, { absolute: true }); const isBrowser = useIsBrowser(); @@ -38,6 +41,7 @@ export default function useSpec( // @ts-expect-error extra prop hasLogo: !!spec.info?.['x-logo'], store: currentStore, + spec, }; }, [isBrowser, spec, fullUrl, specOptions]); diff --git a/packages/docusaurus-theme-redoc/src/theme/useSpecData/index.ts b/packages/docusaurus-theme-redoc/src/theme/useSpecData/index.ts new file mode 100644 index 00000000..da2a5330 --- /dev/null +++ b/packages/docusaurus-theme-redoc/src/theme/useSpecData/index.ts @@ -0,0 +1,3 @@ +import { useSpecData } from './useSpecData'; + +export default useSpecData; diff --git a/packages/docusaurus-theme-redoc/src/utils/useSpecData.ts b/packages/docusaurus-theme-redoc/src/theme/useSpecData/useSpecData.ts similarity index 72% rename from packages/docusaurus-theme-redoc/src/utils/useSpecData.ts rename to packages/docusaurus-theme-redoc/src/theme/useSpecData/useSpecData.ts index 4fb88f79..a6294d48 100644 --- a/packages/docusaurus-theme-redoc/src/utils/useSpecData.ts +++ b/packages/docusaurus-theme-redoc/src/theme/useSpecData/useSpecData.ts @@ -1,6 +1,6 @@ import { useAllPluginInstancesData } from '@docusaurus/useGlobalData'; import type { OpenAPISpec } from 'redoc/typings/types'; -import { SpecProps, SpecPropsWithUrl } from '../types/common'; +import { SpecDataResult } from '../../types/common'; export type ParsedSpec = OpenAPISpec; @@ -13,17 +13,23 @@ export type ParsedSpec = OpenAPISpec; * @param providedSpec spec data * @returns Spec Data of ID or first one if ID is not provided */ -export default function useSpecData(providedSpec: SpecProps): SpecPropsWithUrl { +export function useSpecData( + id?: string, + spec?: OpenAPISpec, + themeId?: string, +): SpecDataResult { const allData = useAllPluginInstancesData('docusaurus-plugin-redoc'); - if (providedSpec.spec) { + if (spec) { // return provided spec when already defined - return providedSpec as SpecPropsWithUrl; + return { + spec, + themeId, + }; } else { // retrieve spec from docusaurus conf - const id = providedSpec.id; const apiData = id ? allData?.[id as string] : Object.values(allData ?? {})?.[0]; - return apiData as SpecPropsWithUrl; + return apiData as SpecDataResult; } } diff --git a/packages/docusaurus-theme-redoc/src/theme/useSpecOptions/index.ts b/packages/docusaurus-theme-redoc/src/theme/useSpecOptions/index.ts new file mode 100644 index 00000000..a7fdb6ed --- /dev/null +++ b/packages/docusaurus-theme-redoc/src/theme/useSpecOptions/index.ts @@ -0,0 +1,3 @@ +import { useSpecOptions } from './useSpecOptions'; + +export default useSpecOptions; diff --git a/packages/docusaurus-theme-redoc/src/utils/useSpecOptions.ts b/packages/docusaurus-theme-redoc/src/theme/useSpecOptions/useSpecOptions.ts similarity index 92% rename from packages/docusaurus-theme-redoc/src/utils/useSpecOptions.ts rename to packages/docusaurus-theme-redoc/src/theme/useSpecOptions/useSpecOptions.ts index 676f5f67..3a867167 100644 --- a/packages/docusaurus-theme-redoc/src/utils/useSpecOptions.ts +++ b/packages/docusaurus-theme-redoc/src/theme/useSpecOptions/useSpecOptions.ts @@ -6,10 +6,10 @@ import { } from '@docusaurus/useGlobalData'; import { useColorMode } from '@docusaurus/theme-common'; import merge from 'lodash/merge'; -import '../global'; +import '../../global'; import { RedocRawOptions } from 'redoc'; -import { SpecProps } from '../types/common'; -import { GlobalData } from '../types/options'; +import { SpecProps } from '../../types/common'; +import { GlobalData } from '../../types/options'; /** * Redocusaurus @@ -17,7 +17,7 @@ import { GlobalData } from '../types/options'; * (c) 2024 Rohit Gohri * Released under the MIT License */ -export default function useSpecOptions( +export function useSpecOptions( themeId: SpecProps['themeId'] = 'theme-redoc', optionsOverrides?: RedocRawOptions, ) { diff --git a/packages/docusaurus-theme-redoc/src/types/common.ts b/packages/docusaurus-theme-redoc/src/types/common.ts index 3d7c3eca..03937907 100644 --- a/packages/docusaurus-theme-redoc/src/types/common.ts +++ b/packages/docusaurus-theme-redoc/src/types/common.ts @@ -1,14 +1,11 @@ import type { Props as LayoutProps } from '@theme/Layout'; -import type { ObjectDescriptionProps, RedocRawOptions } from 'redoc'; -import type { OpenAPISpec } from 'redoc/typings/types'; - -export type ParsedSpec = OpenAPISpec; +import type { ObjectDescriptionProps } from 'redoc'; export interface SpecProps { /** * Spec to use, already loaded previously */ - spec: ParsedSpec; + spec: import('redoc/typings/types').OpenAPISpec; /** * When spec not provided, load the spec from docusaurus config * fallback to first configuration if not provided @@ -20,23 +17,9 @@ export interface SpecProps { themeId?: string; } -export type SpecPropsWithUrl = Omit & { - /** - * Absolute path to the spec file used - */ - url?: string; -}; - -export type RedocProps = SpecProps & { - /** - * FIXME - incorrect name - should be isExternalUrl - */ - isSpecFile?: boolean; - className?: string; - optionsOverrides?: RedocRawOptions; +export type SpecDataResult = Omit & { /** - * External URL to load spec file from - * FIXME - incorrect name - should be externalUrl + * Public path to the spec file used, used by Redoc as download url */ url?: string; }; @@ -48,17 +31,6 @@ export interface MdxProps { */ id?: string; } -export type ApiSchemaProps = Omit< - ObjectDescriptionProps, - 'parser' | 'options' | 'schemaRef' -> & - MdxProps & { - /** - * Ref to the schema - */ - pointer: ObjectDescriptionProps['schemaRef']; - spec: ParsedSpec; - }; export type ApiDocProps = { specProps: SpecProps; diff --git a/packages/docusaurus-theme-redoc/src/types/modules.ts b/packages/docusaurus-theme-redoc/src/types/modules.ts index a4b38f75..7cf9b28d 100644 --- a/packages/docusaurus-theme-redoc/src/types/modules.ts +++ b/packages/docusaurus-theme-redoc/src/types/modules.ts @@ -1,19 +1,50 @@ interface SpecProps { - // eslint-disable-next-line @typescript-eslint/no-explicit-any + /** + * Spec to use, already loaded previously + */ spec: import('redoc/typings/types').OpenAPISpec; - url?: string; - isSpecFile?: boolean; + /** + * When spec not provided, load the spec from docusaurus config + * fallback to first configuration if not provided + */ + id?: string; + /** + * docusaurus theme to use + */ themeId?: string; } +interface SpecResult { + hasLogo: boolean; + spec: import('redoc/typings/types').OpenAPISpec; + store: import('redoc/typings').AppStore; + options: import('redoc/typings').RedocRawOptions; + darkThemeOptions: import('redoc/typings').RedocRawOptions; + lightThemeOptions: import('redoc/typings').RedocRawOptions; +} + +type RedocProps = SpecProps & { + className?: string; + optionsOverrides?: import('redoc/typings').RedocRawOptions; + /** + * External URL to load spec file from + */ + url?: string; +}; + +type ApiSchemaProps = Omit< + import('redoc/typings').ObjectDescriptionProps, + 'parser' | 'options' | 'schemaRef' +> & + SpecProps & { + /** + * Ref to the schema + */ + pointer: import('redoc/typings').ObjectDescriptionProps['schemaRef']; + optionsOverrides?: import('redoc/typings').RedocRawOptions; + }; declare module '@theme/Redoc' { - import type { RedocRawOptions } from 'redoc'; - const Redoc: ( - props: SpecProps & { - className?: string; - optionsOverrides?: RedocRawOptions; - }, - ) => JSX.Element; + const Redoc: (props: RedocProps) => JSX.Element; export default Redoc; } @@ -49,31 +80,56 @@ declare module '@theme/ApiDocMdx' { } declare module '@theme/ApiSchema' { - interface ApiSchemaProps { - /** - * If you have multiple apis, then add a `id` field in the specs array - * And pass the same here - */ - id?: string; - /** - * Show the example or not - */ - example?: boolean; - - /** - * Ref to the schema - */ - pointer: string; - } - const ApiSchema: (props: ApiSchemaProps) => JSX.Element; export default ApiSchema; } declare module '@theme/useSpecData' { + type SpecDataResult = Omit & { + /** + * Public path to the spec file used, used by Redoc as download url + */ + url?: string; + }; + /** * Load redocusaurus plugin data by ID */ - const useSpecData: (id?: string) => SpecProps; + const useSpecData: ( + id?: string, + spec?: import('redoc/typings/types').OpenAPISpec, + themeId?: string, + ) => SpecDataResult; export default useSpecData; } + +declare module '@theme/useSpec' { + import { RedocRawOptions } from 'redoc'; + /** + * Load redocusaurus plugin data by ID + */ + const useSpec: ( + specInfo: SpecProps, + optionsOverrides?: RedocRawOptions, + ) => SpecResult; + + export default useSpec; +} + +declare module '@theme/useSpecOptions' { + import { RedocRawOptions } from 'redoc'; + interface SpecOptionsResultProps { + options: RedocRawOptions; + darkThemeOptions: RedocRawOptions; + lightThemeOptions: RedocRawOptions; + } + /** + * Load redocusaurus plugin data by ID + */ + const useSpec: ( + themeId: SpecProps['themeId'], + optionsOverrides?: RedocRawOptions, + ) => SpecOptionsResultProps; + + export default useSpec; +} diff --git a/website/docs/getting-started/Installation.md b/website/docs/getting-started/Installation.md index d23d7e57..85ae546e 100644 --- a/website/docs/getting-started/Installation.md +++ b/website/docs/getting-started/Installation.md @@ -72,7 +72,7 @@ author_url: https://rohit.page ``` The API Doc will be available at the path specific by `route`. To skip adding a route altogether just don't set the `route` property. -You will still be able to reference schema elements manually using [Schema Imports](/docs/guides/schema-imports) or create Custom React Pages using the data and theme components. +You will still be able to reference schema elements manually using [Schema Imports](/docs/guides/component-api-schema) or create Custom React Pages using the data and theme components. If you have a [`redocly.yaml`](https://redocly.com/docs/cli/configuration/) it will be loaded automatically. ## Options diff --git a/website/docs/guides/component-api-schema.mdx b/website/docs/guides/component-api-schema.mdx index 10811d03..d6b28325 100644 --- a/website/docs/guides/component-api-schema.mdx +++ b/website/docs/guides/component-api-schema.mdx @@ -4,7 +4,7 @@ sidebar_position: 2 --- import ApiSchema from '@theme/ApiSchema'; -import openApi from '../../openapi/single-json-file/api-with-examples.json' +import openApi from '../../openapi/using-single-json.openapi.json' # ApiSchema @@ -32,7 +32,7 @@ import ApiSchema from '@theme/ApiSchema'; | spec | OpenAPI spec | A JSON content spec to use | | themeId | String | redocusaurus theme to use - default to `theme-redoc` | -## Example +## Examples ### Basic example @@ -69,33 +69,21 @@ const config = { [ 'redocusaurus', { + openapi: { + path: 'openapi', + routeBasePath: '/examples', + }, specs: [ - { - id: 'using-single-yaml', - spec: 'openapi/single-file/openapi.yaml', - route: '/examples/using-single-yaml/', - }, { id: 'using-remote-url', spec: 'https://redocly.github.io/redoc/openapi.yaml', route: '/examples/using-remote-url/', }, ], - theme: { - /** - * Highlight color for docs - */ - primaryColor: '#1890ff', - /** - * Options to pass to redoc - * @see https://github.com/redocly/redoc#redoc-options-object - */ - options: { disableSearch: true }, - }, + [...] }, ], ], - title: 'Redocusaurus', }; ``` @@ -117,7 +105,7 @@ import ApiSchema from '@theme/ApiSchema'; ### Webpack loader example You can provide a JSON spec to the component like this. Webpack will load the file directly, -you don't need to use docusaurus configuration. +you don't need to use redocusaurus configuration inside `docusaurus.config.js`. ```tsx import ApiSchema from '@theme/ApiSchema'; diff --git a/website/docs/guides/component-redoc.mdx b/website/docs/guides/component-redoc.mdx index 68264bba..54072ee4 100644 --- a/website/docs/guides/component-redoc.mdx +++ b/website/docs/guides/component-redoc.mdx @@ -4,7 +4,7 @@ sidebar_position: 1 --- import Redoc from '@theme/Redoc'; -import openApi from '../../openapi/single-json-file/api-with-examples.json' +import openApi from '../../openapi/using-single-json.openapi.json' # Redoc @@ -29,7 +29,7 @@ import Redoc from '@theme/Redoc'; | spec | OpenAPI spec | A JSON content spec to use | | url | String | External URL to load spec file from | -## Example +## Examples ### External URL example @@ -44,7 +44,7 @@ import Redoc from '@theme/Redoc'; ### Webpack loader example You can provide a JSON spec to the component like this. Webpack will load the file directly, -you don't need to use docusaurus configuration. +you don't need to use redocusaurus configuration inside `docusaurus.config.js`. ```tsx import Redoc from '@theme/Redoc'; diff --git a/website/openapi/single-json-file/api-with-examples.json b/website/openapi/using-single-json.openapi.json similarity index 100% rename from website/openapi/single-json-file/api-with-examples.json rename to website/openapi/using-single-json.openapi.json