Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
MisterD123 opened this issue Jul 1, 2021 · 3 comments
Closed

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

MisterD123 opened this issue Jul 1, 2021 · 3 comments

Comments

@MisterD123
Copy link

MisterD123 commented Jul 1, 2021

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.

@epoberezkin
Copy link
Member

I can reproduce, good catch - thanks. It is a regression from v6.

@epoberezkin
Copy link
Member

epoberezkin commented Jul 4, 2021

it's actually a wider issue that would affect any keyword that is validated after "not" - that is any keyword that is applicable to all types (which, luckily, does NOT include "properties", "items", etc., but also includes if/then/else and any user defined keyword not restricted to a particular type)

@epoberezkin
Copy link
Member

fixed in v8.6.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

2 participants