From a4892653055d1bccb58a739825caac466d86da3a Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Mon, 2 Jan 2023 23:48:55 +0000 Subject: [PATCH] correctly narrow "number" type to "integer", fixes #1935 (#2192) * Add failing test for integer subschema narrowing * Add number to includesType check for context types * narrow number to integer correctly * fix lint errors Co-authored-by: Jacob Ley --- lib/compile/validate/index.ts | 11 +- .../1935_integer_narrowing_subschema.spec.ts | 111 ++++++++++++++++++ 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 spec/issues/1935_integer_narrowing_subschema.spec.ts diff --git a/lib/compile/validate/index.ts b/lib/compile/validate/index.ts index f5910c3a3..15ecabd85 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)) + 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[]): 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") + } + 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 new file mode 100644 index 000000000..fc24e987b --- /dev/null +++ b/spec/issues/1935_integer_narrowing_subschema.spec.ts @@ -0,0 +1,111 @@ +import _Ajv from "../ajv" +import Ajv from "ajv" +import * as assert from "assert" + +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`", () => + 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, + }, + then: { + type: "integer", + minimum: 10, + }, + }) + )) + + it("should allow integer in `else`", () => + assert.doesNotThrow(() => + ajv.compile({ + type: "number", + if: { + maximum: 5, + }, + else: { + 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, + }, + }) + )) +})