ajv icon indicating copy to clipboard operation
ajv copied to clipboard

option compile from addKeyword doesn't change on a remove and then add again

Open rgpro007 opened this issue 1 year ago • 1 comments

Here my issue (if it's one):

I setup a keyword with addKeyword and the compile option. I have some external variables used inside. At the first iteration everything is ok, but at the second one we see the variables doesn't change. Is it a normal behavior or an issue ?

What version of Ajv are you using?

version 8.12.0

Your code

const AjvModule = require('ajv/dist/2020');
const express = require('express');

const ajv = new AjvModule();
const schemaName = 'schemaTest';
const keyword = "variableKeywork";

ajv.addSchema({
    "description": "test",
    "type": "object",
    "properties": {
        "item1": {
            "type": "string"
        },
        "item2": {
            "type": "integer"
        }
    },
    "variableKeywork": true
}, schemaName);


const app = express();
const desc = {
    item1: "test",
    item2: 1
}

app.get(`/test`, (req, res) => {
    desc.item1 = req.query.param;
    const time = new Date();
    console.log(`Date outside addKeyword: ${time}`);
    console.log(`sentTo2 outside addKeyword : ${req.query.sentTo2}`);
    console.log(`keyword present : ${JSON.stringify(ajv.getKeyword(keyword))}`);
    ajv.addKeyword({
        keyword: keyword,
        modifying: true,
        compile: (value) => function validate(data, root) {
            console.log(`Date inside addKeyword: ${time}`);
            console.log(`sentTo2 inside addKeyword : ${req.query.sentTo2}`);
            if (Boolean(req.query.sentTo2)) {
                root.rootData.item2 = req.query.param;
            }
            console.log(JSON.stringify(root.rootData))
            return true
        }
    });
    console.log(`keyword present : ${JSON.stringify(ajv.getKeyword(keyword))}`);
    const isDataValid = ajv.validate(schemaName, desc);
    ajv.removeKeyword(keyword);
    console.log(`keyword present : ${JSON.stringify(ajv.getKeyword(keyword))}`);
    res.json(isDataValid);
});

app.listen(8080, function () {
    console.log("serveur started");
});

// try with http://localhost:8080/test?param=1&sentTo2=true
// and then  http://localhost:8080/test?param=1&sentTo2=false

Output obtained

serveur started
Date outside addKeyword: Mon Apr 08 2024 17:24:53 GMT+0200 (Central European Summer Time)
sentTo2 outside addKeyword : true
keyword present : false
keyword present : {"keyword":"variableKeywork","modifying":true,"type":[],"schemaType":[]}
Date inside addKeyword: Mon Apr 08 2024 17:24:53 GMT+0200 (Central European Summer Time)
sentTo2 inside addKeyword : true
{"item1":"1","item2":"1"}
keyword present : false


Date outside addKeyword: Mon Apr 08 2024 17:25:18 GMT+0200 (Central European Summer Time)
sentTo2 outside addKeyword : false
keyword present : false
keyword present : {"keyword":"variableKeywork","modifying":true,"type":[],"schemaType":[]}
Date inside addKeyword: Mon Apr 08 2024 17:24:53 GMT+0200 (Central European Summer Time)
sentTo2 inside addKeyword : true
{"item1":"1","item2":"1"}
keyword present : false

What results did you expect?

the ajv.removeKeyword really remove the keyword and allow us the define it again.

Are you going to resolve the issue?

rgpro007 avatar Apr 08 '24 15:04 rgpro007

Can you please produce a minimal example using this runkit template? It should be possible to reproduce the issue without express.

jasoniangreen avatar May 14 '24 22:05 jasoniangreen

Hello, I have updated the example with a runkit template : runkit example

rgpro007 avatar May 29 '24 07:05 rgpro007

I have looked into this and I have been able to replicate with an even simpler example. https://runkit.com/jasoniangreen/addkeyword-bug-2411

jasoniangreen avatar Jun 07 '24 21:06 jasoniangreen

I got to the bottom of the issue @rgpro007. Here you can see my minimal example that reproduces the issue and then fixes it. The reason you are hitting this issue is that for performance reasons, AJV caches a lot of things. In this case you have a cached schema which has already been compiled. When you try to compile the same schema again, it skips certain steps to save time.

The simple solution is to use ajv.removeSchema (see line 21) before you try and compile it again. Alternatively you could clone the schema {...schema} which would also work because the cache uses a WeakMap which recognises the schema by reference.

jasoniangreen avatar Jun 08 '24 13:06 jasoniangreen

After reviewing the docs I am confident this is how it is intended to be and the docs are clear enough on how you should only compile schemas once. So I will close this one and leave you with two links:

  • https://ajv.js.org/guide/managing-schemas.html#cache-key-schema-vs-key-vs-id
  • https://ajv.js.org/guide/managing-schemas.html#caching-schemas-in-your-code

jasoniangreen avatar Jun 08 '24 14:06 jasoniangreen

Thank for you help. I will modify my code.

rgpro007 avatar Jun 10 '24 08:06 rgpro007