diff --git a/README.md b/README.md
index 642fcd31a..272b89258 100644
--- a/README.md
+++ b/README.md
@@ -346,6 +346,8 @@ yarn build-packages
yarn watch:demo
```
+Whenever you modify the plugin or theme sources, run `yarn build-packages` again so the demo picks up your latest code.
+
## Credits
Special thanks to [@bourdakos1](https://github.com/bourdakos1) (Nick Bourdakos), the author of [docusaurus-openapi](https://github.com/cloud-annotations/docusaurus-openapi), which this project is heavily based on.
diff --git a/demo/docs/intro.mdx b/demo/docs/intro.mdx
index d4779c3f7..5de200071 100644
--- a/demo/docs/intro.mdx
+++ b/demo/docs/intro.mdx
@@ -69,7 +69,7 @@ The plugin extracts a number of vendor extensions from the OpenAPI spec to enric
| `x-displayName` | Overrides tag display names. |
| `x-enumDescription` / `x-enumDescriptions` | Documents enum values. |
-Other ReDoc specific extensions such as `x-circular-ref`, `x-code-samples` (deprecated), `x-examples`, `x-ignoredHeaderParameters`, `x-nullable`, `x-servers`, `x-traitTag`, `x-additionalPropertiesName`, and `x-explicitMappingOnly` are ignored when extracting custom data.
+Circular references flagged with `x-circular-ref` or detected automatically are serialized as `circular(
)`. Other ReDoc specific extensions such as `x-code-samples` (deprecated), `x-examples`, `x-ignoredHeaderParameters`, `x-nullable`, `x-servers`, `x-traitTag`, `x-additionalPropertiesName`, and `x-explicitMappingOnly` are ignored when extracting custom data.
---
diff --git a/demo/docs/vendor-extensions.mdx b/demo/docs/vendor-extensions.mdx
index f0aede3e8..761e12606 100644
--- a/demo/docs/vendor-extensions.mdx
+++ b/demo/docs/vendor-extensions.mdx
@@ -21,4 +21,4 @@ The OpenAPI plugin and theme recognize several [vendor extensions](https://swagg
| `x-displayName` | Override tag names used for grouping. |
| `x-enumDescription` / `x-enumDescriptions` | Document individual enum values. |
-Other ReDoc extensions such as `x-circular-ref`, `x-code-samples` (deprecated), `x-examples`, `x-ignoredHeaderParameters`, `x-nullable`, `x-servers`, `x-traitTag`, `x-additionalPropertiesName`, and `x-explicitMappingOnly` are detected but ignored when extracting custom extensions.
+Circular references flagged with `x-circular-ref` or detected automatically are serialized as `circular()`. Other ReDoc extensions such as `x-code-samples` (deprecated), `x-examples`, `x-ignoredHeaderParameters`, `x-nullable`, `x-servers`, `x-traitTag`, `x-additionalPropertiesName`, and `x-explicitMappingOnly` are detected but ignored when extracting custom extensions.
diff --git a/packages/docusaurus-plugin-openapi-docs/README.md b/packages/docusaurus-plugin-openapi-docs/README.md
index 73121243d..1d1928390 100644
--- a/packages/docusaurus-plugin-openapi-docs/README.md
+++ b/packages/docusaurus-plugin-openapi-docs/README.md
@@ -238,7 +238,7 @@ The plugin extracts a number of vendor extensions from the OpenAPI spec to enric
| `x-displayName` | Overrides tag display names. |
| `x-enumDescription` / `x-enumDescriptions` | Documents enum values. |
-Other ReDoc specific extensions such as `x-circular-ref`, `x-code-samples` (deprecated), `x-examples`, `x-ignoredHeaderParameters`, `x-nullable`, `x-servers`, `x-traitTag`, `x-additionalPropertiesName`, and `x-explicitMappingOnly` are ignored when extracting custom data.
+Circular references flagged with `x-circular-ref` or detected automatically are serialized as `circular()`. Other ReDoc specific extensions such as `x-code-samples` (deprecated), `x-examples`, `x-ignoredHeaderParameters`, `x-nullable`, `x-servers`, `x-traitTag`, `x-additionalPropertiesName`, and `x-explicitMappingOnly` are ignored when extracting custom data.
## CLI Usage
diff --git a/packages/docusaurus-plugin-openapi-docs/src/markdown/__snapshots__/createSchema.test.ts.snap b/packages/docusaurus-plugin-openapi-docs/src/markdown/__snapshots__/createSchema.test.ts.snap
index 3d4d9548d..7b2a8b125 100644
--- a/packages/docusaurus-plugin-openapi-docs/src/markdown/__snapshots__/createSchema.test.ts.snap
+++ b/packages/docusaurus-plugin-openapi-docs/src/markdown/__snapshots__/createSchema.test.ts.snap
@@ -364,24 +364,6 @@ Array [
qualifierMessage={undefined}
schema={{ type: \\"string\\" }}
>;
-",
- ";
-",
- ";
",
]
`;
@@ -729,15 +711,6 @@ Array [
qualifierMessage={undefined}
schema={{ type: \\"string\\" }}
>;
-",
- ";
",
]
`;
@@ -824,34 +797,6 @@ Array [
qualifierMessage={undefined}
schema={{ type: \\"string\\" }}
>;
-",
- "
-
-
-
- type
-
- string
-
-
- **Possible values:** [\`typeA\`, \`typeB\`]
-
-
-
-
- #/definitions/TypeA
-
-
-
-
- #/definitions/TypeB
-
-
-
-
-
;
",
]
`;
diff --git a/packages/docusaurus-plugin-openapi-docs/src/markdown/createSchema.ts b/packages/docusaurus-plugin-openapi-docs/src/markdown/createSchema.ts
index bea754db0..ef6cef99b 100644
--- a/packages/docusaurus-plugin-openapi-docs/src/markdown/createSchema.ts
+++ b/packages/docusaurus-plugin-openapi-docs/src/markdown/createSchema.ts
@@ -783,17 +783,58 @@ export function createNodes(
}
if (schema.allOf !== undefined) {
- const mergedSchemas = mergeAllOf(schema) as SchemaObject;
+ const circularItems = schema.allOf.filter((item: any) => {
+ if (typeof item === "string" && item.includes("circular")) {
+ return true;
+ }
+ if (typeof item === "object" && item["x-circular-ref"]) {
+ return true;
+ }
+ return false;
+ });
- if (
- mergedSchemas.oneOf !== undefined ||
- mergedSchemas.anyOf !== undefined
- ) {
- nodes.push(createAnyOneOf(mergedSchemas));
+ for (const label of circularItems) {
+ const schemaName =
+ typeof label === "string"
+ ? label
+ : label.title
+ ? `circular(${label.title})`
+ : "circular()";
+ nodes.push(
+ create("SchemaItem", {
+ collapsible: false,
+ name: "",
+ required: false,
+ schemaName,
+ qualifierMessage: undefined,
+ schema: {},
+ })
+ );
}
- if (mergedSchemas.properties !== undefined) {
- nodes.push(createProperties(mergedSchemas));
+ const rest = schema.allOf.filter((item: any) => {
+ if (typeof item === "string" && item.includes("circular")) {
+ return false;
+ }
+ if (typeof item === "object" && item["x-circular-ref"]) {
+ return false;
+ }
+ return true;
+ });
+
+ if (rest.length) {
+ const mergedSchemas = mergeAllOf({ allOf: rest } as SchemaObject);
+
+ if (
+ mergedSchemas.oneOf !== undefined ||
+ mergedSchemas.anyOf !== undefined
+ ) {
+ nodes.push(createAnyOneOf(mergedSchemas));
+ }
+
+ if (mergedSchemas.properties !== undefined) {
+ nodes.push(createProperties(mergedSchemas));
+ }
}
}
@@ -805,14 +846,25 @@ export function createNodes(
if (schema.type !== undefined) {
if (schema.allOf) {
//handle circular result in allOf
- if (schema.allOf.length && typeof schema.allOf[0] === "string") {
+ const first: any = schema.allOf[0];
+ if (
+ schema.allOf.length &&
+ ((typeof first === "string" && first.includes("circular")) ||
+ (typeof first === "object" && first["x-circular-ref"]))
+ ) {
+ const label =
+ typeof first === "string"
+ ? first
+ : first.title
+ ? `circular(${first.title})`
+ : "circular()";
return create("div", {
style: {
marginTop: ".5rem",
marginBottom: ".5rem",
marginLeft: "1rem",
},
- children: createDescription(schema.allOf[0]),
+ children: createDescription(label),
});
}
}
diff --git a/packages/docusaurus-plugin-openapi-docs/src/markdown/schema.ts b/packages/docusaurus-plugin-openapi-docs/src/markdown/schema.ts
index 185019c17..d3576e4ce 100644
--- a/packages/docusaurus-plugin-openapi-docs/src/markdown/schema.ts
+++ b/packages/docusaurus-plugin-openapi-docs/src/markdown/schema.ts
@@ -7,7 +7,13 @@
import { SchemaObject } from "../openapi/types";
-function prettyName(schema: SchemaObject, circular?: boolean) {
+function prettyName(schema: SchemaObject | string, circular?: boolean) {
+ if (typeof schema === "string") {
+ return schema.startsWith("circular(") ? schema : "";
+ }
+ if (schema["x-circular-ref"]) {
+ return schema.title ? `circular(${schema.title})` : "circular()";
+ }
if (schema.format) {
if (schema.type) {
return `${schema.type}<${schema.format}>`;
@@ -51,10 +57,10 @@ function prettyName(schema: SchemaObject, circular?: boolean) {
}
export function getSchemaName(
- schema: SchemaObject,
+ schema: SchemaObject | string,
circular?: boolean
): string {
- if (schema.items) {
+ if (typeof schema !== "string" && schema.items) {
return prettyName(schema.items, circular) + "[]";
}
diff --git a/packages/docusaurus-plugin-openapi-docs/src/openapi/__fixtures__/examples/petstore.yaml b/packages/docusaurus-plugin-openapi-docs/src/openapi/__fixtures__/examples/petstore.yaml
new file mode 100644
index 000000000..1de97c514
--- /dev/null
+++ b/packages/docusaurus-plugin-openapi-docs/src/openapi/__fixtures__/examples/petstore.yaml
@@ -0,0 +1,1355 @@
+openapi: 3.0.0
+servers:
+ - url: https://petstore.swagger.io/v2
+ description: Default server
+ - url: https://petstore.swagger.io/sandbox
+ description: Sandbox server
+ - url: http://127.0.0.1:4010
+ description: Prism Mock API (local)
+info:
+ description: |
+ This is a sample server Petstore server.
+ You can find out more about Swagger at
+ [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).
+ For this sample, you can use the api key `special-key` to test the authorization filters.
+
+ ## Introduction
+ This API is documented in **OpenAPI format** and is based on
+ [Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
+ It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
+ tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard
+ OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md).
+
+ ## OpenAPI Specification
+ This API is documented in **OpenAPI format** and is based on
+ [Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
+ It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
+ tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard
+ OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md).
+
+ ## Cross-Origin Resource Sharing
+ This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/).
+ And that allows cross-domain communication from the browser.
+ All responses have a wildcard same-origin which makes them completely public and accessible to everyone, including any code on any site.
+
+ ## Authentication
+
+ Petstore offers two forms of authentication:
+ - API Key
+ - OAuth2
+
+ OAuth2 - an open protocol to allow secure authorization in a simple
+ and standard method from web, mobile and desktop applications.
+
+ version: 2.0.0
+ title: Swagger Petstore YAML
+ 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
+ x-dark-logo:
+ url: "/img/petstore-logo-dark.png"
+ altText: "Petstore dark 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
+ x-displayName: Pets
+ - name: store
+ description: Access to Petstore orders
+ x-displayName: Petstore Orders
+ - name: user
+ description: Operations about user
+ x-displayName: Users
+ - name: pet_model
+ x-displayName: The Pet Model
+ description: |
+
+ - name: store_model
+ x-displayName: The Order Model
+ description: |
+
+x-tagGroups:
+ - name: General
+ tags:
+ - pet
+ - store
+ - name: User Management
+ tags:
+ - user
+ - name: Models
+ tags:
+ - pet_model
+ - store_model
+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:
+ "200":
+ description: |
+ All good, here's some MDX:
+
+ :::note
+
+ Some content but no markdown is supported :(
+
+ :::
+
+ :::tip
+ A TIP with no leading or trailing spaces between delimiters.
+ :::
+
+ :::info
+
+ Some **content** with _Markdown_ `syntax`. Check [this `api`](#).
+
+ | Month | Savings |
+ | -------- | ------- |
+ | January | $250 |
+ | February | $80 |
+ | March | $420 |
+
+ Hmm.....
+
+ :::
+
+ :::warning
+
+ Some **content** with _Markdown_ `syntax`. Check [this `api`](#) which is not supported :( yet
+
+ :::
+
+ :::danger
+
+ Some plain text
+
+ Some more plain text
+
+ And more
+
+ :::
+
+ A **code snippet**!
+
+ ```python
+ print("hello")
+ ```
+
+ _And_ a table!
+
+ | Month | Savings |
+ | -------- | ------- |
+ | January | $250 |
+ | February | $80 |
+ | March | $420 |
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ data:
+ oneOf:
+ - type: string
+ - type: object
+ "405":
+ description: Invalid input
+ security:
+ - petstore_auth:
+ - "write:pets"
+ - "read:pets"
+ - api_key: []
+ - ApiKeyAuth: []
+ - BasicAuth: []
+ - BearerAuth: []
+ - OAuth2: []
+ - OpenID: []
+
+ x-codeSamples:
+ - lang: "C#"
+ source: |
+ PetStore.v1.Pet pet = new PetStore.v1.Pet();
+ pet.setApiKey("your api key");
+ pet.petType = PetStore.v1.Pet.TYPE_DOG;
+ pet.name = "Rex";
+ // set other fields
+ PetStoreResponse response = pet.create();
+ if (response.statusCode == HttpStatusCode.Created)
+ {
+ // Successfully created
+ }
+ else
+ {
+ // Something wrong -- check response for errors
+ Console.WriteLine(response.getRawResponse());
+ }
+ - lang: PHP
+ label: Custom
+ source: |
+ $form = new \PetStore\Entities\Pet();
+ $form->setPetType("Dog");
+ $form->setName("Rex");
+ // set other fields
+ try {
+ $pet = $client->pets()->create($form);
+ } catch (UnprocessableEntityException $e) {
+ var_dump($e->getErrors());
+ }
+ 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();
+ $form->setPetId(1);
+ $form->setPetType("Dog");
+ $form->setName("Rex");
+ // set other fields
+ try {
+ $pet = $client->pets()->update($form);
+ } catch (UnprocessableEntityException $e) {
+ var_dump($e->getErrors());
+ }
+ 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
+ x-enumDescriptions:
+ available: When the pet is available
+ pending: When the pet is being sold
+ sold: When the pet has been 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:
+ - api_key: []
+ /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
+ items:
+ $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
+ 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"
+ examples:
+ OrderDelivered:
+ summary: Order delivered
+ value:
+ quantity: 4
+ shipDate: 2022-10-12
+ status: delivered
+ requestId: 444-4444-444-4444
+ OrderPlaced:
+ summary: Order placed
+ value:
+ quantity: 10
+ shipDate: 2022-10-01
+ status: placed
+ requestId: 111-222-333-444
+ OrderApproved:
+ summary: Order approved
+ value:
+ quantity: 1000
+ shipDate: 2022-09-01
+ status: approved
+ requestId: 000-111-222-333
+ 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: |
+
+
+ 123
+ inProgress
+ 2018-10-19T16:46:45Z
+
+ 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();
+ pet.setApiKey("your api key");
+ pet.petType = PetStore.v1.Pet.TYPE_DOG;
+ pet.name = "Rex";
+ // set other fields
+ PetStoreResponse response = pet.create();
+ if (response.statusCode == HttpStatusCode.Created)
+ {
+ // Successfully created
+ }
+ else
+ {
+ // Something wrong -- check response for errors
+ Console.WriteLine(response.getRawResponse());
+ }
+ - lang: PHP
+ source: |
+ $form = new \PetStore\Entities\Pet();
+ $form->setPetType("Dog");
+ $form->setName("Rex");
+ // set other fields
+ try {
+ $pet = $client->pets()->create($form);
+ } catch (UnprocessableEntityException $e) {
+ var_dump($e->getErrors());
+ }
+ 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: |
+
+
+ 123
+ inProgress
+ 2018-10-19T16:46:45Z
+
+ 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: A callback triggered every time an Order is shipped 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"
+ 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 list
+ 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:
+ summary: |
+ This is an example of using **Docusaurus** `markdown` in a summary. Note that admonitions are not fully supported.
+ 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: .01
+ default: 0
+ 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
+ description: A pet
+ required:
+ - name
+ - photoUrls
+ - tags
+ 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
+ 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
+ x-enumDescriptions:
+ available: When the pet is available
+ pending: When the pet is being sold
+ sold: |
+ When the pet has been sold.
+
+ These descriptions can contain line
+
+ breaks and also [links](https://docusaurus.io/)
+ petType:
+ description: Type of a pet
+ type: string
+ oneOf:
+ - $ref: "#/components/schemas/Cat"
+ - $ref: "#/components/schemas/Dog"
+ - $ref: "#/components/schemas/HoneyBee"
+ xml:
+ name: Pet
+ Tag:
+ type: object
+ description: A pet tag
+ 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
+ xml:
+ name: User
+ requestBodies:
+ Pet:
+ content:
+ application/json:
+ schema:
+ allOf:
+ - description: My Pet
+ title: Pettie
+ - $ref: "#/components/schemas/Pet"
+ example:
+ summary: A great example!
+ category:
+ name: Great Dane
+ sub:
+ prop1: Just a test property
+ name: Pepper
+ photoUrls:
+ - https://assets.orvis.com/is/image/orvisprd/great-dane
+ tags:
+ - name: Great Danes
+ status: pending
+ petType:
+ huntingSkill: lazy
+ application/xml:
+ schema:
+ type: "object"
+ properties:
+ name:
+ type: string
+ description: hooray
+ 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.
+ OAuth2 is also a safer and more secure way to give you access.
+ 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.
+ type: apiKey
+ name: api_key
+ in: header
+ BasicAuth:
+ type: http
+ scheme: basic
+ BearerAuth:
+ type: http
+ scheme: bearer
+ ApiKeyAuth:
+ type: apiKey
+ in: header
+ name: X-API-Key
+ OpenID:
+ type: openIdConnect
+ openIdConnectUrl: https://example.com/.well-known/openid-configuration
+ OAuth2:
+ type: oauth2
+ flows:
+ authorizationCode:
+ authorizationUrl: https://example.com/oauth/authorize
+ tokenUrl: https://example.com/oauth/token
+ scopes:
+ read: Grants read access
+ write: Grants write access
+ admin: Grants access to admin operations
+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
diff --git a/packages/docusaurus-plugin-openapi-docs/src/openapi/__fixtures__/examples/self-ref.yaml b/packages/docusaurus-plugin-openapi-docs/src/openapi/__fixtures__/examples/self-ref.yaml
new file mode 100644
index 000000000..6c5024783
--- /dev/null
+++ b/packages/docusaurus-plugin-openapi-docs/src/openapi/__fixtures__/examples/self-ref.yaml
@@ -0,0 +1,15 @@
+openapi: 3.0.3
+info:
+ title: Self Ref Example
+ version: 1.0.0
+paths: {}
+components:
+ schemas:
+ Node:
+ title: Node
+ type: object
+ properties:
+ name:
+ type: string
+ child:
+ $ref: "#/components/schemas/Node"
diff --git a/packages/docusaurus-plugin-openapi-docs/src/openapi/circular.test.ts b/packages/docusaurus-plugin-openapi-docs/src/openapi/circular.test.ts
new file mode 100644
index 000000000..b4fa93098
--- /dev/null
+++ b/packages/docusaurus-plugin-openapi-docs/src/openapi/circular.test.ts
@@ -0,0 +1,25 @@
+/* ============================================================================
+ * 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 path from "path";
+
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { posixPath } from "@docusaurus/utils";
+
+import { loadAndResolveSpec } from "./utils/loadAndResolveSpec";
+
+describe("circular references", () => {
+ it("flags self referencing schemas", async () => {
+ const file = posixPath(
+ path.join(__dirname, "__fixtures__/examples/self-ref.yaml")
+ );
+ const spec: any = await loadAndResolveSpec(file);
+ expect(spec.components.schemas.Node.properties.child).toBe(
+ "circular(Node)"
+ );
+ });
+});
diff --git a/packages/docusaurus-plugin-openapi-docs/src/openapi/petstoreCircular.test.ts b/packages/docusaurus-plugin-openapi-docs/src/openapi/petstoreCircular.test.ts
new file mode 100644
index 000000000..10ccc0d74
--- /dev/null
+++ b/packages/docusaurus-plugin-openapi-docs/src/openapi/petstoreCircular.test.ts
@@ -0,0 +1,25 @@
+/* ============================================================================
+ * 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 path from "path";
+
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { posixPath } from "@docusaurus/utils";
+
+import { loadAndResolveSpec } from "./utils/loadAndResolveSpec";
+
+describe("petstore circular refs", () => {
+ it("labels friend property as circular", async () => {
+ const file = posixPath(
+ path.join(__dirname, "__fixtures__/examples/petstore.yaml")
+ );
+ const spec: any = await loadAndResolveSpec(file);
+ expect(spec.components.schemas.Pet.properties.friend.allOf[0]).toBe(
+ "circular(Pet)"
+ );
+ });
+});
diff --git a/packages/docusaurus-plugin-openapi-docs/src/openapi/types.ts b/packages/docusaurus-plugin-openapi-docs/src/openapi/types.ts
index b623a6fb1..8bf328e02 100644
--- a/packages/docusaurus-plugin-openapi-docs/src/openapi/types.ts
+++ b/packages/docusaurus-plugin-openapi-docs/src/openapi/types.ts
@@ -359,6 +359,7 @@ export type SchemaObject = Omit<
example?: any;
deprecated?: boolean;
"x-tags"?: string[];
+ "x-circular-ref"?: boolean;
"x-enumDescriptions"?: Record;
};
@@ -392,6 +393,7 @@ export type SchemaObjectWithRef = Omit<
externalDocs?: ExternalDocumentationObject;
example?: any;
deprecated?: boolean;
+ "x-circular-ref"?: boolean;
};
export interface DiscriminatorObject {
diff --git a/packages/docusaurus-plugin-openapi-docs/src/openapi/utils/loadAndResolveSpec.ts b/packages/docusaurus-plugin-openapi-docs/src/openapi/utils/loadAndResolveSpec.ts
index ee59054b2..d525e0f01 100644
--- a/packages/docusaurus-plugin-openapi-docs/src/openapi/utils/loadAndResolveSpec.ts
+++ b/packages/docusaurus-plugin-openapi-docs/src/openapi/utils/loadAndResolveSpec.ts
@@ -22,11 +22,19 @@ function serializer(replacer: any, cycleReplacer: any) {
if (cycleReplacer === undefined)
cycleReplacer = function (key: any, value: any) {
+ if (value?.["x-circular-ref"]) {
+ return value.title ? `circular(${value.title})` : "circular()";
+ }
if (stack[0] === value) return "circular()";
return value.title ? `circular(${value.title})` : "circular()";
};
return function (key: any, value: any) {
+ // Convert objects flagged as circular
+ if (value?.["x-circular-ref"]) {
+ return value.title ? `circular(${value.title})` : "circular()";
+ }
+
// Resolve discriminator ref pointers
if (value?.discriminator !== undefined) {
const parser = new OpenAPIParser(stack[0]);
@@ -115,6 +123,60 @@ async function resolveJsonRefs(specUrlOrObject: object | string) {
}
}
+function markCircularRefs(
+ obj: any,
+ stack: any[] = [],
+ key: string | undefined = undefined
+): boolean | "circular" {
+ if (!obj || typeof obj !== "object") {
+ return false;
+ }
+
+ if (stack.includes(obj)) {
+ return "circular";
+ }
+
+ stack.push(obj);
+ let foundCircular = false;
+
+ if (Array.isArray(obj)) {
+ for (let i = 0; i < obj.length; i++) {
+ const val = obj[i];
+ if (typeof val === "object" && val !== null) {
+ const res = markCircularRefs(val, stack, String(i));
+ if (res) {
+ if (res === "circular") {
+ obj[i] = {
+ title: val.title ?? String(i),
+ "x-circular-ref": true,
+ };
+ }
+ foundCircular = true;
+ }
+ }
+ }
+ } else {
+ for (const k of Object.keys(obj)) {
+ const val = obj[k];
+ if (typeof val === "object" && val !== null) {
+ const res = markCircularRefs(val, stack, k);
+ if (res) {
+ if (res === "circular") {
+ obj[k] = {
+ title: val.title ?? k,
+ "x-circular-ref": true,
+ };
+ }
+ foundCircular = true;
+ }
+ }
+ }
+ }
+
+ stack.pop();
+ return foundCircular;
+}
+
export async function loadAndResolveSpec(specUrlOrObject: object | string) {
const config = new Config({} as ResolvedConfig);
const bundleOpts = {
@@ -154,6 +216,9 @@ export async function loadAndResolveSpec(specUrlOrObject: object | string) {
const resolved = await resolveJsonRefs(parsed);
+ // Mark any remaining circular references
+ markCircularRefs(resolved);
+
// Force serialization and replace circular $ref pointers
// @ts-ignore
const serialized = JSON.stringify(resolved, serializer());
diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/SchemaItem/_SchemaItem.scss b/packages/docusaurus-theme-openapi-docs/src/theme/SchemaItem/_SchemaItem.scss
index e5bd389ea..6aedc8605 100644
--- a/packages/docusaurus-theme-openapi-docs/src/theme/SchemaItem/_SchemaItem.scss
+++ b/packages/docusaurus-theme-openapi-docs/src/theme/SchemaItem/_SchemaItem.scss
@@ -67,6 +67,17 @@
background-color: transparent;
}
+.openapi-schema__circular {
+ display: inline-flex;
+ align-items: center;
+ font-size: 10.5px;
+ font-weight: bold;
+ text-transform: uppercase;
+ color: var(--openapi-circular);
+ margin-left: 1%;
+ background-color: transparent;
+}
+
.openapi-schema__strikethrough {
text-decoration: line-through;
}
diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/SchemaItem/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/SchemaItem/index.tsx
index a77d1f3ee..5f565d99b 100644
--- a/packages/docusaurus-theme-openapi-docs/src/theme/SchemaItem/index.tsx
+++ b/packages/docusaurus-theme-openapi-docs/src/theme/SchemaItem/index.tsx
@@ -92,6 +92,23 @@ export default function SchemaItem(props: Props) {
nullable
));
+ const circularMatch =
+ typeof schemaName === "string" && schemaName.startsWith("circular(")
+ ? schemaName.match(/^circular\\(([^)]*)\\)/)
+ : null;
+
+ const circularTitle = schema?.["x-circular-ref"]
+ ? schema.title
+ ? `circular(${schema.title})`
+ : "circular"
+ : circularMatch
+ ? `circular(${circularMatch[1]})`
+ : null;
+
+ const renderCircular = guard(circularTitle, () => (
+ {circularTitle}
+ ));
+
const renderEnumDescriptions = guard(
getEnumDescriptionMarkdown(enumDescriptions),
(value) => {
@@ -206,6 +223,7 @@ export default function SchemaItem(props: Props) {
{renderNullable}
{renderRequired}
{renderDeprecated}
+ {renderCircular}
{renderSchemaDescription}
{renderEnumDescriptions}
diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/styles.scss b/packages/docusaurus-theme-openapi-docs/src/theme/styles.scss
index 5e0933346..fef5bd6ec 100644
--- a/packages/docusaurus-theme-openapi-docs/src/theme/styles.scss
+++ b/packages/docusaurus-theme-openapi-docs/src/theme/styles.scss
@@ -46,6 +46,7 @@
--openapi-required: var(--ifm-color-danger);
--openapi-deprecated: var(--ifm-color-warning);
--openapi-nullable: var(--ifm-color-info);
+ --openapi-circular: var(--ifm-color-primary);
--openapi-code-blue: var(--ifm-color-info);
--openapi-code-red: var(--ifm-color-danger);
--openapi-code-orange: var(--ifm-color-warning);