Skip to content

Commit

Permalink
fix: generated $ref schemas when $refed with different descriptio…
Browse files Browse the repository at this point in the history
…ns (#252)

* fix: handle schemas with different descriptions

* refactor: define 2 different oas and json schema parsers

* refactor: minor commment improvements
  • Loading branch information
toomuchdesign authored May 15, 2024
1 parent 8cc1522 commit 6d55193
Show file tree
Hide file tree
Showing 9 changed files with 52 additions and 36 deletions.
5 changes: 5 additions & 0 deletions .changeset/itchy-seas-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"openapi-ts-json-schema": patch
---

Fix generated `$ref` schemas when `$ref`ed with different descriptions
59 changes: 33 additions & 26 deletions src/openapiToTsJsonSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, JSONSchema> = new Map();
const dereferencedJsonSchema = await schemaParser.dereference(
// Inline and collect internal $ref definitions
const dereferencedJsonSchema = await jsonSchemaParser.dereference(
initialJsonSchema,
{
dereference: {
Expand All @@ -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}"`,
},
];
}
},
},
Expand Down
8 changes: 4 additions & 4 deletions src/utils/convertOpenApiToJsonSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ function convertToJsonSchema<Value extends unknown>(
}

/**
* 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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Node extends unknown>(
Expand All @@ -21,7 +21,7 @@ function replaceInlinedSchemaWithPlaceholder<Node extends unknown>(
/**
* 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/utils/makeTsJsonSchema/replacePlaceholdersWithRefs.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
2 changes: 2 additions & 0 deletions test/$idMapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
},
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/ref-property/specs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions test/refHandling-inline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
},
Expand Down

0 comments on commit 6d55193

Please sign in to comment.