-
-
Notifications
You must be signed in to change notification settings - Fork 910
Description
What version of Ajv are you using? Does the issue happen if you use the latest version?
tested on 8.1.0 and 8.6.0 (latest at time of posting)
Ajv options object
default options
JSON Schema
{
"anyOf": [{"const": 1}],
"not": {"const": true},
}
using oneOf instead of anyOf produces the same issue:
{
"oneOf": [{"const": 1}],
"not": {"const": true},
}
Sample data
3
Your code
const ajv = new (require("ajv").default)()
console.log(ajv.compile({
"anyOf": [{"const": 1}],
"not": {"const": true},
}(3))
console.log(ajv.compile({
"oneOf": [{"const": 1}],
"not": {"const": true},
}(3))
Validation result, data AFTER validation, error messages
validation returns true
in both cases, data unchanged, no error messages
What results did you expect?
validation should return false
, because 3
does does not match {anyOf: ["const": 1]}
(or {allOf: ["const": 1]}
respectively).
Note that without an anyOf/oneOf wrapper, the const schema is correctly evaluated:
ajv.compile({
{"const": 1},
"not": {"const": true},
}(3)) === false
And vice versa, removing the not
condition results in anyOf and oneOf being evaluated correctly:
ajv.compile({
"anyOf": [{"const": 1}],
}(3)) === false
ajv.compile({
"oneOf": [{"const": 1}],
}(3)) === false
Are you going to resolve the issue?
I'll try to take a look, but I have no idea of AJV code generation yet, so not sure if I can figure this out in reasonable time. From what I've seen, it looks like the code for anyOf/allOf gets generated correctly, but is inserted as dead code after the code generated by not, because the closing parenthesis of the not failure case is misplaced:
(function anonymous(self, scope
) {
const schema11 = scope.schema[6]
return function validate10(data, {instancePath = "",parentData,parentDataProperty,rootData = data} = {}) {
let vErrors = null
let errors = 0
const _errs0 = errors
const _errs1 = errors
if (true !== data) {
const err0 = {}
if (vErrors === null) {
vErrors = [err0]
} else {
vErrors.push(err0)
}
errors++
}
var valid0 = _errs1 === errors
if (!valid0) {
errors = _errs0
if (vErrors !== null) {
if (_errs0) {
vErrors.length = _errs0
} else {
vErrors = null
}
}
} else { // ====== MARK FOR REFERENCE: opening parenthesis here
validate10.errors = [{instancePath,schemaPath: "#/not",keyword: "not",params: {},message: "must NOT be valid"}]
return false
// ======================= DEAD CODE FOR ANYOF STARTS HERE =======================
const _errs2 = errors
let valid1 = false
const _errs3 = errors
if (2 !== data) {
const err1 = {instancePath,schemaPath: "#/anyOf/0/const",keyword: "const",params: {allowedValue: 2},message: "must be equal to constant"}
if (vErrors === null) {
vErrors = [err1]
} else {
vErrors.push(err1)
}
errors++
}
var _valid0 = _errs3 === errors
valid1 = valid1 || _valid0
if (!valid1) {
const err2 = {instancePath,schemaPath: "#/anyOf",keyword: "anyOf",params: {},message: "must match a schema in anyOf"}
if (vErrors === null) {
vErrors = [err2]
} else {
vErrors.push(err2)
}
errors++
validate10.errors = vErrors
return false
} else {
errors = _errs2
if (vErrors !== null) {
if (_errs2) {
vErrors.length = _errs2
} else {
vErrors = null
}
}
}
} // ====== MARK FOR REFERENCE: closing parenthesis here
validate10.errors = vErrors
return errors === 0
}
})
Note I have marked the opening and closing parenthesis of the not failure case. If the closing parenthesis is moved to where the dead code for anyof begins, i.e., if the code for anyOf is moved out of the not failure case, the validation function behaves correctly.