Skip to content

Commit

Permalink
correctly narrow "number" type to "integer", fixes #1935 (#2192)
Browse files Browse the repository at this point in the history
* 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 <jacob@getshowcase.io>
  • Loading branch information
epoberezkin and JacobLey committed Jan 2, 2023
1 parent a211e8d commit a489265
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 1 deletion.
11 changes: 10 additions & 1 deletion lib/compile/validate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)`
Expand Down
111 changes: 111 additions & 0 deletions spec/issues/1935_integer_narrowing_subschema.spec.ts
Original file line number Diff line number Diff line change
@@ -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,
},
})
))
})

0 comments on commit a489265

Please sign in to comment.