Skip to content

anyOf and allOf get ignored in the presence of not #1668

@MisterD123

Description

@MisterD123

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions