ajv-validator / ajv

The fastest JSON schema Validator. Supports JSON Schema draft-04/06/07/2019-09/2020-12 and JSON Type Definition (RFC8927)
https://ajv.js.org
MIT License
13.57k stars 868 forks source link

contains with maxContains and minContains not behaving as expected #2461

Open jamesbmorris92 opened 2 weeks ago

jamesbmorris92 commented 2 weeks ago

What version of Ajv are you using? Does the issue happen if you use the latest version? "version": "8.12.0",

Ajv options object

import AJV from "ajv/dist/2019";

    const ajv = new AJV({
        $data: true,
        useDefaults: true,
        allErrors: true,
        verbose: true,
        strict: false
    })

JSON Schema

        "testFields": {
                    "type": "array",
                    "minItems": 1,
                    "contains": {
                      "properties": {
                        "lastUpdatedAtTime": {
                          "const": true
                        }
                      }
                    },
                    "minContains": 1,
                    "maxContains": 1,
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "description": "",
                          "type": "string"
                        },
                        "lastUpdatedAtTime": {
                          "description": "Whether the field is a 'Date-time modified' field.",
                          "type": "boolean",
                          "default": false
                        }
                      },
                      "additionalProperties": false
                    },
                    "uniqueItemProperties": [
                      "name"
                    ]
                  }

Sample data

[
    {
        "lastUpdatedAtTime": true,
        "name": "test1"
    },
    {
        "lastUpdatedAtTime": false,
        "name": "test2"
    },
    {
        "lastUpdatedAtTime": false,
        "name": "test3"
    },
    {
        "lastUpdatedAtTime": true,
        "name": "test4"
    }
]

Your code

    const validate = ajv.compile(jsonSchema)
    validate(data);

Validation result, data AFTER validation, error messages

[
    {
        "instancePath": "/solutionFactoryConfiguration/viObjects/entities/1/testFields/1/lastUpdatedAtTime",
        "schemaPath": "#/properties/solutionFactoryConfiguration/properties/viObjects/properties/entities/items/properties/testFields/contains/properties/lastUpdatedAtTime/const",
        "keyword": "const",
        "params": {
            "allowedValue": true
        },
        "message": "must be equal to constant",
        "schema": true,
        "parentSchema": {
            "const": true
        },
        "data": false
    },
    {
        "instancePath": "/solutionFactoryConfiguration/viObjects/entities/1/testFields/2/lastUpdatedAtTime",
        "schemaPath": "#/properties/solutionFactoryConfiguration/properties/viObjects/properties/entities/items/properties/testFields/contains/properties/lastUpdatedAtTime/const",
        "keyword": "const",
        "params": {
            "allowedValue": true
        },
        "message": "must be equal to constant",
        "schema": true,
        "parentSchema": {
            "const": true
        },
        "data": false
    },
    {
        "instancePath": "/solutionFactoryConfiguration/viObjects/entities/1/testFields",
        "schemaPath": "#/properties/solutionFactoryConfiguration/properties/viObjects/properties/entities/items/properties/testFields/contains",
        "keyword": "contains",
        "params": {
            "minContains": 1,
            "maxContains": 1
        },
        "message": "must contain at least 1 and no more than 1 valid item(s)",
        "schema": {
            "properties": {
                "lastUpdatedAtTime": {
                    "const": true
                }
            }
        },
        "parentSchema": {
            "type": "array",
            "minItems": 1,
            "contains": {
                "properties": {
                    "lastUpdatedAtTime": {
                        "const": true
                    }
                }
            },
            "minContains": 1,
            "maxContains": 1,
            "items": {
                "type": "object",
                "properties": {
                    "name": {
                        "description": "",
                        "type": "string"
                    },
                    "lastUpdatedAtTime": {
                        "description": "Whether the field is a 'Date-time modified' field.",
                        "type": "boolean",
                        "default": false
                    }
                },
                "additionalProperties": false
            },
            "uniqueItemProperties": [
                "name"
            ]
        },
        "data": [
            {
                "name": "test1",
                "lastUpdatedAtTime": true
            },
            {
                "name": "test2",
                "lastUpdatedAtTime": false
            },
            {
                "name": "test3",
                "lastUpdatedAtTime": false
            },
            {
                "name": "test4",
                "lastUpdatedAtTime": true
            }
        ]
    }
]

What results did you expect? I got three errors where I expected just one. I expected the third error as a result of using contains with maxContains and minContains because I had more than one row of test data where lastUpdatedAtTime: true. However I didn't expect the first two errors that say the lastUpdatedAtTime must be equal to constant and the only permitted value is true.

Are you going to resolve the issue? I would like to but i'm not sure how. Apologies if i've made any mistakes or missed anything above this is my first issue submission.

jasoniangreen commented 2 weeks ago

Hi @jamesbmorris92, just to help me investigate, could you setup an example of this in runkit?

jamesbmorris92 commented 2 weeks ago

Hi @jasoniangreen here's an example: https://runkit.com/jamesbmorris92/6682b96af2a88c000808ae0e

jasoniangreen commented 2 weeks ago

Hi @jasoniangreen here's an example: https://runkit.com/jamesbmorris92/6682b96af2a88c000808ae0e

Thanks @jamesbmorris92, I will look into this, but in the meantime, I have a question. It's quite a complex / deeply nested example, do you think the example can be simplified further while still exhibiting the problem?

jamesbmorris92 commented 2 weeks ago

@jasoniangreen my apologies, i've made the example a bit more simple with less nesting: https://runkit.com/jamesbmorris92/6682b96af2a88c000808ae0e

jamesbmorris92 commented 4 days ago

hi @jasoniangreen were you able to recreate the issue?

jasoniangreen commented 3 days ago

I can see what you report, I've just got to dig more into the docs to understand what should be expected. There are a lot of cases in AJV and JSON Schema where you will legitimately get more errors than you expect, I'm just trying to understand if this is one of those situations.

jasoniangreen commented 3 days ago

Ah @jamesbmorris92 if you remove the allErrors: true option, it get's the expected result of just the contains error. Does this solve your issue? All errors true means that AJV will continue to check your data after encountering the first error, which seems to be the case here.