fix(oas): support additional props, fix circular references patch, and other fixes (#9213)

* chore(oas): support additional props, fix circular references patch, and other fixes

* fix description

* description fixes
This commit is contained in:
Shahed Nasser
2024-09-20 17:22:19 +03:00
committed by GitHub
parent 3084008fc9
commit 6a2a105cf8
8 changed files with 197 additions and 36 deletions
@@ -16,6 +16,7 @@ decorators:
- ProductCategoryResponse - ProductCategoryResponse
AdminShippingOption: AdminShippingOption:
- AdminShippingOption - AdminShippingOption
- AdminServiceZone
AdminProductCategory: AdminProductCategory:
- AdminProductCategory - AdminProductCategory
- AdminProduct - AdminProduct
@@ -41,6 +42,8 @@ decorators:
AdminTaxRegion: AdminTaxRegion:
- AdminTaxRegion - AdminTaxRegion
- AdminTaxRate - AdminTaxRate
AdminInventoryLevel:
- AdminInventoryItem
theme: theme:
openapi: openapi:
theme: theme:
@@ -238,10 +238,13 @@ ${hint}
` `
const redoclyConfigPath = path.join(basePath, "redocly", "redocly-config.yaml") const redoclyConfigPath = path.join(basePath, "redocly", "redocly-config.yaml")
const originalContent = await readYaml(redoclyConfigPath) as CircularReferenceConfig const originalContent = await readYaml(redoclyConfigPath) as CircularReferenceConfig
originalContent.decorators["medusa/circular-patch"].schemas = Object.assign( Object.keys(recommendation).forEach((recKey) => {
originalContent.decorators["medusa/circular-patch"].schemas, originalContent.decorators["medusa/circular-patch"].schemas[recKey] = [
recommendation ...originalContent.decorators["medusa/circular-patch"].schemas[recKey],
) ...recommendation[recKey]
]
})
await writeYaml(redoclyConfigPath, jsonObjectToYamlString(originalContent)) await writeYaml(redoclyConfigPath, jsonObjectToYamlString(originalContent))
console.log(`🟡 Added the following unhandled circular references to redocly-config.ts:` + hintMessage) console.log(`🟡 Added the following unhandled circular references to redocly-config.ts:` + hintMessage)
} }
@@ -1,3 +1,5 @@
"use client"
import type { SchemaObject } from "@/types/openapi" import type { SchemaObject } from "@/types/openapi"
import TagOperationParametersDefault from "../Default" import TagOperationParametersDefault from "../Default"
import dynamic from "next/dynamic" import dynamic from "next/dynamic"
@@ -5,6 +7,7 @@ import type { TagOperationParametersProps } from "../.."
import type { TagsOperationParametersNestedProps } from "../../Nested" import type { TagsOperationParametersNestedProps } from "../../Nested"
import checkRequired from "@/utils/check-required" import checkRequired from "@/utils/check-required"
import { Loading, type DetailsProps } from "docs-ui" import { Loading, type DetailsProps } from "docs-ui"
import { useMemo } from "react"
const TagOperationParameters = dynamic<TagOperationParametersProps>( const TagOperationParameters = dynamic<TagOperationParametersProps>(
async () => import("../.."), async () => import("../.."),
@@ -41,9 +44,22 @@ const TagOperationParametersObject = ({
isRequired, isRequired,
topLevel = false, topLevel = false,
}: TagOperationParametersObjectProps) => { }: TagOperationParametersObjectProps) => {
const isPropertiesEmpty = useMemo(
() => !schema.properties || !Object.values(schema.properties).length,
[schema]
)
const isAdditionalPropertiesEmpty = useMemo(
() =>
!schema.additionalProperties ||
schema.additionalProperties.type !== "object" ||
!schema.additionalProperties.properties ||
!Object.values(schema.additionalProperties.properties).length,
[schema]
)
if ( if (
(schema.type !== "object" && schema.type !== undefined) || (schema.type !== "object" && schema.type !== undefined) ||
(!schema.properties && !name) (isPropertiesEmpty && isAdditionalPropertiesEmpty && !name)
) { ) {
return <></> return <></>
} }
@@ -65,22 +81,19 @@ const TagOperationParametersObject = ({
} }
const getPropertyParameterElms = (isNested = false) => { const getPropertyParameterElms = (isNested = false) => {
const properties = isPropertiesEmpty
? schema.additionalProperties!.properties
: schema.properties
// sort properties to show required fields first // sort properties to show required fields first
const sortedProperties = Object.keys(schema.properties).sort( const sortedProperties = Object.keys(properties).sort(
(property1, property2) => { (property1, property2) => {
schema.properties[property1].isRequired = checkRequired( properties[property1].isRequired = checkRequired(schema, property1)
schema, properties[property2].isRequired = checkRequired(schema, property2)
property1
)
schema.properties[property2].isRequired = checkRequired(
schema,
property2
)
return schema.properties[property1].isRequired && return properties[property1].isRequired &&
schema.properties[property2].isRequired properties[property2].isRequired
? 0 ? 0
: schema.properties[property1].isRequired : properties[property1].isRequired
? -1 ? -1
: 1 : 1
} }
@@ -90,13 +103,12 @@ const TagOperationParametersObject = ({
{sortedProperties.map((property, index) => ( {sortedProperties.map((property, index) => (
<TagOperationParameters <TagOperationParameters
schemaObject={{ schemaObject={{
...schema.properties[property], ...properties[property],
parameterName: property, parameterName: property,
}} }}
key={index} key={index}
isRequired={ isRequired={
schema.properties[property].isRequired || properties[property].isRequired || checkRequired(schema, property)
checkRequired(schema, property)
} }
/> />
))} ))}
@@ -114,7 +126,7 @@ const TagOperationParametersObject = ({
) )
} }
if (!schema.properties || !Object.values(schema.properties).length) { if (isPropertiesEmpty && isAdditionalPropertiesEmpty) {
return getPropertyDescriptionElm() return getPropertyDescriptionElm()
} }
+8 -1
View File
@@ -72,9 +72,15 @@ export type ArraySchemaObject = Omit<
export type NonArraySchemaObject = Omit< export type NonArraySchemaObject = Omit<
OpenAPIV3.NonArraySchemaObject, OpenAPIV3.NonArraySchemaObject,
"properties" | "anyOf" | "allOf" | "oneOf" | "examples" | "properties"
| "anyOf"
| "allOf"
| "oneOf"
| "examples"
| "additionalProperties"
> & { > & {
properties: PropertiesObject properties: PropertiesObject
additionalProperties?: SchemaObject
anyOf?: SchemaObject[] anyOf?: SchemaObject[]
allOf?: SchemaObject[] allOf?: SchemaObject[]
oneOf?: SchemaObject[] oneOf?: SchemaObject[]
@@ -90,6 +96,7 @@ export type SchemaObject = (ArraySchemaObject | NonArraySchemaObject) & {
"x-featureFlag"?: string "x-featureFlag"?: string
"x-expandable"?: string "x-expandable"?: string
"x-schemaName"?: string "x-schemaName"?: string
additionalProperties?: SchemaObject
} }
export type PropertiesObject = { export type PropertiesObject = {
@@ -1,14 +1,48 @@
/** /**
* @schema AdminWorkflowExecutionExecution * @schema AdminWorkflowExecutionExecution
* type: object * type: object
* description: The workflow execution's execution. * description: The workflow execution's steps details.
* x-schemaName: AdminWorkflowExecutionExecution * x-schemaName: AdminWorkflowExecutionExecution
* required: * required:
* - steps * - steps
* properties: * properties:
* steps: * steps:
* type: object * type: object
* description: The execution's steps. * description: The execution's steps. Each object key is a step ID, and the value is the object whose properties are shown below.
* required:
* - id
* - invoke
* - definition
* - compensate
* - depth
* - startedAt
* additionalProperties:
* type: object
* properties:
* id:
* type: string
* title: id
* description: The step's ID.
* invoke:
* type: object
* description: The state of the step's invokation function.
* x-schemaName: WorkflowExecutionFn
* definition:
* type: object
* description: The step's definition details.
* x-schemaName: WorkflowExecutionDefinition
* compensate:
* type: object
* description: The state of the step's compensation function.
* x-schemaName: WorkflowExecutionFn
* depth:
* type: number
* title: depth
* description: The step's depth in the workflow's execution.
* startedAt:
* type: number
* title: startedAt
* description: The timestamp the step started executing.
* *
*/ */
@@ -69,9 +69,16 @@ class OasSchemaHelper {
// check if schema has child schemas // check if schema has child schemas
// and convert those // and convert those
if (schema.properties) { const properties = schema.properties
Object.keys(schema.properties).forEach((property) => { ? schema.properties
const propertySchema = schema.properties![property] : schema.additionalProperties &&
typeof schema.additionalProperties !== "boolean" &&
!this.isRefObject(schema.additionalProperties)
? schema.additionalProperties.properties
: undefined
if (properties) {
Object.keys(properties).forEach((property) => {
const propertySchema = properties![property]
if ("$ref" in propertySchema) { if ("$ref" in propertySchema) {
return return
} }
@@ -105,7 +112,7 @@ class OasSchemaHelper {
}) })
} }
schema.properties![property] = properties![property] =
this.namedSchemaToReference( this.namedSchemaToReference(
propertySchema as OpenApiSchema, propertySchema as OpenApiSchema,
level + 1 level + 1
@@ -178,6 +185,28 @@ class OasSchemaHelper {
this.namedSchemaToReference(transformedProperty) || this.namedSchemaToReference(transformedProperty) ||
transformedProperty transformedProperty
}) })
} else if (
clonedSchema.additionalProperties &&
typeof clonedSchema.additionalProperties !== "boolean" &&
!this.isRefObject(clonedSchema.additionalProperties) &&
clonedSchema.additionalProperties.properties
) {
const additionalProps = schema.additionalProperties as OpenApiSchema
Object.entries(clonedSchema.additionalProperties.properties).forEach(
([key, property]) => {
if (this.isRefObject(property)) {
return
}
const transformedProperty = this.schemaChildrenToRefs(
property,
level + 1
)
additionalProps.properties![key] =
this.namedSchemaToReference(transformedProperty) ||
transformedProperty
}
)
} }
return clonedSchema return clonedSchema
@@ -186,10 +215,17 @@ class OasSchemaHelper {
isSchemaEmpty(schema: OpenApiSchema): boolean { isSchemaEmpty(schema: OpenApiSchema): boolean {
switch (schema.type) { switch (schema.type) {
case "object": case "object":
return ( const isPropertiesEmpty =
schema.properties === undefined || schema.properties === undefined ||
Object.keys(schema.properties).length === 0 Object.keys(schema.properties).length === 0
) const isAdditionalPropertiesEmpty =
schema.additionalProperties === undefined ||
typeof schema.additionalProperties === "boolean" ||
(!this.isRefObject(schema.additionalProperties) &&
(schema.additionalProperties.properties === undefined ||
Object.keys(schema.additionalProperties.properties).length == 0))
return isPropertiesEmpty && isAdditionalPropertiesEmpty
case "array": case "array":
return ( return (
!this.isRefObject(schema.items) && this.isSchemaEmpty(schema.items) !this.isRefObject(schema.items) && this.isSchemaEmpty(schema.items)
@@ -350,6 +386,7 @@ class OasSchemaHelper {
| OpenApiSchema | OpenApiSchema
| OpenAPIV3.RequestBodyObject | OpenAPIV3.RequestBodyObject
| OpenAPIV3.ResponseObject | OpenAPIV3.ResponseObject
| OpenAPIV3.ParameterObject
| undefined | undefined
): schema is OpenAPIV3.ReferenceObject { ): schema is OpenAPIV3.ReferenceObject {
return schema !== undefined && "$ref" in schema return schema !== undefined && "$ref" in schema
@@ -1512,10 +1512,6 @@ class OasKindGenerator extends FunctionKindGenerator {
case itemType.isClassOrInterface() || case itemType.isClassOrInterface() ||
itemType.isTypeParameter() || itemType.isTypeParameter() ||
(itemType as ts.Type).flags === ts.TypeFlags.Object: (itemType as ts.Type).flags === ts.TypeFlags.Object:
const properties: Record<
string,
OpenApiSchema | OpenAPIV3.ReferenceObject
> = {}
const requiredProperties: string[] = [] const requiredProperties: string[] = []
const baseType = itemType.getBaseTypes()?.[0] const baseType = itemType.getBaseTypes()?.[0]
@@ -1535,8 +1531,26 @@ class OasKindGenerator extends FunctionKindGenerator {
required: undefined, required: undefined,
} }
const properties: Record<
string,
OpenApiSchema | OpenAPIV3.ReferenceObject
> = {}
let isAdditionalProperties = false
if (level + 1 <= this.MAX_LEVEL) { if (level + 1 <= this.MAX_LEVEL) {
itemType.getProperties().forEach((property) => { let itemProperties = itemType.getProperties()
if (
!itemProperties.length &&
itemType.aliasTypeArguments?.length === 2 &&
itemType.aliasTypeArguments[0].flags === ts.TypeFlags.String
) {
// object has dynamic keys, so put the properties under additionalProperties
itemProperties = itemType.aliasTypeArguments[1].getProperties()
isAdditionalProperties = true
}
itemProperties.forEach((property) => {
if ( if (
(allowedChildren && !allowedChildren.includes(property.name)) || (allowedChildren && !allowedChildren.includes(property.name)) ||
(disallowedChildren && disallowedChildren.includes(property.name)) (disallowedChildren && disallowedChildren.includes(property.name))
@@ -1624,7 +1638,14 @@ class OasKindGenerator extends FunctionKindGenerator {
} }
if (Object.values(properties).length) { if (Object.values(properties).length) {
objSchema.properties = properties if (isAdditionalProperties) {
objSchema.additionalProperties = {
type: "object",
properties,
}
} else {
objSchema.properties = properties
}
} }
objSchema.required = objSchema.required =
@@ -1987,6 +2008,26 @@ class OasKindGenerator extends FunctionKindGenerator {
oldSchemaObj!.properties = newSchemaObj.properties oldSchemaObj!.properties = newSchemaObj.properties
} else if (!newSchemaObj?.properties) { } else if (!newSchemaObj?.properties) {
delete oldSchemaObj!.properties delete oldSchemaObj!.properties
// check if additionalProperties should be updated
if (
!oldSchemaObj!.additionalProperties &&
newSchemaObj.additionalProperties
) {
oldSchemaObj!.additionalProperties = newSchemaObj.additionalProperties
} else if (!newSchemaObj.additionalProperties) {
delete oldSchemaObj!.additionalProperties
} else if (
typeof oldSchemaObj!.additionalProperties !== "boolean" &&
typeof newSchemaObj!.additionalProperties !== "boolean"
) {
oldSchemaObj!.additionalProperties =
this.updateSchema({
oldSchema: oldSchemaObj!.additionalProperties,
newSchema: newSchemaObj.additionalProperties,
level: level + 1,
}) || oldSchemaObj!.additionalProperties
}
} else { } else {
// update existing properties // update existing properties
Object.entries(oldSchemaObj!.properties!).forEach( Object.entries(oldSchemaObj!.properties!).forEach(
@@ -175,6 +175,30 @@ export default async function () {
}) })
// collect schemas // collect schemas
oas.parameters?.forEach((parameter) => {
if (oasSchemaHelper.isRefObject(parameter)) {
referencedSchemas.add(
oasSchemaHelper.normalizeSchemaName(parameter.$ref)
)
return
}
if (!parameter.schema) {
return
}
if (oasSchemaHelper.isRefObject(parameter.schema)) {
referencedSchemas.add(
oasSchemaHelper.normalizeSchemaName(parameter.schema.$ref)
)
return
}
testAndFindReferenceSchema(parameter.schema)
})
if (oas.requestBody) { if (oas.requestBody) {
if (oasSchemaHelper.isRefObject(oas.requestBody)) { if (oasSchemaHelper.isRefObject(oas.requestBody)) {
referencedSchemas.add( referencedSchemas.add(