From 3b93f83c3b69c626062749335095085b3418c736 Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Tue, 9 Jul 2024 12:08:01 +0100 Subject: [PATCH 1/7] correctly deal with nullable --- src/Convertor.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Convertor.js b/src/Convertor.js index b6ae903..8672c12 100644 --- a/src/Convertor.js +++ b/src/Convertor.js @@ -186,6 +186,7 @@ class Convertor { let defaultValue; let types = schema.type; let removeeNum = false; + const nullable = types.includes("null"); if (nullable === true) { types = types.filter((type) => { @@ -245,9 +246,13 @@ class Convertor { oneOf.push(newTypeObj); } - schema.oneOf = oneOf; - if (removeeNum) delete schema.enum; - delete schema.type; + if (oneOf.length > 1) { + schema.oneOf = oneOf; + delete schema.type; + if (removeeNum) delete schema.enum; + } else { + Object.assign(schema, oneOf[0]); + } } } From f274248f8ee795896870d97a7df7d64e62ffe5c4 Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Tue, 9 Jul 2024 12:08:16 +0100 Subject: [PATCH 2/7] add a schema with nullable --- test/schemas/nullProperties/nullAndType.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 test/schemas/nullProperties/nullAndType.json diff --git a/test/schemas/nullProperties/nullAndType.json b/test/schemas/nullProperties/nullAndType.json new file mode 100644 index 0000000..da0592c --- /dev/null +++ b/test/schemas/nullProperties/nullAndType.json @@ -0,0 +1,11 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "JSON API Schema", + "description": "This is a schema for responses in the JSON API format. For more, see http://jsonapi.org", + "type": "object", + "properties": { + "typedProperty": { + "type": ["null", "string"] + } + } +} From 2dbafc54c128f0a474f8e8b0993179e01b56c81d Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Tue, 9 Jul 2024 12:08:27 +0100 Subject: [PATCH 3/7] tests --- test/src/Convertor.spec.js | 45 ++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/test/src/Convertor.spec.js b/test/src/Convertor.spec.js index 5b005da..6310d75 100644 --- a/test/src/Convertor.spec.js +++ b/test/src/Convertor.spec.js @@ -17,6 +17,7 @@ const basic = require("../schemas/basic.json"); const invalidFieldOne = require("../schemas/invalidFields/invalidField.json"); // null property type const nullProperty = require("../schemas/nullProperties/nullProperty.json"); +const nullAndTypeProperty = require("../schemas/nullProperties/nullAndType.json"); // array types const arrayType = require("../schemas/arrayTypes/arrayType.json"); const arrayTypeWithNull = require("../schemas/arrayTypes/arrayTypeIncludingNull.json"); @@ -178,24 +179,24 @@ describe("Convertor", () => { 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); + it(`should set types as nullable when null is provided along with a type`, async function () { + const newConvertor = new Convertor(nullAndTypeProperty); const result = newConvertor.convert("basic"); + + expect(result.schemas.basic.properties.typedProperty).to.have.property( + "type" + ); + expect(result.schemas.basic.properties.typedProperty).to.have.property( + "type", + "string" + ); + expect(result.schemas.basic.properties.typedProperty).to.have.property( + "nullable" + ); 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); + result.schemas.basic.properties.typedProperty.nullable + ).to.be.equal(true); const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); Object.assign(cloned, { components: result }); @@ -205,9 +206,11 @@ describe("Convertor", () => { 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); + 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 @@ -220,13 +223,7 @@ describe("Convertor", () => { ).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); + ).to.be.equal(2); const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); Object.assign(cloned, { components: result }); From 96bc6d827cb4d938f4db71aad3f2a827e51b3ee0 Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Tue, 9 Jul 2024 13:06:35 +0100 Subject: [PATCH 4/7] add some extra anyOf mocks --- test/schemas/ofNulls/moreThanOneanyOf.json | 21 +++++++++++++++++++++ test/schemas/ofNulls/moreThanOneoneOf.json | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 test/schemas/ofNulls/moreThanOneanyOf.json create mode 100644 test/schemas/ofNulls/moreThanOneoneOf.json diff --git a/test/schemas/ofNulls/moreThanOneanyOf.json b/test/schemas/ofNulls/moreThanOneanyOf.json new file mode 100644 index 0000000..c496642 --- /dev/null +++ b/test/schemas/ofNulls/moreThanOneanyOf.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "JSON API Schema", + "description": "This is a schema for responses in the JSON API format. For more, see http://jsonapi.org", + "type": "object", + "properties": { + "payment": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + }, + { + "type": "integer" + } + ] + } + } +} diff --git a/test/schemas/ofNulls/moreThanOneoneOf.json b/test/schemas/ofNulls/moreThanOneoneOf.json new file mode 100644 index 0000000..3a0f3d5 --- /dev/null +++ b/test/schemas/ofNulls/moreThanOneoneOf.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "JSON API Schema", + "description": "This is a schema for responses in the JSON API format. For more, see http://jsonapi.org", + "type": "object", + "properties": { + "payment": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + }, + { + "type": "boolean" + } + ] + } + } +} From 4c6642ef54798a303caf62b5f90bd9a1df7859a8 Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Tue, 9 Jul 2024 13:06:49 +0100 Subject: [PATCH 5/7] update tests for nullable anyOf and oneOfs --- test/src/Convertor.spec.js | 90 ++++++++++++++++++++++++++++++++++---- 1 file changed, 81 insertions(+), 9 deletions(-) diff --git a/test/src/Convertor.spec.js b/test/src/Convertor.spec.js index 6310d75..1fbc470 100644 --- a/test/src/Convertor.spec.js +++ b/test/src/Convertor.spec.js @@ -56,6 +56,8 @@ const listOfBannedSchemas = require("../schemas/SchemasThatCannotBeConverted/lis // anyOf/oneOf Nulls const oneOfNull = require("../schemas/ofNulls/oneOfNull.json"); const anyOfNull = require("../schemas/ofNulls/anyOfNull.json"); +const moreThanoneOfNull = require("../schemas/ofNulls/moreThanOneoneOf.json"); +const moreThananyOfNull = require("../schemas/ofNulls/moreThanOneanyOf.json"); // anyOf/oneOf Nulls const allOfProperties = require("../schemas/propertiesOutsideOf/allOf.json"); const oneOfProperties = require("../schemas/propertiesOutsideOf/oneOf.json"); @@ -712,13 +714,18 @@ describe("Convertor", () => { 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( + + expect(result.schemas.basic.properties.payment).to.not.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); + expect(result.schemas.basic.properties.payment).to.have.property( + "type", + "string" + ); + expect(result.schemas.basic.properties.payment).to.have.property( + "nullable", + true + ); const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); Object.assign(cloned, { components: result }); @@ -732,13 +739,78 @@ describe("Convertor", () => { 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.not.have.property( + "oneOf" + ); + expect(result.schemas.basic.properties.payment).to.have.property( + "type", + "string" + ); + expect(result.schemas.basic.properties.payment).to.have.property( + "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 an anyOf with a type of null and more than one non null type", async function () { + const newConvertor = new Convertor(moreThananyOfNull); + const result = newConvertor.convert("basic"); + + expect(result.schemas.basic.properties.payment).to.have.property( + "anyOf" + ); + expect(result.schemas.basic.properties.payment.anyOf).to.have.lengthOf( + 2 + ); + const stringAnyOf = + result.schemas.basic.properties.payment.anyOf.filter( + (schema) => schema.type === "string" + ); + expect(stringAnyOf[0]).to.have.property("nullable", true); + + const integerAnyOf = + result.schemas.basic.properties.payment.anyOf.filter( + (schema) => schema.type === "integer" + ); + expect(integerAnyOf[0]).to.have.property("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 a oneOf with a type of null and more than one non null type", async function () { + const newConvertor = new Convertor(moreThanoneOfNull); + 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.oneOf).to.have.lengthOf( + 2 + ); + const stringOneOf = + result.schemas.basic.properties.payment.oneOf.filter( + (schema) => schema.type === "string" + ); + expect(stringOneOf[0]).to.have.property("nullable", true); + + const booleanOneOf = + result.schemas.basic.properties.payment.oneOf.filter( + (schema) => schema.type === "boolean" + ); + expect(booleanOneOf[0]).to.have.property("nullable", true); const cloned = JSON.parse(JSON.stringify(basicOpenAPI)); Object.assign(cloned, { components: result }); From bf7fd9705f98b3819c8ad47e9f0180d134e75f2f Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Tue, 9 Jul 2024 13:07:08 +0100 Subject: [PATCH 6/7] deal with oneOf and anyOf's that are nullable --- src/Convertor.js | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/Convertor.js b/src/Convertor.js index 8672c12..f572d7a 100644 --- a/src/Convertor.js +++ b/src/Convertor.js @@ -481,19 +481,25 @@ class Convertor { }); if (hasNullType) { - schemaOf.forEach((obj) => { - if (obj.type !== "null") { - obj.nullable = true; - } - }); - const newOf = schemaOf.filter((obj) => { - if (obj.type !== "null") return obj; - }); + const nullableSchemasFiltered = schemaOf + .filter((schemaContainignNull) => { + if (schemaContainignNull.type !== "null") { + return schemaContainignNull; + } + }) + .map((schemasNotContainingNull) => { + schemasNotContainingNull.nullable = true; + return schemasNotContainingNull; + }); - if (isOneOf) { - schema.oneOf = newOf; + if (nullableSchemasFiltered.length > 1) { + if (isOneOf) schema.oneOf = nullableSchemasFiltered; + else schema.anyOf = nullableSchemasFiltered; } else { - schema.anyOf = newOf; + if (isOneOf) delete schema.oneOf; + else delete schema.anyOf; + + Object.assign(schema, nullableSchemasFiltered[0]); } } } From 402f65efbd039cbd818eb324024c490557aab873 Mon Sep 17 00:00:00 2001 From: Jared Evans Date: Tue, 9 Jul 2024 13:07:26 +0100 Subject: [PATCH 7/7] 0.5.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 c4f9d72..85649d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "json-schema-for-openapi", - "version": "0.4.3", + "version": "0.5.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "json-schema-for-openapi", - "version": "0.4.3", + "version": "0.5.0", "license": "Apache-2.0", "dependencies": { "json-schema-traverse": "^1.0.0", diff --git a/package.json b/package.json index cd87fc0..d33d8e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json-schema-for-openapi", - "version": "0.4.3", + "version": "0.5.0", "description": "Converts a regular JSON Schema to a compatible OpenAPI 3.0.X Schema Object", "keywords": [ "json",