docs-util: fixes to base OAS + circular-patch redocly plugin (#7382)

* docs-util: remove MultipleErrors schema from base OAS

* fixes to circular patch plugin

* general fixes

* change nested schemas to references
This commit is contained in:
Shahed Nasser
2024-05-27 15:29:48 +03:00
committed by GitHub
parent b5b41c7a33
commit 98615c388b
15 changed files with 237 additions and 217 deletions

View File

@@ -55,18 +55,21 @@ function CircularPatch({ schemas = {}, verbose = false }) {
return
}
for (const patch of patches) {
if (resolved.location.pointer !== patch.schemaPointer) {
if (resolved.location.pointer.replaceAll("\n", "") !== patch.schemaPointer) {
continue
}
const ctxSchemaPointer =
ctx.location.pointer.match(refPathPrefixRegex)[0]
if (!patch.schemaToPatchPointers.includes(ctxSchemaPointer)) {
const pointerMatch = ctx.location.pointer.match(refPathPrefixRegex) ||
resolved.location.pointer.match(refPathPrefixRegex)
if (!pointerMatch.length) {
continue
}
if (!patch.schemaToPatchPointers.includes(pointerMatch[0])) {
continue
}
applyPatch(ref)
if (verbose) {
logs.push(
`${ctxSchemaPointer.substring(refPathPrefixLength)} patch $ref to ${
`${pointerMatch[0].substring(refPathPrefixLength)} patch $ref to ${
patch.schemaName
}`
)

View File

@@ -6,138 +6,13 @@ plugins:
decorators:
medusa/circular-patch:
schemas:
Address:
- Customer
Cart:
- Customer
- Order
- Payment
- PaymentSession
ClaimImage:
- ClaimItem
ClaimItem:
- ClaimOrder
ClaimOrder:
- Fulfillment
- Order
- Return
Country:
- Region
Customer:
- Order
CustomerGroup:
- Customer
- PriceList
Discount:
- Discount
DiscountRule:
- DiscountCondition
DraftOrder:
- Cart
- Order
Fulfillment:
- ClaimOrder
- Order
- Swap
FulfillmentItem:
- Fulfillment
GiftCard:
- Order
GiftCardTransaction:
- GiftCard
- Order
LineItem:
- Cart
- ClaimOrder
- Order
- OrderEdit
- Swap
LineItemAdjustment:
- LineItem
LineItemTaxLine:
- LineItem
MoneyAmount:
- PriceList
- ProductVariant
- Region
Notification:
- Notification
Order:
- Cart
- ClaimOrder
- Customer
- DraftOrder
- Fulfillment
- OrderEdit
- Payment
- Refund
- Return
- Swap
OrderEdit:
- Order
OrderItemChange:
- OrderEdit
Payment:
- Cart
- Order
- Swap
ProductCategory:
- ProductCategory
- Product
ProductCollection:
- Product
ProductOption:
- Product
ProductOptionValue:
- ProductOption
- ProductVariant
ProductVariant:
- Product
ProductVariantInventoryItem:
- ProductVariant
Refund:
- Order
- Payment
Return:
- ClaimOrder
- Order
- Swap
ReturnItem:
- Return
ReturnReason:
- ReturnReason
SalesChannelLocation:
- SalesChannel
ShippingMethod:
- Cart
- ClaimOrder
- Order
- Payment
- Return
- Swap
ShippingMethodTaxLine:
- ShippingMethod
ShippingOption:
- Region
ShippingOptionRequirement:
- ShippingOption
ShippingProfile:
- Product
- ShippingOption
Swap:
- Cart
- Fulfillment
- Order
- Payment
- Return
TaxRate:
- Region
TrackingLink:
- Fulfillment
SalesChannel:
- Order
- Cart
- PublishableApiKey
CustomerGroupResponse:
- CustomerResponse
ProductCategoryResponse:

View File

@@ -3,6 +3,7 @@ import { SchemaObject } from "../../../types/openapi"
import path from "path"
import { existsSync, promises as fs } from "fs"
import { parseDocument } from "yaml"
import dereference from "../../../utils/dereference"
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
@@ -37,14 +38,14 @@ export async function GET(request: Request) {
.replace("#/components/schemas/", "")
.replaceAll("./components/schemas/", "")
const schemaPath = path.join(
const baseSchemasPath = path.join(
process.cwd(),
"specs",
area,
"components",
"schemas",
name
"schemas"
)
const schemaPath = path.join(baseSchemasPath, name)
if (!existsSync(schemaPath)) {
return NextResponse.json(
@@ -61,9 +62,17 @@ export async function GET(request: Request) {
const schemaContent = await fs.readFile(schemaPath, "utf-8")
const schema = parseDocument(schemaContent).toJS() as SchemaObject
// resolve references in schema
const dereferencedDocument = await dereference({
basePath: baseSchemasPath,
schemas: [schema],
})
return NextResponse.json(
{
schema,
schema: dereferencedDocument.components?.schemas
? Object.values(dereferencedDocument.components?.schemas)[0]
: schema,
},
{
status: 200,

View File

@@ -114,7 +114,7 @@ const TagOperationParametersObject = ({
)
}
if (!schema.properties) {
if (!schema.properties || !Object.values(schema.properties).length) {
return getPropertyDescriptionElm()
}

View File

@@ -15,6 +15,7 @@ import SectionContainer from "../../../Section/Container"
import useSchemaExample from "../../../../hooks/use-schema-example"
import { InView } from "react-intersection-observer"
import checkElementInViewport from "../../../../utils/check-element-in-viewport"
import { singular } from "pluralize"
export type TagSectionSchemaProps = {
schema: SchemaObject
@@ -24,15 +25,10 @@ export type TagSectionSchemaProps = {
const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
const { addItems, setActivePath, activePath } = useSidebar()
const tagSlugName = useMemo(() => getSectionId([tagName]), [tagName])
const formattedName = useMemo(() => {
if (!schema["x-schemaName"]) {
return tagName.replaceAll(" ", "")
}
return schema["x-schemaName"]
.replaceAll(/^(Admin|Store)/g, "")
.replaceAll(/^Create/g, "")
}, [schema, tagName])
const formattedName = useMemo(
() => singular(tagName).replaceAll(" ", ""),
[tagName]
)
const schemaSlug = useMemo(
() => getSectionId([tagName, formattedName, "schema"]),
[tagName, formattedName]

View File

@@ -36,6 +36,7 @@
"next-mdx-remote": "^4.4.1",
"openapi-sampler": "^1.3.1",
"openapi-types": "^12.1.3",
"pluralize": "^8.0.0",
"postcss": "8.4.27",
"prism-react-renderer": "2.3.1",
"react": "^18.2.0",
@@ -54,6 +55,7 @@
"devDependencies": {
"@next/bundle-analyzer": "^14.2.3",
"@types/jsdom": "^21.1.1",
"@types/pluralize": "^0.0.33",
"types": "*"
},
"engines": {

View File

@@ -118,3 +118,7 @@ export type ExpandedDocument = Document & {
export type TagObject = OpenAPIV3.TagObject & {
"x-associatedSchema"?: OpenAPIV3.ReferenceObject
}
export type ParsedPathItemObject = OpenAPIV3.PathItemObject<Operation> & {
operationPath?: string
}

View File

@@ -0,0 +1,59 @@
import { Document, ParsedPathItemObject, SchemaObject } from "@/types/openapi"
import OpenAPIParser from "@readme/openapi-parser"
type Options = {
basePath: string
paths?: ParsedPathItemObject[]
schemas?: SchemaObject[]
}
export default async function dereference({
basePath,
paths,
schemas,
}: Options): Promise<Document> {
// dereference the references in the paths
let document: Document = {
paths: {},
// These attributes are only for validation purposes
openapi: "3.0.0",
info: {
title: "Medusa API",
version: "1.0.0",
},
components: {
schemas: {},
},
}
if (paths) {
paths.forEach((path) => {
const documentPath = path.operationPath || ""
delete path.operationPath
document.paths[documentPath] = path
})
}
if (schemas) {
schemas.forEach((schema) => {
if (!schema["x-schemaName"]) {
return
}
document.components!.schemas![schema["x-schemaName"]] = schema
})
}
// resolve references in paths
document = (await OpenAPIParser.dereference(`${basePath}/`, document, {
parse: {
text: {
// This ensures that all files are parsed as expected
// resolving the error with incorrect new lines for
// example files having `undefined` extension.
canParse: /.*/,
},
},
})) as unknown as Document
return document
}

View File

@@ -1,14 +1,10 @@
import path from "path"
import { promises as fs } from "fs"
import type { OpenAPIV3 } from "openapi-types"
import type { Operation, Document } from "@/types/openapi"
import type { Operation, Document, ParsedPathItemObject } from "@/types/openapi"
import readSpecDocument from "./read-spec-document"
import getSectionId from "./get-section-id"
import OpenAPIParser from "@readme/openapi-parser"
type ParsedPathItemObject = OpenAPIV3.PathItemObject<Operation> & {
operationPath?: string
}
import dereference from "./dereference"
export default async function getPathsOfTag(
tagName: string,
@@ -46,34 +42,8 @@ export default async function getPathsOfTag(
})
)
// dereference the references in the paths
let paths: Document = {
paths: {},
// These attributes are only for validation purposes
openapi: "3.0.0",
info: {
title: "Medusa API",
version: "1.0.0",
},
}
documents.forEach((document) => {
const documentPath = document.operationPath || ""
delete document.operationPath
paths.paths[documentPath] = document
return dereference({
basePath,
paths: documents,
})
// resolve references in paths
paths = (await OpenAPIParser.dereference(`${basePath}/`, paths, {
parse: {
text: {
// This ensures that all files are parsed as expected
// resolving the error with incorrect new lines for
// example files having `undefined` extension.
canParse: /.*/,
},
},
})) as unknown as Document
return paths
}

View File

@@ -123,20 +123,16 @@ components:
message: Entity with id 1 was not found
type: not_found
400_error:
description: Client Error or Multiple Errors
description: Client Error
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/Error"
- $ref: "#/components/schemas/MultipleErrors"
$ref: "#/components/schemas/Error"
examples:
not_allowed:
$ref: "#/components/examples/not_allowed_error"
invalid_data:
$ref: "#/components/examples/invalid_data_error"
MultipleErrors:
$ref: "#/components/examples/multiple_errors"
500_error:
description: Server Error
content:

View File

@@ -81,20 +81,16 @@ components:
message: Entity with id 1 was not found
type: not_found
400_error:
description: Client Error or Multiple Errors
description: Client Error
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/Error"
- $ref: "#/components/schemas/MultipleErrors"
$ref: "#/components/schemas/Error"
examples:
not_allowed:
$ref: "#/components/examples/not_allowed_error"
invalid_data:
$ref: "#/components/examples/invalid_data_error"
MultipleErrors:
$ref: "#/components/examples/multiple_errors"
500_error:
description: Server Error
content:

View File

@@ -54,7 +54,7 @@ class OasSchemaHelper {
* @returns The schema as a reference. If the schema doesn't have the x-schemaName property set,
* the schema isn't converted and `undefined` is returned.
*/
schemaToReference(
namedSchemaToReference(
schema: OpenApiSchema,
level = 0
): OpenAPIV3.ReferenceObject | undefined {
@@ -84,7 +84,7 @@ class OasSchemaHelper {
!("$ref" in propertySchema.items)
) {
propertySchema.items =
this.schemaToReference(propertySchema.items, level + 1) ||
this.namedSchemaToReference(propertySchema.items, level + 1) ||
propertySchema.items
} else if (
propertySchema.oneOf ||
@@ -101,13 +101,15 @@ class OasSchemaHelper {
}
schemaTarget![index] =
this.schemaToReference(item, level + 1) || item
this.namedSchemaToReference(item, level + 1) || item
})
}
schema.properties![property] =
this.schemaToReference(propertySchema as OpenApiSchema, level + 1) ||
propertySchema
this.namedSchemaToReference(
propertySchema as OpenApiSchema,
level + 1
) || propertySchema
})
}
@@ -118,6 +120,67 @@ class OasSchemaHelper {
}
}
schemaChildrenToRefs(schema: OpenApiSchema, level = 0): OpenApiSchema {
if (level > this.MAX_LEVEL) {
return schema
}
const clonedSchema = Object.assign({}, schema)
if (clonedSchema.allOf) {
clonedSchema.allOf = clonedSchema.allOf.map((item) => {
if (this.isRefObject(item)) {
return item
}
const transformChildItems = this.schemaChildrenToRefs(item, level + 1)
return (
this.namedSchemaToReference(transformChildItems) ||
transformChildItems
)
})
} else if (clonedSchema.oneOf) {
clonedSchema.oneOf = clonedSchema.oneOf.map((item) => {
if (this.isRefObject(item)) {
return item
}
const transformChildItems = this.schemaChildrenToRefs(item, level + 1)
return (
this.namedSchemaToReference(transformChildItems) ||
transformChildItems
)
})
} else if (
clonedSchema.type === "array" &&
!this.isRefObject(clonedSchema.items)
) {
const transformedChildItems = this.schemaChildrenToRefs(
clonedSchema.items,
level
)
clonedSchema.items =
this.namedSchemaToReference(transformedChildItems) ||
transformedChildItems
} else if (clonedSchema.properties && !clonedSchema["x-schemaName"]) {
Object.entries(clonedSchema.properties).forEach(([key, property]) => {
if (this.isRefObject(property)) {
return
}
const transformedProperty = this.schemaChildrenToRefs(
property,
level + 1
)
schema.properties![key] =
this.namedSchemaToReference(transformedProperty) ||
transformedProperty
})
}
return clonedSchema
}
/**
* Retrieve the expected file name of the schema.
*
@@ -264,7 +327,11 @@ class OasSchemaHelper {
*/
tagNameToSchemaName(tagName: string, area: OasArea): string[] {
const mainSchemaName = wordsToPascal(pluralize.singular(tagName))
return [mainSchemaName, `${capitalize(area)}Create${mainSchemaName}`]
return [
mainSchemaName,
`${mainSchemaName}Response`,
`${capitalize(area)}Create${mainSchemaName}`,
]
}
}

View File

@@ -15,6 +15,18 @@ class SchemaFactory {
BigNumber: {
type: "string",
},
created_at: {
type: "string",
format: "date-time",
},
updated_at: {
type: "string",
format: "date-time",
},
deleted_at: {
type: "string",
format: "date-time",
},
}
/**

View File

@@ -260,8 +260,8 @@ class OasKindGenerator extends FunctionKindGenerator {
content: {
"application/json": {
schema:
this.oasSchemaHelper.schemaToReference(requestSchema) ||
requestSchema,
this.oasSchemaHelper.namedSchemaToReference(requestSchema) ||
this.oasSchemaHelper.schemaChildrenToRefs(requestSchema),
},
},
}
@@ -335,8 +335,8 @@ class OasKindGenerator extends FunctionKindGenerator {
;(oas.responses[responseStatus] as OpenAPIV3.ResponseObject).content = {
"application/json": {
schema:
this.oasSchemaHelper.schemaToReference(responseSchema) ||
responseSchema,
this.oasSchemaHelper.namedSchemaToReference(responseSchema) ||
this.oasSchemaHelper.schemaChildrenToRefs(responseSchema),
},
}
}
@@ -462,7 +462,7 @@ class OasKindGenerator extends FunctionKindGenerator {
newSchema: requestSchema,
})
if (!updatedRequestSchema && existingRequestBodySchema) {
if (!updatedRequestSchema) {
// if there's no request schema, remove it from the OAS
delete oas.requestBody
} else {
@@ -470,10 +470,11 @@ class OasKindGenerator extends FunctionKindGenerator {
oas.requestBody = {
content: {
"application/json": {
schema: updatedRequestSchema
? this.oasSchemaHelper.schemaToReference(updatedRequestSchema) ||
schema:
this.oasSchemaHelper.namedSchemaToReference(
updatedRequestSchema
: updatedRequestSchema,
) ||
this.oasSchemaHelper.schemaChildrenToRefs(updatedRequestSchema),
},
},
}
@@ -495,8 +496,10 @@ class OasKindGenerator extends FunctionKindGenerator {
content: {
"application/json": {
schema:
this.oasSchemaHelper.schemaToReference(newResponseSchema) ||
newResponseSchema,
this.oasSchemaHelper.namedSchemaToReference(
newResponseSchema
) ||
this.oasSchemaHelper.schemaChildrenToRefs(newResponseSchema),
},
},
},
@@ -532,8 +535,10 @@ class OasKindGenerator extends FunctionKindGenerator {
content: {
"application/json": {
schema: updatedResponseSchema
? this.oasSchemaHelper.schemaToReference(updatedResponseSchema) ||
updatedResponseSchema
? this.oasSchemaHelper.namedSchemaToReference(
updatedResponseSchema
) ||
this.oasSchemaHelper.schemaChildrenToRefs(updatedResponseSchema)
: updatedResponseSchema,
},
},
@@ -1122,6 +1127,13 @@ class OasKindGenerator extends FunctionKindGenerator {
return
}
// check if parameter is already added
const isAdded = parameters.some((param) => param.name === key)
if (isAdded) {
return
}
parameters.push(
this.getParameterObject({
name: key,
@@ -1134,7 +1146,7 @@ class OasKindGenerator extends FunctionKindGenerator {
}
)
}
} else {
} else if (methodName !== "delete") {
requestSchema = parameterSchema
}
}
@@ -1273,8 +1285,8 @@ class OasKindGenerator extends FunctionKindGenerator {
descriptionOptions as SchemaDescriptionOptions
)
: title
? this.getSchemaDescription({ typeStr: title, nodeType: itemType })
: this.defaultSummary
? this.getSchemaDescription({ typeStr: title, nodeType: itemType })
: this.defaultSummary
const typeAsString =
zodObjectTypeName || this.checker.typeToString(itemType)
@@ -1315,8 +1327,8 @@ class OasKindGenerator extends FunctionKindGenerator {
itemType.flags === ts.TypeFlags.StringLiteral
? "string"
: itemType.flags === ts.TypeFlags.NumberLiteral
? "number"
: "boolean",
? "number"
: "boolean",
title: title || typeAsString,
description,
format: this.getSchemaTypeFormat({
@@ -1518,13 +1530,16 @@ class OasKindGenerator extends FunctionKindGenerator {
: undefined,
required:
requiredProperties.length > 0 ? requiredProperties : undefined,
properties,
}
if (Object.values(properties).length) {
objSchema.properties = properties
}
if (objSchema["x-schemaName"]) {
// add object to schemas to be created
// if necessary
this.oasSchemaHelper.schemaToReference(objSchema)
this.oasSchemaHelper.namedSchemaToReference(objSchema)
}
return objSchema
@@ -1826,7 +1841,7 @@ class OasKindGenerator extends FunctionKindGenerator {
if (oldSchemaObj!.type === "object") {
if (!oldSchemaObj?.properties && newSchemaObj?.properties) {
oldSchemaObj!.properties = newSchemaObj.properties
} else if (oldSchemaObj?.properties && !newSchemaObj?.properties) {
} else if (!newSchemaObj?.properties) {
delete oldSchemaObj!.properties
} else {
// update existing properties

View File

@@ -5040,6 +5040,13 @@ __metadata:
languageName: node
linkType: hard
"@types/pluralize@npm:^0.0.33":
version: 0.0.33
resolution: "@types/pluralize@npm:0.0.33"
checksum: 24899caf85b79dd291a6b6e9b9f3b67b452b18d578d0ac0d531a705bf5ee0361d9386ea1f8532c64de9e22c6e9606c5497787bb5e31bd299c487980436c59785
languageName: node
linkType: hard
"@types/prismjs@npm:^1.26.0":
version: 1.26.3
resolution: "@types/prismjs@npm:1.26.3"
@@ -5657,6 +5664,7 @@ __metadata:
"@types/mapbox__rehype-prism": ^0.8.0
"@types/mdx": ^2.0.5
"@types/node": 20.4.5
"@types/pluralize": ^0.0.33
"@types/react": ^18.2.0
"@types/react-dom": ^18.2.0
"@types/react-transition-group": ^4.4.6
@@ -5671,6 +5679,7 @@ __metadata:
next-mdx-remote: ^4.4.1
openapi-sampler: ^1.3.1
openapi-types: ^12.1.3
pluralize: ^8.0.0
postcss: 8.4.27
prism-react-renderer: 2.3.1
react: ^18.2.0
@@ -12494,6 +12503,13 @@ eslint-config-next@latest:
languageName: node
linkType: hard
"pluralize@npm:^8.0.0":
version: 8.0.0
resolution: "pluralize@npm:8.0.0"
checksum: 2044cfc34b2e8c88b73379ea4a36fc577db04f651c2909041b054c981cd863dd5373ebd030123ab058d194ae615d3a97cfdac653991e499d10caf592e8b3dc33
languageName: node
linkType: hard
"possible-typed-array-names@npm:^1.0.0":
version: 1.0.0
resolution: "possible-typed-array-names@npm:1.0.0"