From 2105b058135cab5401d2d7c9dd215f4dc55dfc9e Mon Sep 17 00:00:00 2001 From: Jacob Ley Date: Fri, 25 Mar 2022 17:44:09 -0400 Subject: [PATCH 1/4] Add failing test for integer subschema narrowing --- .../1935_integer_narrowing_subschema.spec.ts | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 spec/issues/1935_integer_narrowing_subschema.spec.ts diff --git a/spec/issues/1935_integer_narrowing_subschema.spec.ts b/spec/issues/1935_integer_narrowing_subschema.spec.ts new file mode 100644 index 000000000..93fcd90f7 --- /dev/null +++ b/spec/issues/1935_integer_narrowing_subschema.spec.ts @@ -0,0 +1,103 @@ +import _Ajv from "../ajv" +import Ajv from "ajv" + +describe("issue 1935: integer valid type in number sub-schema", () => { + let ajv: Ajv + before(() => { + ajv = new _Ajv({strict: true}) + }) + + it("should allow integer in `if`", () => { + ajv.compile({ + type: "number", + if: { + type: "integer", + maximum: 5, + }, + else: { + minimum: 10, + }, + }) + }) + + it("should allow integer in `then`", () => { + ajv.compile({ + type: "number", + if: { + multipleOf: 2, + }, + then: { + type: "integer", + minimum: 10, + }, + }) + }) + + it("should allow integer in `else`", () => { + ajv.compile({ + type: "number", + if: { + maximum: 5, + }, + else: { + type: "integer", + minimum: 10, + }, + }) + }) + + it("should allow integer in `allOf`", () => { + ajv.compile({ + type: "number", + allOf: [ + { + type: "integer", + minimum: 10, + }, + { + multipleOf: 2, + }, + ], + }) + }) + + it("should allow integer in `oneOf`", () => { + ajv.compile({ + type: "number", + oneOf: [ + { + type: "integer", + minimum: 10, + }, + { + multipleOf: 2, + }, + ], + }) + }) + + it("should allow integer in `anyOf`", () => { + ajv.compile({ + type: "number", + oneOf: [ + { + type: "integer", + minimum: 10, + }, + { + multipleOf: 2, + }, + ], + }) + }) + + it("should allow integer in `not`", () => { + ajv.compile({ + type: "number", + not: { + type: "integer", + minimum: 10, + }, + }) + }) +}) From 1d664d939f7047392885f15cf15135f2b0b55f3b Mon Sep 17 00:00:00 2001 From: Jacob Ley Date: Fri, 25 Mar 2022 17:45:10 -0400 Subject: [PATCH 2/4] Add number to includesType check for context types --- lib/compile/validate/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compile/validate/index.ts b/lib/compile/validate/index.ts index f5910c3a3..4b751460a 100644 --- a/lib/compile/validate/index.ts +++ b/lib/compile/validate/index.ts @@ -286,7 +286,7 @@ function checkContextTypes(it: SchemaObjCxt, types: JSONType[]): void { strictTypesError(it, `type "${t}" not allowed by context "${it.dataTypes.join(",")}"`) } }) - it.dataTypes = it.dataTypes.filter((t) => includesType(types, t)) + it.dataTypes = it.dataTypes.filter((t) => includesType([...types, "number"], t)) } function checkMultipleTypes(it: SchemaObjCxt, ts: JSONType[]): void { From ffbc2099d459ba979a38573ee6a29003966344c7 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Mon, 2 Jan 2023 23:37:40 +0000 Subject: [PATCH 3/4] narrow number to integer correctly --- lib/compile/validate/index.ts | 11 +- .../1935_integer_narrowing_subschema.spec.ts | 170 +++++++++--------- 2 files changed, 99 insertions(+), 82 deletions(-) diff --git a/lib/compile/validate/index.ts b/lib/compile/validate/index.ts index 4b751460a..e3f9b5e67 100644 --- a/lib/compile/validate/index.ts +++ b/lib/compile/validate/index.ts @@ -286,7 +286,7 @@ function checkContextTypes(it: SchemaObjCxt, types: JSONType[]): void { strictTypesError(it, `type "${t}" not allowed by context "${it.dataTypes.join(",")}"`) } }) - it.dataTypes = it.dataTypes.filter((t) => includesType([...types, "number"], t)) + narrowSchemaTypes(it, types) } function checkMultipleTypes(it: SchemaObjCxt, ts: JSONType[]): void { @@ -316,6 +316,15 @@ function includesType(ts: JSONType[], t: JSONType): boolean { return ts.includes(t) || (t === "integer" && ts.includes("number")) } +function narrowSchemaTypes(it: SchemaObjCxt, withTypes: JSONType[]) { + const ts: JSONType[] = [] + for (const t of it.dataTypes) { + if (includesType(withTypes, t)) ts.push(t) + else if (withTypes.includes("integer") && t == "number") ts.push("integer") + } + it.dataTypes = ts +} + function strictTypesError(it: SchemaObjCxt, msg: string): void { const schemaPath = it.schemaEnv.baseId + it.errSchemaPath msg += ` at "${schemaPath}" (strictTypes)` diff --git a/spec/issues/1935_integer_narrowing_subschema.spec.ts b/spec/issues/1935_integer_narrowing_subschema.spec.ts index 93fcd90f7..fc24e987b 100644 --- a/spec/issues/1935_integer_narrowing_subschema.spec.ts +++ b/spec/issues/1935_integer_narrowing_subschema.spec.ts @@ -1,103 +1,111 @@ import _Ajv from "../ajv" import Ajv from "ajv" +import * as assert from "assert" -describe("issue 1935: integer valid type in number sub-schema", () => { +describe("integer valid type in number sub-schema (issue #1935)", () => { let ajv: Ajv before(() => { ajv = new _Ajv({strict: true}) }) - it("should allow integer in `if`", () => { - ajv.compile({ - type: "number", - if: { - type: "integer", - maximum: 5, - }, - else: { - minimum: 10, - }, - }) - }) - - it("should allow integer in `then`", () => { - ajv.compile({ - type: "number", - if: { - multipleOf: 2, - }, - then: { - type: "integer", - minimum: 10, - }, - }) - }) - - it("should allow integer in `else`", () => { - ajv.compile({ - type: "number", - if: { - maximum: 5, - }, - else: { - type: "integer", - minimum: 10, - }, - }) - }) - - it("should allow integer in `allOf`", () => { - ajv.compile({ - type: "number", - allOf: [ - { + it("should allow integer in `if`", () => + assert.doesNotThrow(() => + ajv.compile({ + type: "number", + if: { type: "integer", + maximum: 5, + }, + else: { minimum: 10, }, - { + }) + )) + + it("should allow integer in `then`", () => + assert.doesNotThrow(() => + ajv.compile({ + type: "number", + if: { multipleOf: 2, }, - ], - }) - }) - - it("should allow integer in `oneOf`", () => { - ajv.compile({ - type: "number", - oneOf: [ - { + then: { type: "integer", minimum: 10, }, - { - multipleOf: 2, - }, - ], - }) - }) + }) + )) - it("should allow integer in `anyOf`", () => { - ajv.compile({ - type: "number", - oneOf: [ - { + it("should allow integer in `else`", () => + assert.doesNotThrow(() => + ajv.compile({ + type: "number", + if: { + maximum: 5, + }, + else: { type: "integer", minimum: 10, }, - { - multipleOf: 2, - }, - ], - }) - }) + }) + )) - it("should allow integer in `not`", () => { - ajv.compile({ - type: "number", - not: { - type: "integer", - minimum: 10, - }, - }) - }) + it("should allow integer in `allOf`", () => + assert.doesNotThrow(() => + ajv.compile({ + type: "number", + allOf: [ + { + type: "integer", + minimum: 10, + }, + { + multipleOf: 2, + }, + ], + }) + )) + + it("should allow integer in `oneOf`", () => + assert.doesNotThrow(() => + ajv.compile({ + type: "number", + oneOf: [ + { + type: "integer", + minimum: 10, + }, + { + multipleOf: 2, + }, + ], + }) + )) + + it("should allow integer in `anyOf`", () => + assert.doesNotThrow(() => + ajv.compile({ + type: "number", + oneOf: [ + { + type: "integer", + minimum: 10, + }, + { + multipleOf: 2, + }, + ], + }) + )) + + it("should allow integer in `not`", () => + assert.doesNotThrow(() => + ajv.compile({ + type: "number", + not: { + type: "integer", + minimum: 10, + }, + }) + )) }) From 0bf0f48e4ee617eb10028c28843c166b15227561 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Mon, 2 Jan 2023 23:42:51 +0000 Subject: [PATCH 4/4] fix lint errors --- lib/compile/validate/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/compile/validate/index.ts b/lib/compile/validate/index.ts index e3f9b5e67..15ecabd85 100644 --- a/lib/compile/validate/index.ts +++ b/lib/compile/validate/index.ts @@ -316,11 +316,11 @@ function includesType(ts: JSONType[], t: JSONType): boolean { return ts.includes(t) || (t === "integer" && ts.includes("number")) } -function narrowSchemaTypes(it: SchemaObjCxt, withTypes: JSONType[]) { +function narrowSchemaTypes(it: SchemaObjCxt, withTypes: JSONType[]): void { const ts: JSONType[] = [] for (const t of it.dataTypes) { if (includesType(withTypes, t)) ts.push(t) - else if (withTypes.includes("integer") && t == "number") ts.push("integer") + else if (withTypes.includes("integer") && t === "number") ts.push("integer") } it.dataTypes = ts }