diff --git a/jest.config.cjs b/jest.config.cjs index 94203d9..6e04c9c 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -16,9 +16,9 @@ module.exports = { coverageThreshold: { global: { branches: 90, - functions: 89, - lines: 89, - statements: 89 + functions: 100, + lines: 96, + statements: 96 } } }; \ No newline at end of file diff --git a/src/jsonschema.ts b/src/jsonschema.ts index 91778d4..c6390c4 100644 --- a/src/jsonschema.ts +++ b/src/jsonschema.ts @@ -2,16 +2,20 @@ import { OpenAPIV3_1 } from "openapi-types"; import { debug } from "./debug.js"; -function walk(schema: OpenAPIV3_1.SchemaObject, defs: any, history: any): void { +function walk(schema: OpenAPIV3_1.SchemaObject, defs: any, history: any): boolean { if (schema.type === 'object') { if (schema.properties) { for (const key in schema.properties) { const property = schema.properties[key]; - walk(property, defs, history); + if (walk(property, defs, history)) { + delete((schema.properties as any)[key]); + } } } - } else if ((schema as any)["$ref"]) { + return false; + } + if ((schema as any)["$ref"]) { const canonicalRef = (schema as any)['$ref']; const paths = canonicalRef.split('/'); @@ -20,17 +24,17 @@ function walk(schema: OpenAPIV3_1.SchemaObject, defs: any, history: any): void { if (history.includes(ref)) { debug("Circular reference detected for " + ref + " in history: " + history); delete((schema as any)["$ref"]); - } else { - const def = defs[ref]; - for (const k in def) { - (schema as any)[k] = def[k]; - } - - delete((schema as any)["$ref"]); - - walk(schema, defs, [...history, ref]); - } + return true; + } + const def = defs[ref]; + for (const k in def) { + (schema as any)[k] = def[k]; + } + delete((schema as any)["$ref"]); + walk(schema, defs, [...history, ref]); + return false } + return false } export function expandJSONSchemaDefinition(schema: any, defs: any): any { diff --git a/tests/jsonschema.test.ts b/tests/jsonschema.test.ts index ab3563a..724287d 100644 --- a/tests/jsonschema.test.ts +++ b/tests/jsonschema.test.ts @@ -77,3 +77,85 @@ test('expandJSONSchemaDefinition $ref using definitions for properties', () => { expect(expandJSONSchemaDefinition(schema, defs)).toEqual(schemaRef); }); +test('expandJSONSchemaDefinition removes $ref definition from attribute', () => { + const schema = { + type: 'object', + properties: { + borrower: { + type: 'object', + properties: { + name: { type: 'string' }, + spouse: { $ref: "#/components/schemas/toto" } + } + } + } + }; + const expected = { + type: 'object', + properties: { + borrower: { + type: 'object', + properties: { + name: { type: 'string' }, + spouse: { } + } + } + } + }; + const result = expandJSONSchemaDefinition(schema, {}); + expect(result).toEqual(expected); + + // Explicitly verify that borrower.spouse is an empty object + const spouse = result.properties.borrower.properties.spouse; + expect(spouse).toEqual({}); + expect(Object.keys(spouse).length).toBe(0); +}); + +test('expandJSONSchemaDefinition removes attributes with circular $ref definition', () => { + const defs = { + borrower: { + type: 'object', + properties: { + name: { type: 'string' }, + spouse: { $ref: "#/components/schemas/borrower" } + } + } + }; + + const schema = { + type: 'object', + properties: { + input: { + type: "object", + properties: { + borrower: { + "$ref": "#/components/schemas/borrower" + } + } + }, + } + }; + + const expected = { + type: 'object', + properties: { + input: { + type: "object", + properties: { + borrower: { + type: 'object', + properties: { + name: { type: 'string' }, + } + } + } + } + } + }; + const result = expandJSONSchemaDefinition(schema, defs); + expect(result).toEqual(expected); + + // Explicitly verify that borrower.spouse is undefined + const spouse = result.properties.input.properties.borrower.properties.spouse; + expect(spouse).toBeUndefined(); +});