From 6a2a105cf8864fcb0f7f66fab23f30bb829319e0 Mon Sep 17 00:00:00 2001 From: Shahed Nasser Date: Fri, 20 Sep 2024 17:22:19 +0300 Subject: [PATCH] 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 --- .../redocly/redocly-config.yaml | 3 ++ .../oas/medusa-oas-cli/src/command-docs.ts | 11 ++-- .../Parameters/Types/Object/index.tsx | 46 ++++++++++------ www/apps/api-reference/types/openapi.ts | 9 +++- .../AdminWorkflowExecutionExecution.ts | 38 ++++++++++++- .../src/classes/helpers/oas-schema.ts | 49 ++++++++++++++--- .../docs-generator/src/classes/kinds/oas.ts | 53 ++++++++++++++++--- .../docs-generator/src/commands/clean-oas.ts | 24 +++++++++ 8 files changed, 197 insertions(+), 36 deletions(-) diff --git a/packages/cli/oas/medusa-oas-cli/redocly/redocly-config.yaml b/packages/cli/oas/medusa-oas-cli/redocly/redocly-config.yaml index 1e49ac3858..38b152b8fe 100644 --- a/packages/cli/oas/medusa-oas-cli/redocly/redocly-config.yaml +++ b/packages/cli/oas/medusa-oas-cli/redocly/redocly-config.yaml @@ -16,6 +16,7 @@ decorators: - ProductCategoryResponse AdminShippingOption: - AdminShippingOption + - AdminServiceZone AdminProductCategory: - AdminProductCategory - AdminProduct @@ -41,6 +42,8 @@ decorators: AdminTaxRegion: - AdminTaxRegion - AdminTaxRate + AdminInventoryLevel: + - AdminInventoryItem theme: openapi: theme: diff --git a/packages/cli/oas/medusa-oas-cli/src/command-docs.ts b/packages/cli/oas/medusa-oas-cli/src/command-docs.ts index 4122c37ad4..b2cd29c267 100644 --- a/packages/cli/oas/medusa-oas-cli/src/command-docs.ts +++ b/packages/cli/oas/medusa-oas-cli/src/command-docs.ts @@ -238,10 +238,13 @@ ${hint} ` const redoclyConfigPath = path.join(basePath, "redocly", "redocly-config.yaml") const originalContent = await readYaml(redoclyConfigPath) as CircularReferenceConfig - originalContent.decorators["medusa/circular-patch"].schemas = Object.assign( - originalContent.decorators["medusa/circular-patch"].schemas, - recommendation - ) + Object.keys(recommendation).forEach((recKey) => { + originalContent.decorators["medusa/circular-patch"].schemas[recKey] = [ + ...originalContent.decorators["medusa/circular-patch"].schemas[recKey], + ...recommendation[recKey] + ] + }) + await writeYaml(redoclyConfigPath, jsonObjectToYamlString(originalContent)) console.log(`🟡 Added the following unhandled circular references to redocly-config.ts:` + hintMessage) } diff --git a/www/apps/api-reference/components/Tags/Operation/Parameters/Types/Object/index.tsx b/www/apps/api-reference/components/Tags/Operation/Parameters/Types/Object/index.tsx index 0d18ea80ea..3b6bd05d59 100644 --- a/www/apps/api-reference/components/Tags/Operation/Parameters/Types/Object/index.tsx +++ b/www/apps/api-reference/components/Tags/Operation/Parameters/Types/Object/index.tsx @@ -1,3 +1,5 @@ +"use client" + import type { SchemaObject } from "@/types/openapi" import TagOperationParametersDefault from "../Default" import dynamic from "next/dynamic" @@ -5,6 +7,7 @@ import type { TagOperationParametersProps } from "../.." import type { TagsOperationParametersNestedProps } from "../../Nested" import checkRequired from "@/utils/check-required" import { Loading, type DetailsProps } from "docs-ui" +import { useMemo } from "react" const TagOperationParameters = dynamic( async () => import("../.."), @@ -41,9 +44,22 @@ const TagOperationParametersObject = ({ isRequired, topLevel = false, }: 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 ( (schema.type !== "object" && schema.type !== undefined) || - (!schema.properties && !name) + (isPropertiesEmpty && isAdditionalPropertiesEmpty && !name) ) { return <> } @@ -65,22 +81,19 @@ const TagOperationParametersObject = ({ } const getPropertyParameterElms = (isNested = false) => { + const properties = isPropertiesEmpty + ? schema.additionalProperties!.properties + : schema.properties // sort properties to show required fields first - const sortedProperties = Object.keys(schema.properties).sort( + const sortedProperties = Object.keys(properties).sort( (property1, property2) => { - schema.properties[property1].isRequired = checkRequired( - schema, - property1 - ) - schema.properties[property2].isRequired = checkRequired( - schema, - property2 - ) + properties[property1].isRequired = checkRequired(schema, property1) + properties[property2].isRequired = checkRequired(schema, property2) - return schema.properties[property1].isRequired && - schema.properties[property2].isRequired + return properties[property1].isRequired && + properties[property2].isRequired ? 0 - : schema.properties[property1].isRequired + : properties[property1].isRequired ? -1 : 1 } @@ -90,13 +103,12 @@ const TagOperationParametersObject = ({ {sortedProperties.map((property, index) => ( ))} @@ -114,7 +126,7 @@ const TagOperationParametersObject = ({ ) } - if (!schema.properties || !Object.values(schema.properties).length) { + if (isPropertiesEmpty && isAdditionalPropertiesEmpty) { return getPropertyDescriptionElm() } diff --git a/www/apps/api-reference/types/openapi.ts b/www/apps/api-reference/types/openapi.ts index babfcf6801..5e206e7330 100644 --- a/www/apps/api-reference/types/openapi.ts +++ b/www/apps/api-reference/types/openapi.ts @@ -72,9 +72,15 @@ export type ArraySchemaObject = Omit< export type NonArraySchemaObject = Omit< OpenAPIV3.NonArraySchemaObject, - "properties" | "anyOf" | "allOf" | "oneOf" | "examples" + | "properties" + | "anyOf" + | "allOf" + | "oneOf" + | "examples" + | "additionalProperties" > & { properties: PropertiesObject + additionalProperties?: SchemaObject anyOf?: SchemaObject[] allOf?: SchemaObject[] oneOf?: SchemaObject[] @@ -90,6 +96,7 @@ export type SchemaObject = (ArraySchemaObject | NonArraySchemaObject) & { "x-featureFlag"?: string "x-expandable"?: string "x-schemaName"?: string + additionalProperties?: SchemaObject } export type PropertiesObject = { diff --git a/www/utils/generated/oas-output/schemas/AdminWorkflowExecutionExecution.ts b/www/utils/generated/oas-output/schemas/AdminWorkflowExecutionExecution.ts index e72e06d9aa..337a014a1d 100644 --- a/www/utils/generated/oas-output/schemas/AdminWorkflowExecutionExecution.ts +++ b/www/utils/generated/oas-output/schemas/AdminWorkflowExecutionExecution.ts @@ -1,14 +1,48 @@ /** * @schema AdminWorkflowExecutionExecution * type: object - * description: The workflow execution's execution. + * description: The workflow execution's steps details. * x-schemaName: AdminWorkflowExecutionExecution * required: * - steps * properties: * steps: * 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. * */ diff --git a/www/utils/packages/docs-generator/src/classes/helpers/oas-schema.ts b/www/utils/packages/docs-generator/src/classes/helpers/oas-schema.ts index a026018e37..3a32f7ed48 100644 --- a/www/utils/packages/docs-generator/src/classes/helpers/oas-schema.ts +++ b/www/utils/packages/docs-generator/src/classes/helpers/oas-schema.ts @@ -69,9 +69,16 @@ class OasSchemaHelper { // check if schema has child schemas // and convert those - if (schema.properties) { - Object.keys(schema.properties).forEach((property) => { - const propertySchema = schema.properties![property] + const properties = schema.properties + ? schema.properties + : 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) { return } @@ -105,7 +112,7 @@ class OasSchemaHelper { }) } - schema.properties![property] = + properties![property] = this.namedSchemaToReference( propertySchema as OpenApiSchema, level + 1 @@ -178,6 +185,28 @@ class OasSchemaHelper { this.namedSchemaToReference(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 @@ -186,10 +215,17 @@ class OasSchemaHelper { isSchemaEmpty(schema: OpenApiSchema): boolean { switch (schema.type) { case "object": - return ( + const isPropertiesEmpty = schema.properties === undefined || 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": return ( !this.isRefObject(schema.items) && this.isSchemaEmpty(schema.items) @@ -350,6 +386,7 @@ class OasSchemaHelper { | OpenApiSchema | OpenAPIV3.RequestBodyObject | OpenAPIV3.ResponseObject + | OpenAPIV3.ParameterObject | undefined ): schema is OpenAPIV3.ReferenceObject { return schema !== undefined && "$ref" in schema diff --git a/www/utils/packages/docs-generator/src/classes/kinds/oas.ts b/www/utils/packages/docs-generator/src/classes/kinds/oas.ts index d7c19dc2d0..df50612cbb 100644 --- a/www/utils/packages/docs-generator/src/classes/kinds/oas.ts +++ b/www/utils/packages/docs-generator/src/classes/kinds/oas.ts @@ -1512,10 +1512,6 @@ class OasKindGenerator extends FunctionKindGenerator { case itemType.isClassOrInterface() || itemType.isTypeParameter() || (itemType as ts.Type).flags === ts.TypeFlags.Object: - const properties: Record< - string, - OpenApiSchema | OpenAPIV3.ReferenceObject - > = {} const requiredProperties: string[] = [] const baseType = itemType.getBaseTypes()?.[0] @@ -1535,8 +1531,26 @@ class OasKindGenerator extends FunctionKindGenerator { required: undefined, } + const properties: Record< + string, + OpenApiSchema | OpenAPIV3.ReferenceObject + > = {} + let isAdditionalProperties = false + 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 ( (allowedChildren && !allowedChildren.includes(property.name)) || (disallowedChildren && disallowedChildren.includes(property.name)) @@ -1624,7 +1638,14 @@ class OasKindGenerator extends FunctionKindGenerator { } if (Object.values(properties).length) { - objSchema.properties = properties + if (isAdditionalProperties) { + objSchema.additionalProperties = { + type: "object", + properties, + } + } else { + objSchema.properties = properties + } } objSchema.required = @@ -1987,6 +2008,26 @@ class OasKindGenerator extends FunctionKindGenerator { oldSchemaObj!.properties = newSchemaObj.properties } else if (!newSchemaObj?.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 { // update existing properties Object.entries(oldSchemaObj!.properties!).forEach( diff --git a/www/utils/packages/docs-generator/src/commands/clean-oas.ts b/www/utils/packages/docs-generator/src/commands/clean-oas.ts index e5ed643b81..3e511ab44d 100644 --- a/www/utils/packages/docs-generator/src/commands/clean-oas.ts +++ b/www/utils/packages/docs-generator/src/commands/clean-oas.ts @@ -175,6 +175,30 @@ export default async function () { }) // 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 (oasSchemaHelper.isRefObject(oas.requestBody)) { referencedSchemas.add(