From 2d867061d2ebaad926187aa3c94c99dd9f0b9a62 Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Thu, 21 Sep 2023 16:17:38 +0100 Subject: [PATCH 01/14] use cloneDeep library --- package-lock.json | 11 +++++++++++ package.json | 1 + 2 files changed, 12 insertions(+) diff --git a/package-lock.json b/package-lock.json index 0d93fd3..501f564 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "Apache-2.0", "dependencies": { "json-schema-traverse": "^1.0.0", + "lodash.clonedeep": "^4.5.0", "uuid": "^9.0.0" }, "devDependencies": { @@ -707,6 +708,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -1953,6 +1959,11 @@ "p-locate": "^5.0.0" } }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", diff --git a/package.json b/package.json index 103928f..12c5c86 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ }, "dependencies": { "json-schema-traverse": "^1.0.0", + "lodash.clonedeep": "^4.5.0", "uuid": "^9.0.0" } } From 1ccb24b6bd938961756a575bb6d1ba7f1d09f2a6 Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Thu, 21 Sep 2023 16:17:51 +0100 Subject: [PATCH 02/14] create a circular schema --- test/schemas/circular/circular.json | 96 +++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 test/schemas/circular/circular.json diff --git a/test/schemas/circular/circular.json b/test/schemas/circular/circular.json new file mode 100644 index 0000000..e2cbac4 --- /dev/null +++ b/test/schemas/circular/circular.json @@ -0,0 +1,96 @@ +{ + "title": "UserResponse", + "description": "Response object for user", + "type": "object", + "properties": { + "user": { + "$ref": "#/definitions/default_1", + "description": "user details" + } + }, + "definitions": { + "default_1": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "basicInformation": { + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + } + } + }, + "classes": { + "type": "array", + "items": { + "$ref": "#/definitions/default_2" + } + } + } + }, + "default_2": { + "type": "object", + "properties": { + "className": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/default_3" + } + }, + "id": { + "type": "number" + }, + "parentId": { + "type": "number" + }, + "subRows": { + "type": "array", + "items": { + "$ref": "#/definitions/default_2" + } + } + } + }, + "default_3": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "detailDesc": { + "type": "string" + }, + "detailType": { + "$ref": "#/definitions/default_4" + } + } + }, + "default_4": { + "type": "object", + "properties": { + "detailTypeName": { + "type": "string" + }, + "detailCode": { + "type": "string" + }, + "id": { + "type": "number" + } + } + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} From d1974c06f368c64bc6f4c753e6a5a94007985850 Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Thu, 21 Sep 2023 16:18:10 +0100 Subject: [PATCH 03/14] formats and adds a circular schema test --- test/src/Convertor.spec.js | 1557 ++++++++++++++++++++---------------- 1 file changed, 858 insertions(+), 699 deletions(-) diff --git a/test/src/Convertor.spec.js b/test/src/Convertor.spec.js index 58d61e0..d487a06 100644 --- a/test/src/Convertor.spec.js +++ b/test/src/Convertor.spec.js @@ -1,750 +1,909 @@ -'use strict' +"use strict"; -const expect = require('chai').expect -const validator = require('oas-validator') +const expect = require("chai").expect; +const validator = require("oas-validator"); // for external schema validation tests -const fetch = require('node-fetch') -const $RefParser = require("@apidevtools/json-schema-ref-parser") +const fetch = require("node-fetch"); +const $RefParser = require("@apidevtools/json-schema-ref-parser"); -const Convertor = require('../../src/Convertor') +const Convertor = require("../../src/Convertor"); // JSON Schemas // basic -const basic = require('../schemas/basic.json') +const basic = require("../schemas/basic.json"); // invalid Fields -const invalidFieldOne = require('../schemas/invalidFields/invalidField.json') +const invalidFieldOne = require("../schemas/invalidFields/invalidField.json"); // null property type -const nullProperty = require('../schemas/nullProperties/nullProperty.json') +const nullProperty = require("../schemas/nullProperties/nullProperty.json"); // array types -const arrayType = require('../schemas/arrayTypes/arrayType.json') -const arrayTypeWithNull = require('../schemas/arrayTypes/arrayTypeIncludingNull.json') -const arrayTypeWithArrayTypes = require('../schemas/arrayTypes/arrayTypeWithArray.json') -const arrayTypeWithObjectTypes = require('../schemas/arrayTypes/arrayTypeWithObject.json') -const arrayTypeWithDefaults = require('../schemas/arrayTypes/arrayTypeWithDefault.json') +const arrayType = require("../schemas/arrayTypes/arrayType.json"); +const arrayTypeWithNull = require("../schemas/arrayTypes/arrayTypeIncludingNull.json"); +const arrayTypeWithArrayTypes = require("../schemas/arrayTypes/arrayTypeWithArray.json"); +const arrayTypeWithObjectTypes = require("../schemas/arrayTypes/arrayTypeWithObject.json"); +const arrayTypeWithDefaults = require("../schemas/arrayTypes/arrayTypeWithDefault.json"); // defaults -const defaultArray = require('../schemas/defaultValues/defaultArray.json') -const defaultBoolean = require('../schemas/defaultValues/defaultBoolean.json') -const defaultNumbers = require('../schemas/defaultValues/defaultNumbers.json') -const defaultString = require('../schemas/defaultValues/defaultString.json') +const defaultArray = require("../schemas/defaultValues/defaultArray.json"); +const defaultBoolean = require("../schemas/defaultValues/defaultBoolean.json"); +const defaultNumbers = require("../schemas/defaultValues/defaultNumbers.json"); +const defaultString = require("../schemas/defaultValues/defaultString.json"); // array items -const basicArray = require('../schemas/arrayItems/basicArray.json') -const multiArray = require('../schemas/arrayItems/multipleItemArray.json') +const basicArray = require("../schemas/arrayItems/basicArray.json"); +const multiArray = require("../schemas/arrayItems/multipleItemArray.json"); // const values -const basicConst = require('../schemas/const/basicConst.json') +const basicConst = require("../schemas/const/basicConst.json"); // if/then/else schemas -const ifThenElse = require('../schemas/ifelse/ifthenelse.json') -const ifThen = require('../schemas/ifelse/ifthen.json') -const ifElse = require('../schemas/ifelse/ifelse.json') -const ifSchema = require('../schemas/ifelse/if.json') -const elseSchema = require('../schemas/ifelse/else.json') -const thenSchema = require('../schemas/ifelse/then.json') -const thenElseSchema = require('../schemas/ifelse/thenelse.json') +const ifThenElse = require("../schemas/ifelse/ifthenelse.json"); +const ifThen = require("../schemas/ifelse/ifthen.json"); +const ifElse = require("../schemas/ifelse/ifelse.json"); +const ifSchema = require("../schemas/ifelse/if.json"); +const elseSchema = require("../schemas/ifelse/else.json"); +const thenSchema = require("../schemas/ifelse/then.json"); +const thenElseSchema = require("../schemas/ifelse/thenelse.json"); // dependencies schemas -const dependenciesArray = require('../schemas/dependencies/dependenciesArray.json') -const dependenciesSchema = require('../schemas/dependencies/dependenciesSchema.json') -const dependentRequired = require('../schemas/dependencies/dependentRequired.json') -const dependentSchemas = require('../schemas/dependencies/dependentSchemas.json') +const dependenciesArray = require("../schemas/dependencies/dependenciesArray.json"); +const dependenciesSchema = require("../schemas/dependencies/dependenciesSchema.json"); +const dependentRequired = require("../schemas/dependencies/dependentRequired.json"); +const dependentSchemas = require("../schemas/dependencies/dependentSchemas.json"); // camelcased keys -const camelCased = require('../schemas/camelCasedKey/camelCasedKey.json') +const camelCased = require("../schemas/camelCasedKey/camelCasedKey.json"); // array Keys -const arrayKeyOneOf = require('../schemas/arrayKeys/arrayKeyOneOf.json') +const arrayKeyOneOf = require("../schemas/arrayKeys/arrayKeyOneOf.json"); // External Schemas That I Cannot Currently Convert -const listOfBannedSchemas = require('../schemas/SchemasThatCannotBeConverted/list.json') +const listOfBannedSchemas = require("../schemas/SchemasThatCannotBeConverted/list.json"); // anyOf/oneOf Nulls -const oneOfNull = require('../schemas/ofNulls/oneOfNull.json') -const anyOfNull = require('../schemas/ofNulls/anyOfNull.json') +const oneOfNull = require("../schemas/ofNulls/oneOfNull.json"); +const anyOfNull = require("../schemas/ofNulls/anyOfNull.json"); // anyOf/oneOf Nulls -const allOfProperties = require('../schemas/propertiesOutsideOf/allOf.json') -const oneOfProperties = require('../schemas/propertiesOutsideOf/oneOf.json') -const anyOfProperties = require('../schemas/propertiesOutsideOf/anyOf.json') - +const allOfProperties = require("../schemas/propertiesOutsideOf/allOf.json"); +const oneOfProperties = require("../schemas/propertiesOutsideOf/oneOf.json"); +const anyOfProperties = require("../schemas/propertiesOutsideOf/anyOf.json"); +// circular +const circular = require("../schemas/circular/circular.json"); // OpenAPI -const basicOpenAPI = require('../openAPI/basic.json') +const basicOpenAPI = require("../openAPI/basic.json"); + +describe("Convertor", () => { + let convertor; -describe('Convertor', () => { - let convertor + beforeEach(function () { + convertor = new Convertor(basic); + }); - beforeEach(function() { - convertor = new Convertor(basic) + describe("Class", () => { + it("should be a class", function () { + expect(Convertor).to.be.a("function"); }); - describe('Class', () => { - it('should be a class', function() { - expect(Convertor).to.be.a('function') - }); + it("should have a convert function", function () { + expect(convertor).to.have.property("convert"); + }); - it('should have a convert function', function() { - expect(convertor).to.have.property('convert') - }); + it("should have a property of this.schema when instantiated", function () { + expect(Convertor).to.not.have.a.property("schema"); + expect(convertor).to.have.a.property("schema"); + }); - it('should have a property of this.schema when instantiated', function() { - expect(Convertor).to.not.have.a.property('schema') - expect(convertor).to.have.a.property('schema') - }); + it("should have property this.schema equal to the argument instantiated with", function () { + let schema = { + a: 123, + }; - it('should have property this.schema equal to the argument instantiated with', function() { - let schema = { - a: 123 - } + const convertor2 = new Convertor(schema); + expect(convertor2.schema).to.be.deep.equal(schema); + }); - const convertor2 = new Convertor(schema) - expect(convertor2.schema).to.be.deep.equal(schema) - }); + it("should have a property of this.specialSchemaFields", function () { + expect(Convertor).to.not.have.a.property("specialSchemaFields"); + expect(convertor).to.have.a.property("specialSchemaFields"); + expect(convertor.specialSchemaFields).to.be.an("Array"); + expect(convertor.specialSchemaFields.length).to.be.equal(11); + }); - it('should have a property of this.specialSchemaFields', function() { - expect(Convertor).to.not.have.a.property('specialSchemaFields') - expect(convertor).to.have.a.property('specialSchemaFields') - expect(convertor.specialSchemaFields).to.be.an('Array') - expect(convertor.specialSchemaFields.length).to.be.equal(11) - }); + it("should have a property of this.validSchemaFields", function () { + expect(Convertor).to.not.have.a.property("validSchemaFields"); + expect(convertor).to.have.a.property("validSchemaFields"); + expect(convertor.validSchemaFields).to.be.an("Array"); + expect(convertor.validSchemaFields.length).to.be.equal(36); + }); - it('should have a property of this.validSchemaFields', function() { - expect(Convertor).to.not.have.a.property('validSchemaFields') - expect(convertor).to.have.a.property('validSchemaFields') - expect(convertor.validSchemaFields).to.be.an('Array') - expect(convertor.validSchemaFields.length).to.be.equal(36) - }); + it("should have a property of this.components", function () { + expect(Convertor).to.not.have.a.property("components"); + expect(convertor).to.have.a.property("components"); + expect(convertor.components).to.be.an("Object"); + }); + }); - it('should have a property of this.components', function() { - expect(Convertor).to.not.have.a.property('components') - expect(convertor).to.have.a.property('components') - expect(convertor.components).to.be.an('Object') - }); + describe("convert", () => { + it("should return this.components as an object", function () { + const result = convertor.convert("basic"); + expect(result).to.deep.equal(convertor.components); + expect(result).to.be.an("Object"); }); - describe('convert', () => { - it('should return this.components as an object', function() { - const result = convertor.convert('basic') - expect(result).to.deep.equal(convertor.components) - expect(result).to.be.an('Object') - }); + describe("invalid Keys", () => { + it("should remove keys that do not feature in the validFields array", async function () { + const result = convertor.convert("basic"); + expect(result.schemas.basic).to.not.have.property("$schema"); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should remove deeply nested keys that do not feature in the validFields array", async function () { + expect(invalidFieldOne.properties.errors).to.have.property("$schema"); + const newConvertor = new Convertor(invalidFieldOne); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic).to.not.have.property("$schema"); + expect(result.schemas.basic.properties.errors).to.not.have.property( + "$schema" + ); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + }); - describe('invalid Keys', () => { - it('should remove keys that do not feature in the validFields array', async function() { - const result = convertor.convert('basic') - expect(result.schemas.basic).to.not.have.property('$schema') - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should remove deeply nested keys that do not feature in the validFields array', async function() { - expect(invalidFieldOne.properties.errors).to.have.property('$schema') - const newConvertor = new Convertor(invalidFieldOne) - const result = newConvertor.convert('basic') - expect(result.schemas.basic).to.not.have.property('$schema') - expect(result.schemas.basic.properties.errors).to.not.have.property('$schema') - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - }); + describe("properties as null", () => { + it("should convert properties as null to nullable", async function () { + const newConvertor = new Convertor(nullProperty); + const result = newConvertor.convert("basic"); + expect( + result.schemas.basic.properties.nullProperty + ).to.not.have.property("type"); + expect(result.schemas.basic.properties.nullProperty).to.have.property( + "nullable" + ); + expect( + result.schemas.basic.properties.nullProperty.nullable + ).to.be.equal(true); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + }); - describe('properties as null', () => { - it('should convert properties as null to nullable', async function() { - const newConvertor = new Convertor(nullProperty) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.nullProperty).to.not.have.property('type') - expect(result.schemas.basic.properties.nullProperty).to.have.property('nullable') - expect(result.schemas.basic.properties.nullProperty.nullable).to.be.equal(true) - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - }); + describe("arrays of types", () => { + it("should convert properties that have an array of types to a oneOf", async function () { + const newConvertor = new Convertor(arrayType); + const result = newConvertor.convert("basic"); + expect( + result.schemas.basic.properties.arrayTypeProperty + ).to.not.have.property("type"); + expect( + result.schemas.basic.properties.arrayTypeProperty + ).to.have.property("oneOf"); + expect( + result.schemas.basic.properties.arrayTypeProperty.oneOf + ).to.be.an("array"); + expect( + result.schemas.basic.properties.arrayTypeProperty.oneOf.length + ).to.be.equal(2); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should convert properties that have an array of types to a oneOf with null fields", async function () { + const newConvertor = new Convertor(arrayTypeWithNull); + const result = newConvertor.convert("basic"); + expect( + result.schemas.basic.properties.arrayTypeProperty + ).to.not.have.property("type"); + expect( + result.schemas.basic.properties.arrayTypeProperty + ).to.have.property("oneOf"); + expect( + result.schemas.basic.properties.arrayTypeProperty.oneOf + ).to.be.an("array"); + expect( + result.schemas.basic.properties.arrayTypeProperty.oneOf.length + ).to.be.equal(1); + expect( + result.schemas.basic.properties.arrayTypeProperty.oneOf[0].type + ).to.be.equal("string"); + expect( + result.schemas.basic.properties.arrayTypeProperty.oneOf[0].nullable + ).to.be.equal(true); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should convert properties that have an array of types to a oneOf with array types", async function () { + const newConvertor = new Convertor(arrayTypeWithArrayTypes); + const result = newConvertor.convert("basic"); + expect( + result.schemas.basic.properties.arrayTypeProperty + ).to.not.have.property("type"); + expect( + result.schemas.basic.properties.arrayTypeProperty + ).to.have.property("oneOf"); + expect( + result.schemas.basic.properties.arrayTypeProperty.oneOf + ).to.be.an("array"); + expect( + result.schemas.basic.properties.arrayTypeProperty.oneOf.length + ).to.be.equal(2); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should convert properties that have an array of types to a oneOf with object types", async function () { + const newConvertor = new Convertor(arrayTypeWithObjectTypes); + const result = newConvertor.convert("basic"); + expect( + result.schemas.basic.properties.arrayTypeProperty + ).to.not.have.property("type"); + expect( + result.schemas.basic.properties.arrayTypeProperty + ).to.have.property("oneOf"); + expect( + result.schemas.basic.properties.arrayTypeProperty.oneOf + ).to.be.an("array"); + expect( + result.schemas.basic.properties.arrayTypeProperty.oneOf.length + ).to.be.equal(2); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should convert properties that have an array of types to a oneOf with defaults", async function () { + const newConvertor = new Convertor(arrayTypeWithDefaults); + const result = newConvertor.convert("basic"); + expect( + result.schemas.basic.properties.arrayTypeProperty + ).to.not.have.property("type"); + expect( + result.schemas.basic.properties.arrayTypeProperty + ).to.have.property("oneOf"); + expect( + result.schemas.basic.properties.arrayTypeProperty.oneOf.length + ).to.be.equal(2); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + }); - describe('arrays of types', () => { - it('should convert properties that have an array of types to a oneOf', async function() { - const newConvertor = new Convertor(arrayType) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.arrayTypeProperty).to.not.have.property('type') - expect(result.schemas.basic.properties.arrayTypeProperty).to.have.property('oneOf') - expect(result.schemas.basic.properties.arrayTypeProperty.oneOf).to.be.an('array') - expect(result.schemas.basic.properties.arrayTypeProperty.oneOf.length).to.be.equal(2) - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should convert properties that have an array of types to a oneOf with null fields', async function() { - const newConvertor = new Convertor(arrayTypeWithNull) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.arrayTypeProperty).to.not.have.property('type') - expect(result.schemas.basic.properties.arrayTypeProperty).to.have.property('oneOf') - expect(result.schemas.basic.properties.arrayTypeProperty.oneOf).to.be.an('array') - expect(result.schemas.basic.properties.arrayTypeProperty.oneOf.length).to.be.equal(1) - expect(result.schemas.basic.properties.arrayTypeProperty.oneOf[0].type).to.be.equal('string') - expect(result.schemas.basic.properties.arrayTypeProperty.oneOf[0].nullable).to.be.equal(true) - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should convert properties that have an array of types to a oneOf with array types', async function() { - const newConvertor = new Convertor(arrayTypeWithArrayTypes) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.arrayTypeProperty).to.not.have.property('type') - expect(result.schemas.basic.properties.arrayTypeProperty).to.have.property('oneOf') - expect(result.schemas.basic.properties.arrayTypeProperty.oneOf).to.be.an('array') - expect(result.schemas.basic.properties.arrayTypeProperty.oneOf.length).to.be.equal(2) - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should convert properties that have an array of types to a oneOf with object types', async function() { - const newConvertor = new Convertor(arrayTypeWithObjectTypes) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.arrayTypeProperty).to.not.have.property('type') - expect(result.schemas.basic.properties.arrayTypeProperty).to.have.property('oneOf') - expect(result.schemas.basic.properties.arrayTypeProperty.oneOf).to.be.an('array') - expect(result.schemas.basic.properties.arrayTypeProperty.oneOf.length).to.be.equal(2) - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should convert properties that have an array of types to a oneOf with defaults', async function() { - const newConvertor = new Convertor(arrayTypeWithDefaults) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.arrayTypeProperty).to.not.have.property('type') - expect(result.schemas.basic.properties.arrayTypeProperty).to.have.property('oneOf') - expect(result.schemas.basic.properties.arrayTypeProperty.oneOf.length).to.be.equal(2) - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); + describe("default values", () => { + it("should convert default Array values correctly", async function () { + const newConvertor = new Convertor(defaultArray); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties.errors).to.have.property( + "default" + ); + expect(result.schemas.basic.properties.errors.default).to.be.an( + "array" + ); + expect( + result.schemas.basic.properties.errors.default.length + ).to.be.equal(1); + expect(result.schemas.basic.properties.errors.default[0]).to.be.equal( + 1 + ); + expect(result.schemas.basic.properties.other).to.have.property("items"); + expect(result.schemas.basic.properties.other.items).to.be.deep.equal({ + nullable: true, }); - describe('default values', () => { - it('should convert default Array values correctly', async function() { - const newConvertor = new Convertor(defaultArray) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.errors).to.have.property('default') - expect(result.schemas.basic.properties.errors.default).to.be.an('array') - expect(result.schemas.basic.properties.errors.default.length).to.be.equal(1) - expect(result.schemas.basic.properties.errors.default[0]).to.be.equal(1) - expect(result.schemas.basic.properties.other).to.have.property('items') - expect(result.schemas.basic.properties.other.items).to.be.deep.equal({nullable: true}) - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should convert default Boolean values correctly', async function() { - const newConvertor = new Convertor(defaultBoolean) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.errors).to.have.property('default') - expect(result.schemas.basic.properties.errors.default).to.be.equal(true) - expect(result.schemas.basic.properties.other).to.have.property('default') - expect(result.schemas.basic.properties.other.default).to.be.equal(false) - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should convert default Number and Integer values correctly', async function() { - const newConvertor = new Convertor(defaultNumbers) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.errors).to.have.property('default') - expect(result.schemas.basic.properties.errors.default).to.be.equal(1) - expect(result.schemas.basic.properties.other).to.have.property('default') - expect(result.schemas.basic.properties.other.default).to.be.equal(2) - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should convert default String values correctly', async function() { - const newConvertor = new Convertor(defaultString) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.errors).to.have.property('default') - expect(result.schemas.basic.properties.errors.default).to.be.equal('1') - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - }); + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should convert default Boolean values correctly", async function () { + const newConvertor = new Convertor(defaultBoolean); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties.errors).to.have.property( + "default" + ); + expect(result.schemas.basic.properties.errors.default).to.be.equal( + true + ); + expect(result.schemas.basic.properties.other).to.have.property( + "default" + ); + expect(result.schemas.basic.properties.other.default).to.be.equal( + false + ); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should convert default Number and Integer values correctly", async function () { + const newConvertor = new Convertor(defaultNumbers); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties.errors).to.have.property( + "default" + ); + expect(result.schemas.basic.properties.errors.default).to.be.equal(1); + expect(result.schemas.basic.properties.other).to.have.property( + "default" + ); + expect(result.schemas.basic.properties.other.default).to.be.equal(2); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should convert default String values correctly", async function () { + const newConvertor = new Convertor(defaultString); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties.errors).to.have.property( + "default" + ); + expect(result.schemas.basic.properties.errors.default).to.be.equal("1"); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + }); - describe('arrays to objects', () => { - it('should convert array items to an object', async function() { - const newConvertor = new Convertor(basicArray) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.names).to.have.property('type') - expect(result.schemas.basic.properties.names.type).to.be.equal('array') - expect(result.schemas.basic.properties.names.items).to.be.an('object') - expect(result.schemas.basic.properties.names.items).to.not.be.an('array') - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should only use the first item in the array and discard the others', async function() { - const newConvertor = new Convertor(multiArray) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.names).to.have.property('type') - expect(result.schemas.basic.properties.names.type).to.be.equal('array') - expect(result.schemas.basic.properties.names.items).to.be.an('object') - expect(result.schemas.basic.properties.names.items).to.not.be.an('array') - expect(result.schemas.basic.properties.names.items.type).to.equal('object') - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - }); + describe("arrays to objects", () => { + it("should convert array items to an object", async function () { + const newConvertor = new Convertor(basicArray); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties.names).to.have.property("type"); + expect(result.schemas.basic.properties.names.type).to.be.equal("array"); + expect(result.schemas.basic.properties.names.items).to.be.an("object"); + expect(result.schemas.basic.properties.names.items).to.not.be.an( + "array" + ); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should only use the first item in the array and discard the others", async function () { + const newConvertor = new Convertor(multiArray); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties.names).to.have.property("type"); + expect(result.schemas.basic.properties.names.type).to.be.equal("array"); + expect(result.schemas.basic.properties.names.items).to.be.an("object"); + expect(result.schemas.basic.properties.names.items).to.not.be.an( + "array" + ); + expect(result.schemas.basic.properties.names.items.type).to.equal( + "object" + ); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + }); - describe('const values', () => { - it('should convert const values to enum', async function() { - const newConvertor = new Convertor(basicConst) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.name).to.not.have.property('const') - expect(result.schemas.basic.properties.name).to.have.property('enum') - expect(result.schemas.basic.properties.name.enum.length).to.be.equal(1) - expect(result.schemas.basic.properties.name.enum[0]).to.be.equal('blah') - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - }); + describe("const values", () => { + it("should convert const values to enum", async function () { + const newConvertor = new Convertor(basicConst); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties.name).to.not.have.property( + "const" + ); + expect(result.schemas.basic.properties.name).to.have.property("enum"); + expect(result.schemas.basic.properties.name.enum.length).to.be.equal(1); + expect(result.schemas.basic.properties.name.enum[0]).to.be.equal( + "blah" + ); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + }); - describe('if then else schemas', () => { - it('should convert an if then else schema', async function() { - const newConvertor = new Convertor(ifThenElse) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties).to.have.property('street_address') - expect(result.schemas.basic.properties).to.have.property('country') - expect(result.schemas.basic).to.have.property('oneOf') - expect(result.schemas.basic.oneOf).to.be.an('array') - expect(result.schemas.basic.oneOf.length).to.be.equal(2) - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should convert an if then schema', async function() { - const newConvertor = new Convertor(ifThen) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties).to.have.property('street_address') - expect(result.schemas.basic.properties).to.have.property('country') - expect(result.schemas.basic).to.have.property('oneOf') - expect(result.schemas.basic.oneOf).to.be.an('array') - expect(result.schemas.basic.oneOf.length).to.be.equal(1) - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should convert an if else schema', async function() { - const newConvertor = new Convertor(ifElse) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties).to.have.property('street_address') - expect(result.schemas.basic.properties).to.have.property('country') - expect(result.schemas.basic).to.have.property('oneOf') - expect(result.schemas.basic.oneOf).to.be.an('array') - expect(result.schemas.basic.oneOf.length).to.be.equal(1) - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should remove an then else keyword without an if', async function() { - const newConvertor = new Convertor(thenElseSchema) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties).to.have.property('street_address') - expect(result.schemas.basic.properties).to.have.property('country') - expect(result.schemas.basic).to.not.have.property('oneOf') - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should remove an if keyword without a then or an else', async function() { - const newConvertor = new Convertor(ifSchema) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties).to.have.property('street_address') - expect(result.schemas.basic.properties).to.have.property('country') - expect(result.schemas.basic).to.not.have.property('oneOf') - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should remove an else keyword without an if', async function() { - const newConvertor = new Convertor(elseSchema) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties).to.have.property('street_address') - expect(result.schemas.basic.properties).to.have.property('country') - expect(result.schemas.basic).to.not.have.property('oneOf') - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should remove a then keyword without an if', async function() { - const newConvertor = new Convertor(thenSchema) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties).to.have.property('street_address') - expect(result.schemas.basic.properties).to.have.property('country') - expect(result.schemas.basic).to.not.have.property('oneOf') - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - }); + describe("if then else schemas", () => { + it("should convert an if then else schema", async function () { + const newConvertor = new Convertor(ifThenElse); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties).to.have.property( + "street_address" + ); + expect(result.schemas.basic.properties).to.have.property("country"); + expect(result.schemas.basic).to.have.property("oneOf"); + expect(result.schemas.basic.oneOf).to.be.an("array"); + expect(result.schemas.basic.oneOf.length).to.be.equal(2); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should convert an if then schema", async function () { + const newConvertor = new Convertor(ifThen); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties).to.have.property( + "street_address" + ); + expect(result.schemas.basic.properties).to.have.property("country"); + expect(result.schemas.basic).to.have.property("oneOf"); + expect(result.schemas.basic.oneOf).to.be.an("array"); + expect(result.schemas.basic.oneOf.length).to.be.equal(1); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should convert an if else schema", async function () { + const newConvertor = new Convertor(ifElse); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties).to.have.property( + "street_address" + ); + expect(result.schemas.basic.properties).to.have.property("country"); + expect(result.schemas.basic).to.have.property("oneOf"); + expect(result.schemas.basic.oneOf).to.be.an("array"); + expect(result.schemas.basic.oneOf.length).to.be.equal(1); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should remove an then else keyword without an if", async function () { + const newConvertor = new Convertor(thenElseSchema); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties).to.have.property( + "street_address" + ); + expect(result.schemas.basic.properties).to.have.property("country"); + expect(result.schemas.basic).to.not.have.property("oneOf"); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should remove an if keyword without a then or an else", async function () { + const newConvertor = new Convertor(ifSchema); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties).to.have.property( + "street_address" + ); + expect(result.schemas.basic.properties).to.have.property("country"); + expect(result.schemas.basic).to.not.have.property("oneOf"); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should remove an else keyword without an if", async function () { + const newConvertor = new Convertor(elseSchema); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties).to.have.property( + "street_address" + ); + expect(result.schemas.basic.properties).to.have.property("country"); + expect(result.schemas.basic).to.not.have.property("oneOf"); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should remove a then keyword without an if", async function () { + const newConvertor = new Convertor(thenSchema); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties).to.have.property( + "street_address" + ); + expect(result.schemas.basic.properties).to.have.property("country"); + expect(result.schemas.basic).to.not.have.property("oneOf"); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + }); - describe('dependencies schemas', () => { - it('should convert a dependencies as Array schema', async function() { - const newConvertor = new Convertor(dependenciesArray) - const result = newConvertor.convert('basic') - - expect(result.schemas.basic.properties).to.have.property('billing_address') - expect(result.schemas.basic.properties).to.have.property('credit_card') - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should convert a dependencies as Schema schema', async function() { - const newConvertor = new Convertor(dependenciesSchema) - const result = newConvertor.convert('basic') - - expect(result.schemas.basic.properties).to.have.property('name') - expect(result.schemas.basic.properties).to.have.property('credit_card') - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should convert a dependentRequired', async function() { - const newConvertor = new Convertor(dependentRequired) - const result = newConvertor.convert('basic') - - expect(result.schemas.basic.properties).to.have.property('name') - expect(result.schemas.basic.properties).to.have.property('credit_card') - expect(result.schemas.basic.properties).to.have.property('billing_address') - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should convert a dependentSchemas', async function() { - const newConvertor = new Convertor(dependentSchemas) - const result = newConvertor.convert('basic') - - expect(result.schemas.basic.properties).to.have.property('name') - expect(result.schemas.basic.properties).to.have.property('credit_card') - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - }); + describe("dependencies schemas", () => { + it("should convert a dependencies as Array schema", async function () { + const newConvertor = new Convertor(dependenciesArray); + const result = newConvertor.convert("basic"); + + expect(result.schemas.basic.properties).to.have.property( + "billing_address" + ); + expect(result.schemas.basic.properties).to.have.property("credit_card"); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should convert a dependencies as Schema schema", async function () { + const newConvertor = new Convertor(dependenciesSchema); + const result = newConvertor.convert("basic"); + + expect(result.schemas.basic.properties).to.have.property("name"); + expect(result.schemas.basic.properties).to.have.property("credit_card"); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should convert a dependentRequired", async function () { + const newConvertor = new Convertor(dependentRequired); + const result = newConvertor.convert("basic"); + + expect(result.schemas.basic.properties).to.have.property("name"); + expect(result.schemas.basic.properties).to.have.property("credit_card"); + expect(result.schemas.basic.properties).to.have.property( + "billing_address" + ); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should convert a dependentSchemas", async function () { + const newConvertor = new Convertor(dependentSchemas); + const result = newConvertor.convert("basic"); + + expect(result.schemas.basic.properties).to.have.property("name"); + expect(result.schemas.basic.properties).to.have.property("credit_card"); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + }); - describe('camelCased Keys', () => { - it('should convert camelCasedKey correctly', async function() { - const newConvertor = new Convertor(camelCased) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.name).to.not.have.property('oneof') - expect(result.schemas.basic.properties.name).to.have.property('oneOf') - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - }); + describe("camelCased Keys", () => { + it("should convert camelCasedKey correctly", async function () { + const newConvertor = new Convertor(camelCased); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties.name).to.not.have.property( + "oneof" + ); + expect(result.schemas.basic.properties.name).to.have.property("oneOf"); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + }); - describe('array keys', () => { - it('should make sure expected array keys are actually arrays', async function() { - const newConvertor = new Convertor(arrayKeyOneOf) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.name).to.have.property('oneOf') - expect(result.schemas.basic.properties.name.oneOf).to.be.an('array') - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - }); + describe("array keys", () => { + it("should make sure expected array keys are actually arrays", async function () { + const newConvertor = new Convertor(arrayKeyOneOf); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties.name).to.have.property("oneOf"); + expect(result.schemas.basic.properties.name.oneOf).to.be.an("array"); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + }); - describe('anyOf and oneOf with an object of type null', () => { - it('should convert an anyOf with a type of null', async function() { - const newConvertor = new Convertor(anyOfNull) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.payment).to.have.property('anyOf') - expect(result.schemas.basic.properties.payment.anyOf).to.be.an('array') - expect(result.schemas.basic.properties.payment.anyOf.length).to.be.equal(1) - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it('should convert a oneOf with a type of null', async function() { - const newConvertor = new Convertor(oneOfNull) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.payment).to.have.property('oneOf') - expect(result.schemas.basic.properties.payment.oneOf).to.be.an('array') - expect(result.schemas.basic.properties.payment.oneOf.length).to.be.equal(1) - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - }); + describe("anyOf and oneOf with an object of type null", () => { + it("should convert an anyOf with a type of null", async function () { + const newConvertor = new Convertor(anyOfNull); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties.payment).to.have.property( + "anyOf" + ); + expect(result.schemas.basic.properties.payment.anyOf).to.be.an("array"); + expect( + result.schemas.basic.properties.payment.anyOf.length + ).to.be.equal(1); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it("should convert a oneOf with a type of null", async function () { + const newConvertor = new Convertor(oneOfNull); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties.payment).to.have.property( + "oneOf" + ); + expect(result.schemas.basic.properties.payment.oneOf).to.be.an("array"); + expect( + result.schemas.basic.properties.payment.oneOf.length + ).to.be.equal(1); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + }); + + describe(`properties that exist outside of a oneOf|anyOf|allOf`, function () { + it(`should put the property outside of an oneOf into the oneOf`, async function () { + const newConvertor = new Convertor(oneOfProperties); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties.payment).to.have.property( + "oneOf" + ); + expect(result.schemas.basic.properties.payment.oneOf).to.be.an("array"); + expect( + result.schemas.basic.properties.payment.oneOf.length + ).to.be.equal(1); + expect(result.schemas.basic.properties.payment).to.not.have.property( + "default" + ); + expect( + result.schemas.basic.properties.payment.oneOf[0] + ).to.have.property("default"); + expect( + result.schemas.basic.properties.payment.oneOf[0].default + ).to.be.equal("one"); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it(`should put the property outside of an anyOf into the anyOf`, async function () { + const newConvertor = new Convertor(anyOfProperties); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties.payment).to.have.property( + "anyOf" + ); + expect(result.schemas.basic.properties.payment.anyOf).to.be.an("array"); + expect( + result.schemas.basic.properties.payment.anyOf.length + ).to.be.equal(2); + expect(result.schemas.basic.properties.payment).to.not.have.property( + "default" + ); + expect( + result.schemas.basic.properties.payment.anyOf[0] + ).to.have.property("default"); + expect( + result.schemas.basic.properties.payment.anyOf[0].default + ).to.be.equal("one"); + expect( + result.schemas.basic.properties.payment.anyOf[1] + ).to.have.property("default"); + expect( + result.schemas.basic.properties.payment.anyOf[1].default + ).to.be.equal(1); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + + it(`should put the property outside of an allOf into the allOf`, async function () { + const newConvertor = new Convertor(allOfProperties); + const result = newConvertor.convert("basic"); + expect(result.schemas.basic.properties.payment).to.have.property( + "allOf" + ); + expect(result.schemas.basic.properties.payment.allOf).to.be.an("array"); + expect( + result.schemas.basic.properties.payment.allOf.length + ).to.be.equal(1); + expect(result.schemas.basic.properties.payment).to.not.have.property( + "default" + ); + expect( + result.schemas.basic.properties.payment.allOf[0] + ).to.have.property("default"); + expect( + result.schemas.basic.properties.payment.allOf[0].default + ).to.be.equal("one"); + + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + }); + }); + + describe(`circular schemas`, function () { + it(`should convert a circular schema`, async function () { + const bundled = await $RefParser.bundle(circular); + const dereferenced = await $RefParser.dereference(bundled); + + const newConvertor = new Convertor(dereferenced); + const result = newConvertor.convert("basic"); + console.log(result); + }); + }); - describe(`properties that exist outside of a oneOf|anyOf|allOf`, function () { - it(`should put the property outside of an oneOf into the oneOf`, async function() { - const newConvertor = new Convertor(oneOfProperties) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.payment).to.have.property('oneOf') - expect(result.schemas.basic.properties.payment.oneOf).to.be.an('array') - expect(result.schemas.basic.properties.payment.oneOf.length).to.be.equal(1) - expect(result.schemas.basic.properties.payment).to.not.have.property('default') - expect(result.schemas.basic.properties.payment.oneOf[0]).to.have.property('default') - expect(result.schemas.basic.properties.payment.oneOf[0].default).to.be.equal('one') - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it(`should put the property outside of an anyOf into the anyOf`, async function() { - const newConvertor = new Convertor(anyOfProperties) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.payment).to.have.property('anyOf') - expect(result.schemas.basic.properties.payment.anyOf).to.be.an('array') - expect(result.schemas.basic.properties.payment.anyOf.length).to.be.equal(2) - expect(result.schemas.basic.properties.payment).to.not.have.property('default') - expect(result.schemas.basic.properties.payment.anyOf[0]).to.have.property('default') - expect(result.schemas.basic.properties.payment.anyOf[0].default).to.be.equal('one') - expect(result.schemas.basic.properties.payment.anyOf[1]).to.have.property('default') - expect(result.schemas.basic.properties.payment.anyOf[1].default).to.be.equal(1) - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); - - it(`should put the property outside of an allOf into the allOf`, async function() { - const newConvertor = new Convertor(allOfProperties) - const result = newConvertor.convert('basic') - expect(result.schemas.basic.properties.payment).to.have.property('allOf') - expect(result.schemas.basic.properties.payment.allOf).to.be.an('array') - expect(result.schemas.basic.properties.payment.allOf.length).to.be.equal(1) - expect(result.schemas.basic.properties.payment).to.not.have.property('default') - expect(result.schemas.basic.properties.payment.allOf[0]).to.have.property('default') - expect(result.schemas.basic.properties.payment.allOf[0].default).to.be.equal('one') - - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - Object.assign(cloned, {components: result}) - expect(cloned).to.have.property('components') - expect(cloned.components).to.have.property('schemas') - expect(cloned.components.schemas).to.have.property('basic') - let valid = await validator.validateInner(cloned, {}) - expect(valid).to.be.true - }); + xdescribe("use a repo with lots of schemas to find failing ones", () => { + it("should convert all schemas successfully", async function () { + this.timeout(1000000); + // these are mostly now failing because of things like missing types (which i need to decide how to deal with), + // circular references that (?) that can't be handled by a JSON.parse(JSON.stringify(schema)) + // and a few where I am not correctly dereferencing them just yet (the azure ones reference each other) + const bannedSchemas = listOfBannedSchemas; + + const url = `https://api.github.com/repos/SchemaStore/schemastore/contents/src/schemas/json`; + const list = await fetch(url).then((res) => res.json()); + const rawURLs = list.map((item) => { + return item.download_url; }); - xdescribe('use a repo with lots of schemas to find failing ones', () => { - it('should convert all schemas successfully', async function() { - this.timeout(1000000); - // these are mostly now failing because of things like missing types (which i need to decide how to deal with), - // circular references that (?) that can't be handled by a JSON.parse(JSON.stringify(schema)) - // and a few where I am not correctly dereferencing them just yet (the azure ones reference each other) - const bannedSchemas = listOfBannedSchemas - - const url = `https://api.github.com/repos/SchemaStore/schemastore/contents/src/schemas/json`; - const list = await fetch(url).then(res => res.json()); - const rawURLs = list.map(item => { - return item.download_url - }) - - for (const rawUrl of rawURLs) { - console.log(rawUrl) - if (bannedSchemas.includes(rawUrl) === false) { - - const data = await fetch(rawUrl).then(res => res.json()) - .catch(err => { - console.log(err) - throw err; - }) - const newSchema = await $RefParser.dereference(data, {}) - const convertor = new Convertor(newSchema) - const result = convertor.convert('basic') - // console.log(JSON.stringify(result)) - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)) - // console.log(JSON.stringify(cloned)) - // let valid = await validator.validateInner(cloned, {}) - // expect(valid).to.be.true - Object.assign(cloned, {components: result}) - let valid = await validator.validateInner(cloned, {}) - .catch(err => { - console.log(err) - // console.log(JSON.stringify(data)) - // console.log(JSON.stringify(newSchema)) - // console.log(JSON.stringify(result)) - // console.log(JSON.stringify(cloned)) - throw err; - }) - - expect(valid).to.be.true - } - } - }) - }) + for (const rawUrl of rawURLs) { + console.log(rawUrl); + if (bannedSchemas.includes(rawUrl) === false) { + const data = await fetch(rawUrl) + .then((res) => res.json()) + .catch((err) => { + console.log(err); + throw err; + }); + const newSchema = await $RefParser.dereference(data, {}); + const convertor = new Convertor(newSchema); + const result = convertor.convert("basic"); + // console.log(JSON.stringify(result)) + const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + // console.log(JSON.stringify(cloned)) + // let valid = await validator.validateInner(cloned, {}) + // expect(valid).to.be.true + Object.assign(cloned, { components: result }); + let valid = await validator + .validateInner(cloned, {}) + .catch((err) => { + console.log(err); + // console.log(JSON.stringify(data)) + // console.log(JSON.stringify(newSchema)) + // console.log(JSON.stringify(result)) + // console.log(JSON.stringify(cloned)) + throw err; + }); + + expect(valid).to.be.true; + } + } + }); }); + }); }); From 29a1c749db0c6f875dc048236880d7b2e5e89dd2 Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Thu, 21 Sep 2023 16:18:30 +0100 Subject: [PATCH 04/14] formatting and making use of the cloneDeep function --- src/Convertor.js | 878 ++++++++++++++++++++++++----------------------- 1 file changed, 445 insertions(+), 433 deletions(-) diff --git a/src/Convertor.js b/src/Convertor.js index 482a10b..9851f5a 100644 --- a/src/Convertor.js +++ b/src/Convertor.js @@ -1,480 +1,492 @@ -'use strict' +"use strict"; -const traverse = require('json-schema-traverse') -const {v4: uuid} = require('uuid') +const traverse = require("json-schema-traverse"); +const { v4: uuid } = require("uuid"); +const cloneDeep = require("lodash.clonedeep"); class Convertor { - constructor(schema = {}) { - this.schema = JSON.parse(JSON.stringify(schema)) - - this.camelCasedProperties = [ - 'allOf', - 'oneOf', - 'anyOf', - 'additionalProperties', - 'multipleOf', - 'exclusiveMaximum', - 'exclusiveMinimum', - 'maxLength', - 'minLength', - 'maxItems', - 'minItems', - 'uniqueItems', - 'maxProperties', - 'minProperties', - 'readOnly', - 'writeOnly', - 'externalDocs', - ] - - this.arrayFields = [ - 'allOf', - 'oneOf', - 'anyOf', - 'enum', - 'required', - ] - - this.specialSchemaFields = [ - 'type', - 'allOf', - 'oneOf', - 'anyOf', - 'not', - 'items', - 'properties', - 'additionalProperties', - 'description', - 'format', - 'default', - ] - - let validSchemaFields = [ - 'title', - 'multipleOf', - 'maximum', - 'exclusiveMaximum', - 'minimum', - 'exclusiveMinimum', - 'maxLength', - 'minLength', - 'pattern', - 'maxItems', - 'minItems', - 'uniqueItems', - 'maxProperties', - 'minProperties', - 'required', - 'enum', - '$ref' - ] - - let openAPISchemaFields = [ - 'nullable', - 'discriminator', - 'readOnly', - 'writeOnly', - 'xml', - 'externalDocs', - 'example', - 'deprecated', - ] - - this.validSchemaFields = validSchemaFields.concat(this.specialSchemaFields, openAPISchemaFields) - - this.components = {} + constructor(schema = {}) { + this.schema = cloneDeep(schema); + + this.camelCasedProperties = [ + "allOf", + "oneOf", + "anyOf", + "additionalProperties", + "multipleOf", + "exclusiveMaximum", + "exclusiveMinimum", + "maxLength", + "minLength", + "maxItems", + "minItems", + "uniqueItems", + "maxProperties", + "minProperties", + "readOnly", + "writeOnly", + "externalDocs", + ]; + + this.arrayFields = ["allOf", "oneOf", "anyOf", "enum", "required"]; + + this.specialSchemaFields = [ + "type", + "allOf", + "oneOf", + "anyOf", + "not", + "items", + "properties", + "additionalProperties", + "description", + "format", + "default", + ]; + + let validSchemaFields = [ + "title", + "multipleOf", + "maximum", + "exclusiveMaximum", + "minimum", + "exclusiveMinimum", + "maxLength", + "minLength", + "pattern", + "maxItems", + "minItems", + "uniqueItems", + "maxProperties", + "minProperties", + "required", + "enum", + "$ref", + ]; + + let openAPISchemaFields = [ + "nullable", + "discriminator", + "readOnly", + "writeOnly", + "xml", + "externalDocs", + "example", + "deprecated", + ]; + + this.validSchemaFields = validSchemaFields.concat( + this.specialSchemaFields, + openAPISchemaFields + ); + + this.components = {}; + } + + convert(name) { + const traversal = ( + schema, + jsonPointer, + rootSchema, + parentJSONPointer, + parentKeyword, + parentSchema, + property + ) => { + this.parseSchema(schema); + if (this.components.schemas) { + Object.assign(this.components.schemas, { [name]: rootSchema }); + } else { + Object.assign(this.components, { schemas: { [name]: rootSchema } }); + } + }; + if (this.schema.title === "UserResponse") + console.log( + this.schema.properties.user.properties.classes.items.properties.subRows + ); + traverse(this.schema, traversal); + return this.components; + } + + parseSchema(schema) { + this.convertConst(schema); + this.convertArrays(schema); + this.convertIfThenElse(schema); + this.convertTypeArrays(schema); + this.dealWithCamelCase(schema); + this.ensureArrayFields(schema); + this.convertDependencies(schema); + this.removeEmptyRequired(schema); + this.convertNullProperty(schema); + this.convertDefaultValues(schema); + this.pullOrphanedPropertiesIntoOneAnyAllOf(schema); + this.convertOneOfAnyOfNulls(schema); + this.removeInvalidFields(schema); + } + + removeEmptyRequired(schema) { + if (schema.required && schema.required.length === 0) delete schema.required; + } + + removeInvalidFields(schema) { + for (const key in schema) { + if (this.validSchemaFields.includes(key) === false) { + delete schema[key]; + } } + } - convert(name) { - const traversal = ( - schema, - jsonPointer, - rootSchema, - parentJSONPointer, - parentKeyword, - parentSchema, - property - ) => { - this.parseSchema(schema) - if (this.components.schemas) { - Object.assign(this.components.schemas, {[name]: rootSchema}) - } else { - Object.assign(this.components, {schemas: {[name]: rootSchema}}) - } - - } - - traverse(this.schema, traversal) - return this.components + convertNullProperty(schema) { + if (schema?.type === "null") { + schema.nullable = true; + delete schema.type; } + } + + convertTypeArrays(schema) { + if (Array.isArray(schema.type)) { + const oneOf = []; + let defaultValue; + let types = schema.type; + let removeeNum = false; + const nullable = types.includes("null"); + if (nullable === true) { + types = types.filter((type) => { + if (type !== "null") return type; + }); + } + + for (const type of types) { + const newTypeObj = {}; + if ( + defaultValue !== undefined && + type === typeof defaultValue && + (typeof defaultValue === type || + (defaultValue === false && type === "boolean")) + ) { + newTypeObj.default = defaultValue; + } - parseSchema(schema) { - this.convertConst(schema) - this.convertArrays(schema) - this.convertIfThenElse(schema) - this.convertTypeArrays(schema) - this.dealWithCamelCase(schema) - this.ensureArrayFields(schema) - this.convertDependencies(schema) - this.removeEmptyRequired(schema) - this.convertNullProperty(schema) - this.convertDefaultValues(schema) - this.pullOrphanedPropertiesIntoOneAnyAllOf(schema) - this.convertOneOfAnyOfNulls(schema) - this.removeInvalidFields(schema) - } + if (nullable === true) { + newTypeObj.nullable = true; + } - removeEmptyRequired(schema) { - if (schema.required && schema.required.length === 0) - delete schema.required - } + newTypeObj.type = type; - removeInvalidFields(schema) { - for (const key in schema) { - if (this.validSchemaFields.includes(key) === false) { - delete schema[key] - } + if (schema.enum) { + removeeNum = true; + const newEnum = []; + schema.enum.forEach((enumType) => { + if (type === typeof enumType) newEnum.push(enumType); + }); + newTypeObj.enum = newEnum; } - } - convertNullProperty(schema) { - if (schema?.type === 'null') { - schema.nullable = true - delete schema.type + if (Object.keys(schema).includes("default")) { + defaultValue = schema.default; + delete schema.default; + if ( + type === typeof defaultValue || + typeof defaultValue === type || + (defaultValue === false && type === "boolean") + ) + newTypeObj.default = defaultValue; } - } - convertTypeArrays(schema) { - if (Array.isArray(schema.type)) { - const oneOf = [] - let defaultValue - let types = schema.type - let removeeNum = false - const nullable = types.includes('null') - if (nullable === true) { - types = types.filter(type => { - if (type !== 'null') - return type - }) - } - - for (const type of types) { - const newTypeObj = {} - if (defaultValue !== undefined && type === typeof defaultValue && (typeof defaultValue === type || defaultValue === false && type === 'boolean')) { - newTypeObj.default = defaultValue - } - - if (nullable === true) { - newTypeObj.nullable = true - } - - newTypeObj.type = type - - if (schema.enum) { - removeeNum = true - const newEnum = [] - schema.enum.forEach(enumType => { - if (type === typeof enumType) - newEnum.push(enumType) - }) - newTypeObj.enum = newEnum - } - - if (Object.keys(schema).includes('default')) { - defaultValue = schema.default - delete schema.default - if (type === typeof defaultValue || (typeof defaultValue === type || defaultValue === false && type === 'boolean')) - newTypeObj.default = defaultValue - - } - - for (const property of Object.keys(schema)) { - if (type === 'array' && property === 'items') { - newTypeObj.items = schema[property] - delete schema.items - } - - if (type === 'object' && property === 'properties') { - newTypeObj.properties = schema[property] - delete schema.properties - } - } - - - oneOf.push(newTypeObj) - } - - schema.oneOf = oneOf - if (removeeNum) - delete schema.enum - delete schema.type - } - } + for (const property of Object.keys(schema)) { + if (type === "array" && property === "items") { + newTypeObj.items = schema[property]; + delete schema.items; + } - convertDefaultValues(schema) { - if (schema.type === 'object') { - if (typeof schema.default === 'string') { - const schemaDefault = {} - // is it a property? - if (Object.keys(schema.properties).includes(schema.default)) { - const schemaType = schema.properties[schema.default].type - switch (schemaType) { - case 'string': - Object.assign(schemaDefault, {[schema.default]: ''}) - break; - case 'number': - case 'integer': - Object.assign(schemaDefault, {[schema.default]: 0}) - break; - case 'array': - Object.assign(schemaDefault, {[schema.default]: []}) - break; - case 'object': - Object.assign(schemaDefault, {[schema.default]: {}}) - break; - case 'boolean': - Object.assign(schemaDefault, {[schema.default]: true}) - break; - } - schema.default = schemaDefault - } - } + if (type === "object" && property === "properties") { + newTypeObj.properties = schema[property]; + delete schema.properties; + } } - if (schema.type === 'array') { - if (schema.items === undefined) { - schema.items = {nullable: true} - } + oneOf.push(newTypeObj); + } - if (schema.default && Array.isArray(schema.default) === false) { - schema.default = [schema.default] - } + schema.oneOf = oneOf; + if (removeeNum) delete schema.enum; + delete schema.type; + } + } + + convertDefaultValues(schema) { + if (schema.type === "object") { + if (typeof schema.default === "string") { + const schemaDefault = {}; + // is it a property? + if (Object.keys(schema.properties).includes(schema.default)) { + const schemaType = schema.properties[schema.default].type; + switch (schemaType) { + case "string": + Object.assign(schemaDefault, { [schema.default]: "" }); + break; + case "number": + case "integer": + Object.assign(schemaDefault, { [schema.default]: 0 }); + break; + case "array": + Object.assign(schemaDefault, { [schema.default]: [] }); + break; + case "object": + Object.assign(schemaDefault, { [schema.default]: {} }); + break; + case "boolean": + Object.assign(schemaDefault, { [schema.default]: true }); + break; + } + schema.default = schemaDefault; } + } + } - if (schema.type === 'boolean') { - if (schema.default === 'true' || schema.default === 'false') { - if (schema.default === 'true') - schema.default = true - else - schema.default = false - } - } + if (schema.type === "array") { + if (schema.items === undefined) { + schema.items = { nullable: true }; + } - if (schema.type === 'number' || schema.type === 'integer') { - if (typeof schema.default === 'string') { - schema.default = parseInt(schema.default, 10) || 1 - } - } + if (schema.default && Array.isArray(schema.default) === false) { + schema.default = [schema.default]; + } + } - if (schema.type === 'string') { - if (Object.keys(schema).indexOf('default') !== -1) { - schema.default = `${schema.default}` - } - } + if (schema.type === "boolean") { + if (schema.default === "true" || schema.default === "false") { + if (schema.default === "true") schema.default = true; + else schema.default = false; + } } - convertArrays(schema) { - if (Array.isArray(schema.items)) { - const obj = {} - Object.assign(obj, schema.items[0]) - schema.items = obj - } + if (schema.type === "number" || schema.type === "integer") { + if (typeof schema.default === "string") { + schema.default = parseInt(schema.default, 10) || 1; + } } - convertConst(schema) { - if (schema.const) { - schema.enum = [schema.const] - delete schema.const - } + if (schema.type === "string") { + if (Object.keys(schema).indexOf("default") !== -1) { + schema.default = `${schema.default}`; + } } + } - convertIfThenElse(schema) { - if (schema?.if && (schema?.then || schema?.else)) { - const ifSchemaRefName = `if-${uuid()}` - - const traversal = ( - schema, - jsonPointer, - rootSchema, - parentJSONPointer, - parentKeyword, - parentSchema, - property - ) => { - this.parseSchema(schema) - } - - traverse(schema.if, traversal) - - if (this.components.schemas) { - Object.assign(this.components.schemas, {[ifSchemaRefName]: schema.if}) - } else { - Object.assign(this.components, {schemas: {[ifSchemaRefName]: schema.if}}) - } - - if (schema?.then || schema?.else) { - let oneOf = [] - if (schema.then) { - oneOf.push({ - allOf: [ - { - $ref: `#/components/schemas/${ifSchemaRefName}`, - }, - schema.then - ] - }) - } - - if (schema.else) { - oneOf.push({ - allOf: [ - { - not: { - $ref: `#/components/schemas/${ifSchemaRefName}` - }, - }, - schema.else - ] - }) - } - schema.oneOf = oneOf - } - } + convertArrays(schema) { + if (Array.isArray(schema.items)) { + const obj = {}; + Object.assign(obj, schema.items[0]); + schema.items = obj; + } + } - delete schema.if - delete schema.then - delete schema.else + convertConst(schema) { + if (schema.const) { + schema.enum = [schema.const]; + delete schema.const; } + } + + convertIfThenElse(schema) { + if (schema?.if && (schema?.then || schema?.else)) { + const ifSchemaRefName = `if-${uuid()}`; + + const traversal = ( + schema, + jsonPointer, + rootSchema, + parentJSONPointer, + parentKeyword, + parentSchema, + property + ) => { + this.parseSchema(schema); + }; + + traverse(schema.if, traversal); + + if (this.components.schemas) { + Object.assign(this.components.schemas, { + [ifSchemaRefName]: schema.if, + }); + } else { + Object.assign(this.components, { + schemas: { [ifSchemaRefName]: schema.if }, + }); + } + + if (schema?.then || schema?.else) { + let oneOf = []; + if (schema.then) { + oneOf.push({ + allOf: [ + { + $ref: `#/components/schemas/${ifSchemaRefName}`, + }, + schema.then, + ], + }); + } - dealWithCamelCase(schema) { - for (const key of Object.keys(schema)) { - const camelCasedKey = this.camelCasedProperties.filter(camelCasedKey => { - if (key.toLowerCase() === camelCasedKey.toLowerCase()) { - return camelCasedKey - } - }) - - if (camelCasedKey.length && camelCasedKey[0] !== key) { - schema[camelCasedKey[0]] = schema[key] - delete schema[key] - } + if (schema.else) { + oneOf.push({ + allOf: [ + { + not: { + $ref: `#/components/schemas/${ifSchemaRefName}`, + }, + }, + schema.else, + ], + }); } + schema.oneOf = oneOf; + } } - ensureArrayFields(schema) { - for (const key of Object.keys(schema)) { - const arrayField = this.arrayFields.filter(field => { - if (key.toLowerCase() === field.toLowerCase()) { - return field - } - }) - - if (arrayField.length && Array.isArray(schema[key]) === false) { - schema[arrayField] = [schema[key]] - } + delete schema.if; + delete schema.then; + delete schema.else; + } + + dealWithCamelCase(schema) { + for (const key of Object.keys(schema)) { + const camelCasedKey = this.camelCasedProperties.filter( + (camelCasedKey) => { + if (key.toLowerCase() === camelCasedKey.toLowerCase()) { + return camelCasedKey; + } } + ); + + if (camelCasedKey.length && camelCasedKey[0] !== key) { + schema[camelCasedKey[0]] = schema[key]; + delete schema[key]; + } } + } - convertDependencies(schema) { - if (schema.dependencies || schema.dependentSchemas || schema.dependentRequired) { - const allOf = [] - const anyOf = [] - if (schema.dependentSchemas) - schema.dependencies = JSON.parse(JSON.stringify(schema.dependentSchemas)) - else if (schema.dependentRequired) - schema.dependencies = JSON.parse(JSON.stringify(schema.dependentRequired)) - - for (const key of Object.keys(schema.dependencies)) { - if (typeof schema.dependencies[key] === 'object' && Array.isArray(schema.dependencies[key]) === false) { - const anyOf = [] - const notObj = { - not: { - required: [key] - } - } - anyOf.push(notObj) - - const newDep = Object.assign({}, schema.dependencies[key]) - anyOf.push(newDep) - allOf.push({anyOf}) - } else { - const notObj = { - not: { - required: [key] - } - } - anyOf.push(notObj) - const newReq = { - required: [schema.dependencies[key]] - } - anyOf.push(newReq) - } - } - if (allOf.length) - schema.allOf = allOf - else - schema.anyOf = anyOf + ensureArrayFields(schema) { + for (const key of Object.keys(schema)) { + const arrayField = this.arrayFields.filter((field) => { + if (key.toLowerCase() === field.toLowerCase()) { + return field; } - } + }); - convertOneOfAnyOfNulls(schema) { - if (schema.oneOf || schema.anyOf) { - const isOneOf = Boolean(schema.oneOf) - const schemaOf = schema.oneOf || schema.anyOf - const hasNullType = schemaOf.some(obj => { - if (obj.type === 'null') - return true - }) - - if (hasNullType) { - schemaOf.forEach(obj => { - if (obj.type !== 'null') { - obj.nullable = true - } - }) - const newOf = schemaOf.filter(obj => { - if (obj.type !== 'null') - return obj - }) - - if (isOneOf) { - schema.oneOf = newOf - } else { - schema.anyOf = newOf - } - } + if (arrayField.length && Array.isArray(schema[key]) === false) { + schema[arrayField] = [schema[key]]; + } + } + } + + convertDependencies(schema) { + if ( + schema.dependencies || + schema.dependentSchemas || + schema.dependentRequired + ) { + const allOf = []; + const anyOf = []; + if (schema.dependentSchemas) + schema.dependencies = cloneDeep(schema.dependentSchemas); + else if (schema.dependentRequired) + schema.dependencies = cloneDeep(schema.dependentRequired); + + for (const key of Object.keys(schema.dependencies)) { + if ( + typeof schema.dependencies[key] === "object" && + Array.isArray(schema.dependencies[key]) === false + ) { + const anyOf = []; + const notObj = { + not: { + required: [key], + }, + }; + anyOf.push(notObj); + + const newDep = Object.assign({}, schema.dependencies[key]); + anyOf.push(newDep); + allOf.push({ anyOf }); + } else { + const notObj = { + not: { + required: [key], + }, + }; + anyOf.push(notObj); + const newReq = { + required: [schema.dependencies[key]], + }; + anyOf.push(newReq); } + } + if (allOf.length) schema.allOf = allOf; + else schema.anyOf = anyOf; } + } + + convertOneOfAnyOfNulls(schema) { + if (schema.oneOf || schema.anyOf) { + const isOneOf = Boolean(schema.oneOf); + const schemaOf = schema.oneOf || schema.anyOf; + const hasNullType = schemaOf.some((obj) => { + if (obj.type === "null") return true; + }); + + if (hasNullType) { + schemaOf.forEach((obj) => { + if (obj.type !== "null") { + obj.nullable = true; + } + }); + const newOf = schemaOf.filter((obj) => { + if (obj.type !== "null") return obj; + }); + + if (isOneOf) { + schema.oneOf = newOf; + } else { + schema.anyOf = newOf; + } + } + } + } - pullOrphanedPropertiesIntoOneAnyAllOf(schema) { - const properties = ['default'] + pullOrphanedPropertiesIntoOneAnyAllOf(schema) { + const properties = ["default"]; - const addItem = (intersection, schemaOf) => { - for (const property of intersection) { - for (const item of schemaOf) { - item[property] = schema[property] - } - } + const addItem = (intersection, schemaOf) => { + for (const property of intersection) { + for (const item of schemaOf) { + item[property] = schema[property]; } - - if (schema.allOf || schema.anyOf || schema.oneOf) { - const intersection = Object.keys(schema).filter(property => properties.includes(property)) - - if (intersection.length) { - if (schema.allOf) { - addItem(intersection, schema.allOf) - } else if (schema.oneOf) { - addItem(intersection, schema.oneOf) - } else { - addItem(intersection, schema.anyOf) - } - } - - for (const property of intersection) { - delete schema[property] - } + } + }; + + if (schema.allOf || schema.anyOf || schema.oneOf) { + const intersection = Object.keys(schema).filter((property) => + properties.includes(property) + ); + + if (intersection.length) { + if (schema.allOf) { + addItem(intersection, schema.allOf); + } else if (schema.oneOf) { + addItem(intersection, schema.oneOf); + } else { + addItem(intersection, schema.anyOf); } + } + + for (const property of intersection) { + delete schema[property]; + } } + } } -module.exports = Convertor +module.exports = Convertor; From 850476e244f8fd4bde08de4f74fbc6820effeee8 Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Fri, 22 Sep 2023 09:44:15 +0100 Subject: [PATCH 05/14] Try and fix circular refrences --- src/Convertor.js | 97 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 4 deletions(-) diff --git a/src/Convertor.js b/src/Convertor.js index 9851f5a..c3f3f9f 100644 --- a/src/Convertor.js +++ b/src/Convertor.js @@ -94,20 +94,40 @@ class Convertor { property ) => { this.parseSchema(schema); + if (this.components.schemas) { Object.assign(this.components.schemas, { [name]: rootSchema }); } else { Object.assign(this.components, { schemas: { [name]: rootSchema } }); } }; - if (this.schema.title === "UserResponse") - console.log( - this.schema.properties.user.properties.classes.items.properties.subRows - ); + + this.closeCircularReferences(); traverse(this.schema, traversal); return this.components; } + closeCircularReferences() { + const report = this.isCyclic(this.schema, true); + + for (const reportDetail of report) { + try { + this.closingTheCircle(this.schema, reportDetail.duplicate, {}); + } catch (err) { + console.error(err); + throw err; + } + } + } + + closingTheCircle(obj, is, value) { + if (typeof is == "string") + return this.closingTheCircle(obj, is.split("."), value); + else if (is.length == 1 && value !== undefined) return (obj[is[0]] = value); + else if (is.length == 0) return obj; + else return this.closingTheCircle(obj[is[0]], is.slice(1), value); + } + parseSchema(schema) { this.convertConst(schema); this.convertArrays(schema); @@ -487,6 +507,75 @@ class Convertor { } } } + + isCyclic(x, bReturnReport) { + var a_sKeys = [], + a_oStack = [], + wm_oSeenObjects = new WeakMap(), //# see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap + oReturnVal = { + found: false, + report: [], + }; + //# Setup the recursive logic to locate any circular references while kicking off the initial call + (function doIsCyclic(oTarget, sKey) { + var a_sTargetKeys, sCurrentKey, i; + + //# If we've seen this oTarget before, flip our .found to true + if (wm_oSeenObjects.has(oTarget)) { + oReturnVal.found = true; + + //# If we are to bReturnReport, add the entries into our .report + if (bReturnReport) { + oReturnVal.report.push({ + instance: oTarget, + source: a_sKeys.slice(0, a_oStack.indexOf(oTarget) + 1).join("."), + duplicate: a_sKeys.join(".") + "." + sKey, + }); + } + } + //# Else if oTarget is an instanceof Object, determine the a_sTargetKeys and .set our oTarget into the wm_oSeenObjects + else if (oTarget instanceof Object) { + a_sTargetKeys = Object.keys(oTarget); + wm_oSeenObjects.set(oTarget /*, undefined*/); + + //# If we are to bReturnReport, .push the current level's/call's items onto our stacks + if (bReturnReport) { + if (sKey) { + a_sKeys.push(sKey); + } + a_oStack.push(oTarget); + } + + //# Traverse the a_sTargetKeys, pulling each into sCurrentKey as we go + //# NOTE: If you want all properties, even non-enumerables, see Object.getOwnPropertyNames() so there is no need to call .hasOwnProperty (per: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) + for (i = 0; i < a_sTargetKeys.length; i++) { + sCurrentKey = a_sTargetKeys[i]; + + //# If we've already .found a circular reference and we're not bReturnReport, fall from the loop + if (oReturnVal.found && !bReturnReport) { + break; + } + //# Else if the sCurrentKey is an instanceof Object, recurse to test + else if (oTarget[sCurrentKey] instanceof Object) { + doIsCyclic(oTarget[sCurrentKey], sCurrentKey); + } + } + + //# .delete our oTarget into the wm_oSeenObjects + wm_oSeenObjects.delete(oTarget); + + //# If we are to bReturnReport, .pop the current level's/call's items off our stacks + if (bReturnReport) { + if (sKey) { + a_sKeys.pop(); + } + a_oStack.pop(); + } + } + })(x, ""); //# doIsCyclic + + return bReturnReport ? oReturnVal.report : oReturnVal.found; + } } module.exports = Convertor; From fcf21f94aa61ba1d8a1dfe9d4bc44b6e1812b564 Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Fri, 22 Sep 2023 09:48:19 +0100 Subject: [PATCH 06/14] lock the package --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 501f564..bf72e65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "uuid": "^9.0.0" }, "devDependencies": { - "@apidevtools/json-schema-ref-parser": "^9.1.0", + "@apidevtools/json-schema-ref-parser": "9.1.0", "chai": "^4.3.7", "mocha": "^10.1.0", "node-fetch": "^2.6.7", diff --git a/package.json b/package.json index 12c5c86..a3041f3 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@apidevtools/json-schema-ref-parser": "^9.1.0", + "@apidevtools/json-schema-ref-parser": "9.1.0", "chai": "^4.3.7", "mocha": "^10.1.0", "node-fetch": "^2.6.7", From ab821a540b2522cf27b591a3d65d49d37e6e038b Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Fri, 22 Sep 2023 09:53:49 +0100 Subject: [PATCH 07/14] revert --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index bf72e65..27dd1ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "uuid": "^9.0.0" }, "devDependencies": { - "@apidevtools/json-schema-ref-parser": "9.1.0", + "@apidevtools/json-schema-ref-parser": "^9.1.0", "chai": "^4.3.7", "mocha": "^10.1.0", "node-fetch": "^2.6.7", @@ -91,9 +91,9 @@ "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", + "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", "dev": true }, "node_modules/ansi-colors": { @@ -1500,9 +1500,9 @@ "dev": true }, "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", + "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", "dev": true }, "ansi-colors": { diff --git a/package.json b/package.json index a3041f3..12c5c86 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@apidevtools/json-schema-ref-parser": "9.1.0", + "@apidevtools/json-schema-ref-parser": "^9.1.0", "chai": "^4.3.7", "mocha": "^10.1.0", "node-fetch": "^2.6.7", From 14a7bbbfc02c320b13f194f6be11b3ba0b6a435a Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Fri, 22 Sep 2023 09:53:58 +0100 Subject: [PATCH 08/14] this seems to be failing? --- .github/workflows/node.js.yml | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index d1bc1c9..0adc88c 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -11,7 +11,6 @@ on: [push, pull_request] jobs: build: - runs-on: ubuntu-latest strategy: @@ -20,13 +19,13 @@ jobs: # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - - run: npm i -g npm - - run: npm ci - - run: npm run build --if-present - - run: npm test + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + # - run: npm i -g npm + - run: npm ci + - run: npm run build --if-present + - run: npm test From cf49248174dda20cf1f15a6436814caed0885e52 Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Fri, 22 Sep 2023 10:46:43 +0100 Subject: [PATCH 09/14] use let --- src/Convertor.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Convertor.js b/src/Convertor.js index c3f3f9f..527e885 100644 --- a/src/Convertor.js +++ b/src/Convertor.js @@ -509,7 +509,7 @@ class Convertor { } isCyclic(x, bReturnReport) { - var a_sKeys = [], + let a_sKeys = [], a_oStack = [], wm_oSeenObjects = new WeakMap(), //# see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap oReturnVal = { @@ -518,7 +518,7 @@ class Convertor { }; //# Setup the recursive logic to locate any circular references while kicking off the initial call (function doIsCyclic(oTarget, sKey) { - var a_sTargetKeys, sCurrentKey, i; + let a_sTargetKeys, sCurrentKey, i; //# If we've seen this oTarget before, flip our .found to true if (wm_oSeenObjects.has(oTarget)) { From ba5741f85c48421ef42d10119c7a035a42be2be5 Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Fri, 22 Sep 2023 12:15:58 +0100 Subject: [PATCH 10/14] add tests for circular references --- test/src/Convertor.spec.js | 122 ++++++++++++++++++++++++++++++++++--- 1 file changed, 115 insertions(+), 7 deletions(-) diff --git a/test/src/Convertor.spec.js b/test/src/Convertor.spec.js index d487a06..d181cba 100644 --- a/test/src/Convertor.spec.js +++ b/test/src/Convertor.spec.js @@ -772,7 +772,7 @@ describe("Convertor", () => { result.schemas.basic.properties.payment.oneOf[0].default ).to.be.equal("one"); - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + const cloned = cloneDeep(basicOpenAPI); Object.assign(cloned, { components: result }); expect(cloned).to.have.property("components"); expect(cloned.components).to.have.property("schemas"); @@ -807,7 +807,7 @@ describe("Convertor", () => { result.schemas.basic.properties.payment.anyOf[1].default ).to.be.equal(1); - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + const cloned = cloneDeep(basicOpenAPI); Object.assign(cloned, { components: result }); expect(cloned).to.have.property("components"); expect(cloned.components).to.have.property("schemas"); @@ -836,7 +836,7 @@ describe("Convertor", () => { result.schemas.basic.properties.payment.allOf[0].default ).to.be.equal("one"); - const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); + const cloned = cloneDeep(basicOpenAPI); Object.assign(cloned, { components: result }); expect(cloned).to.have.property("components"); expect(cloned.components).to.have.property("schemas"); @@ -848,12 +848,120 @@ describe("Convertor", () => { describe(`circular schemas`, function () { it(`should convert a circular schema`, async function () { - const bundled = await $RefParser.bundle(circular); - const dereferenced = await $RefParser.dereference(bundled); + const parser = new $RefParser(); + const bundled = await parser.bundle(circular); + const dereferenced = await parser.dereference(bundled); + + const getCircularReplacer = () => { + const seen = new WeakSet(); + return (key, value) => { + if (typeof value === "object" && value !== null) { + if (seen.has(value)) { + return; + } + seen.add(value); + } + return value; + }; + }; + + // console.log(JSON.stringify(dereferenced, getCircularReplacer())); + + expect(parser.$refs.circular).to.be.true; + expect(dereferenced).to.have.property("definitions"); + expect( + dereferenced.properties.user.properties.classes.items.properties + ).to.haveOwnProperty("subRows"); + expect( + dereferenced.properties.user.properties.classes.items.properties + ).to.haveOwnProperty("className"); + expect( + dereferenced.properties.user.properties.classes.items.properties + ).to.haveOwnProperty("details"); + expect( + dereferenced.properties.user.properties.classes.items.properties + ).to.haveOwnProperty("id"); + expect( + dereferenced.properties.user.properties.classes.items.properties + ).to.haveOwnProperty("parentId"); + + expect( + dereferenced.properties.user.properties.classes.items.properties + .subRows.type + ).to.be.equal("array"); + expect( + dereferenced.properties.user.properties.classes.items.properties + .subRows.items.properties + ).to.haveOwnProperty("subRows"); + expect( + dereferenced.properties.user.properties.classes.items.properties + .subRows.items.properties + ).to.haveOwnProperty("className"); + expect( + dereferenced.properties.user.properties.classes.items.properties + .subRows.items.properties + ).to.haveOwnProperty("details"); + expect( + dereferenced.properties.user.properties.classes.items.properties + .subRows.items.properties + ).to.haveOwnProperty("id"); + expect( + dereferenced.properties.user.properties.classes.items.properties + .subRows.items.properties + ).to.haveOwnProperty("parentId"); const newConvertor = new Convertor(dereferenced); - const result = newConvertor.convert("basic"); - console.log(result); + try { + const result = newConvertor.convert("basic"); + + expect(result.schemas.basic).to.not.have.property("definitions"); + expect( + result.schemas.basic.properties.user.properties.classes.items + .properties + ).to.haveOwnProperty("subRows"); + expect( + result.schemas.basic.properties.user.properties.classes.items + .properties + ).to.haveOwnProperty("className"); + expect( + result.schemas.basic.properties.user.properties.classes.items + .properties + ).to.haveOwnProperty("details"); + expect( + result.schemas.basic.properties.user.properties.classes.items + .properties + ).to.haveOwnProperty("id"); + expect( + result.schemas.basic.properties.user.properties.classes.items + .properties + ).to.haveOwnProperty("parentId"); + + expect( + result.schemas.basic.properties.user.properties.classes.items + .properties.subRows.type + ).to.be.equal("array"); + + expect( + result.schemas.basic.properties.user.properties.classes.items + .properties.subRows + ).to.have.property("items"); + expect( + result.schemas.basic.properties.user.properties.classes.items + .properties.subRows.items + ).to.be.deep.equal({}); + + // console.log(JSON.stringify(result)); + + const cloned = cloneDeep(basicOpenAPI); + Object.assign(cloned, { components: result }); + expect(cloned).to.have.property("components"); + expect(cloned.components).to.have.property("schemas"); + expect(cloned.components.schemas).to.have.property("basic"); + let valid = await validator.validateInner(cloned, {}); + expect(valid).to.be.true; + } catch (err) { + expect(err).to.be.undefined; + } }); }); From c47aa072528cc5e4f6ee77e9fa8b7767cf239d62 Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Fri, 22 Sep 2023 12:19:40 +0100 Subject: [PATCH 11/14] adds use of the cloneDeep library --- test/src/Convertor.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/src/Convertor.spec.js b/test/src/Convertor.spec.js index d181cba..8325b84 100644 --- a/test/src/Convertor.spec.js +++ b/test/src/Convertor.spec.js @@ -5,6 +5,7 @@ const validator = require("oas-validator"); // for external schema validation tests const fetch = require("node-fetch"); const $RefParser = require("@apidevtools/json-schema-ref-parser"); +const cloneDeep = require("lodash.clonedeep"); const Convertor = require("../../src/Convertor"); From 11e9cbd87c3467605c4f6464a47d79da6d8b1438 Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Fri, 22 Sep 2023 12:26:31 +0100 Subject: [PATCH 12/14] lets add an explanation to why this has been closed off --- src/Convertor.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Convertor.js b/src/Convertor.js index 527e885..ce16cd4 100644 --- a/src/Convertor.js +++ b/src/Convertor.js @@ -3,6 +3,7 @@ const traverse = require("json-schema-traverse"); const { v4: uuid } = require("uuid"); const cloneDeep = require("lodash.clonedeep"); +const packageData = require("../package.json"); class Convertor { constructor(schema = {}) { @@ -112,7 +113,9 @@ class Convertor { for (const reportDetail of report) { try { - this.closingTheCircle(this.schema, reportDetail.duplicate, {}); + this.closingTheCircle(this.schema, reportDetail.duplicate, { + description: `This was found to be a circular reference and has been closed off to avoid repetitive processing. This closure was made by json-schema-for-openapi v${packageData.version} - please open an issue at: ${packageData.bugs}`, + }); } catch (err) { console.error(err); throw err; From b8901d9db64d43f08a5e069c302009442243de51 Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Fri, 22 Sep 2023 12:27:09 +0100 Subject: [PATCH 13/14] test for the message --- test/src/Convertor.spec.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/src/Convertor.spec.js b/test/src/Convertor.spec.js index 8325b84..6e5a22d 100644 --- a/test/src/Convertor.spec.js +++ b/test/src/Convertor.spec.js @@ -8,6 +8,7 @@ const $RefParser = require("@apidevtools/json-schema-ref-parser"); const cloneDeep = require("lodash.clonedeep"); const Convertor = require("../../src/Convertor"); +const packageData = require("../../package.json"); // JSON Schemas // basic @@ -949,7 +950,9 @@ describe("Convertor", () => { expect( result.schemas.basic.properties.user.properties.classes.items .properties.subRows.items - ).to.be.deep.equal({}); + ).to.be.deep.equal({ + description: `This was found to be a circular reference and has been closed off to avoid repetitive processing. This closure was made by json-schema-for-openapi v${packageData.version} - please open an issue at: ${packageData.bugs}`, + }); // console.log(JSON.stringify(result)); From 8273d510c30d78f29becd5923c251164bb222165 Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Fri, 22 Sep 2023 14:22:12 +0100 Subject: [PATCH 14/14] 0.4.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 27dd1ac..fad3963 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "json-schema-for-openapi", - "version": "0.3.2", + "version": "0.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "json-schema-for-openapi", - "version": "0.3.2", + "version": "0.4.0", "license": "Apache-2.0", "dependencies": { "json-schema-traverse": "^1.0.0", diff --git a/package.json b/package.json index 12c5c86..049bb9a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json-schema-for-openapi", - "version": "0.3.2", + "version": "0.4.0", "description": "Converts a regular JSON Schema to a compatible OpenAPI 3.0.X Schema Object", "keywords": [ "json",