diff --git a/.changeset/itchy-seas-own.md b/.changeset/itchy-seas-own.md new file mode 100644 index 0000000..645dab0 --- /dev/null +++ b/.changeset/itchy-seas-own.md @@ -0,0 +1,5 @@ +--- +"openapi-ts-json-schema": patch +--- + +Fix generated `$ref` schemas when `$ref`ed with different descriptions diff --git a/src/openapiToTsJsonSchema.ts b/src/openapiToTsJsonSchema.ts index 4fe3551..6033e38 100644 --- a/src/openapiToTsJsonSchema.ts +++ b/src/openapiToTsJsonSchema.ts @@ -73,12 +73,17 @@ export async function openapiToTsJsonSchema( await clearFolder(outputPath); - const schemaParser = new $RefParser(); - const bundledOpenApiSchema = await schemaParser.bundle(openApiSchemaPath); + const openApiParser = new $RefParser(); + const jsonSchemaParser = new $RefParser(); + + // Resolve and inline external $ref definitions + const bundledOpenApiSchema = await openApiParser.bundle(openApiSchemaPath); + // Convert oas definitions to JSON schema const initialJsonSchema = convertOpenApiToJsonSchema(bundledOpenApiSchema); const inlinedRefs: Map = new Map(); - const dereferencedJsonSchema = await schemaParser.dereference( + // Inline and collect internal $ref definitions + const dereferencedJsonSchema = await jsonSchemaParser.dereference( initialJsonSchema, { dereference: { @@ -88,29 +93,31 @@ export async function openapiToTsJsonSchema( // Keep track of inlined refs if (!inlinedRefs.has(id)) { - // Make a shallow copy of the ref schema to save it from the mutations below - inlinedRefs.set(id, { ...inlinedSchema }); - - /** - * "import" refHandling support: - * mark inlined ref objects with a "SCHEMA_ID_SYMBOL" to retrieve their - * original $ref value once inlined - */ - inlinedSchema[SCHEMA_ID_SYMBOL] = id; - - /** - * "inline" refHandling support: - * add a $ref comment to each inlined schema with the original ref value. - * See: https://github.com/kaelzhang/node-comment-json - */ - if (refHandling === 'inline') { - inlinedSchema[Symbol.for('before')] = [ - { - type: 'LineComment', - value: ` $ref: "${ref}"`, - }, - ]; - } + // Shallow copy the ref schema to avoid the mutations below + inlinedRefs.set(id, { + // @ts-expect-error Spread types may only be created from object types + ...jsonSchemaParser.$refs.get(ref), + }); + } + + /** + * mark inlined ref objects with a "SCHEMA_ID_SYMBOL" + * to retrieve their id once inlined + */ + inlinedSchema[SCHEMA_ID_SYMBOL] = id; + + /** + * "inline" refHandling support: + * add a $ref comment to each inlined schema with the original ref value. + * See: https://github.com/kaelzhang/node-comment-json + */ + if (refHandling === 'inline') { + inlinedSchema[Symbol.for('before')] = [ + { + type: 'LineComment', + value: ` $ref: "${ref}"`, + }, + ]; } }, }, diff --git a/src/utils/convertOpenApiToJsonSchema.ts b/src/utils/convertOpenApiToJsonSchema.ts index b5b0c0c..c99fe24 100644 --- a/src/utils/convertOpenApiToJsonSchema.ts +++ b/src/utils/convertOpenApiToJsonSchema.ts @@ -36,11 +36,11 @@ function convertToJsonSchema( } /** - * Traverse the openAPI schema tree an brutally try to convert everything - * possible to JSON schema. We are probably overdoing since we process any object we find. + * Traverse the openAPI schema tree an brutally try to convert every oas definition + * to JSON schema. We are probably overdoing since we process any found object. * - * - Is there a way to tell an OpenAPI schema objects convertible to JSON schema from the others? - * - Could we explicitly convert only the properties where we know conversion is needed? + * - Is there a way to tell an OpenAPI definition objects convertible to JSON schema from the others? + * - Could we explicitly convert only the properties that need it? * * @TODO Find a nicer way to convert convert all the expected OpenAPI schemas */ diff --git a/src/utils/makeTsJsonSchema/replaceInlinedRefsWithStringPlaceholder.ts b/src/utils/makeTsJsonSchema/replaceInlinedRefsWithStringPlaceholder.ts index 935a575..b9f3390 100644 --- a/src/utils/makeTsJsonSchema/replaceInlinedRefsWithStringPlaceholder.ts +++ b/src/utils/makeTsJsonSchema/replaceInlinedRefsWithStringPlaceholder.ts @@ -5,7 +5,7 @@ import type { JSONSchema, JSONSchemaWithPlaceholders } from '../../types'; /** * Get any JSON schema node and: - * - Return ref placeholder is the entity is an inlined ref schema objects (with SCHEMA_ID_SYMBOL prop) + * - Return id placeholder if the entity is an inlined ref schema objects (with SCHEMA_ID_SYMBOL prop) * - Return provided node in all other cases */ function replaceInlinedSchemaWithPlaceholder( @@ -21,7 +21,7 @@ function replaceInlinedSchemaWithPlaceholder( /** * Iterate a JSON schema to replace inlined ref schema objects * (marked with a SCHEMA_ID_SYMBOL property holding the original $ref value) - * with a string placeholder with a reference to the original $ref value ("_OTJS-START_/id/value_OTJS-END_") + * with a string placeholder with a reference to their internal id ("_OTJS-START_/id/value_OTJS-END_") */ export function replaceInlinedRefsWithStringPlaceholder( schema: JSONSchema, diff --git a/src/utils/makeTsJsonSchema/replacePlaceholdersWithImportedSchemas.ts b/src/utils/makeTsJsonSchema/replacePlaceholdersWithImportedSchemas.ts index 459c174..0153159 100644 --- a/src/utils/makeTsJsonSchema/replacePlaceholdersWithImportedSchemas.ts +++ b/src/utils/makeTsJsonSchema/replacePlaceholdersWithImportedSchemas.ts @@ -2,7 +2,7 @@ import { makeRelativeModulePath, PLACEHOLDER_REGEX } from '..'; import type { SchemaMetaDataMap } from '../../types'; /** - * Replace Refs placeholders with imported schemas + * Replace id placeholders with imported schemas */ export function replacePlaceholdersWithImportedSchemas({ schemaAsText, diff --git a/src/utils/makeTsJsonSchema/replacePlaceholdersWithRefs.ts b/src/utils/makeTsJsonSchema/replacePlaceholdersWithRefs.ts index 65d3923..7fae360 100644 --- a/src/utils/makeTsJsonSchema/replacePlaceholdersWithRefs.ts +++ b/src/utils/makeTsJsonSchema/replacePlaceholdersWithRefs.ts @@ -1,7 +1,7 @@ import { PLACEHOLDER_REGEX } from '..'; /** - * Replace Refs placeholders with original ref objects + * Replace id placeholders with their relevant $ref object */ export function replacePlaceholdersWithRefs({ schemaAsText, diff --git a/test/$idMapper.test.ts b/test/$idMapper.test.ts index cc3aa11..d946cff 100644 --- a/test/$idMapper.test.ts +++ b/test/$idMapper.test.ts @@ -26,10 +26,12 @@ describe('$idMapper option', () => { description: 'January description', properties: { isJanuary: { + description: 'isJanuary description', enum: ['yes', 'no', null], type: ['string', 'null'], }, isFebruary: { + description: 'isFebruary description', enum: ['yes', 'no', null], type: ['string', 'null'], }, diff --git a/test/fixtures/ref-property/specs.yaml b/test/fixtures/ref-property/specs.yaml index edabc7b..cf0487a 100644 --- a/test/fixtures/ref-property/specs.yaml +++ b/test/fixtures/ref-property/specs.yaml @@ -12,10 +12,10 @@ components: - isJanuary properties: isJanuary: - # description: isJanuary description + description: isJanuary description $ref: '#/components/schemas/Answer' isFebruary: - # description: isFebruary description + description: isFebruary description $ref: '#/components/schemas/Answer' Answer: type: string diff --git a/test/refHandling-inline.test.ts b/test/refHandling-inline.test.ts index 359c326..66a468f 100644 --- a/test/refHandling-inline.test.ts +++ b/test/refHandling-inline.test.ts @@ -25,11 +25,13 @@ describe('refHandling option === "inline"', () => { properties: { isJanuary: { // $ref: "#/components/schemas/Answer" + description: "isJanuary description", type: ["string", "null"], enum: ["yes", "no", null], }, isFebruary: { // $ref: "#/components/schemas/Answer" + description: "isFebruary description", type: ["string", "null"], enum: ["yes", "no", null], },