joi icon indicating copy to clipboard operation
joi copied to clipboard

unknown(false) is not work when configuration "allowUnknown" is true

Open LuEason opened this issue 1 year ago • 10 comments

Support plan

  • is this issue currently blocking your project? (yes/no): yes
  • is this issue affecting a production system? (yes/no): yes

Context

  • node version: 14.20.0
  • module version: 17.6.0
  • environment (e.g. node, browser, native): node
  • used with (e.g. hapi application, another framework, standalone, ...): node
  • any other relevant information:

How can we help?

Our project use version 14 before, we want to allow all fields of all object except for some fields we don't expect For example, we want to allow field "e" of data.d only, and allow other unknown fields for data.b and even data, we use below code:

const joi = require('joi');

const schema = joi.object().keys({
  a: joi.string(),
  b: joi.object().keys({
    c: joi.string()
  }),
  d: joi.object().keys({
    e: joi.string()
  }).unknown(false)
});

const value = {
  a: '123',
  b: {
    c: '123',
    f: 123
  },
  d: {
    e: '12',
    g: 123
  },
  h: 123
};

const result = schema.validate(value, {
  abortEarly: false,
  allowUnknown: true
});

console.log(result?.error?.message);
// child "d" fails because ["g" is not allowed]

After we use version 17, the data pass the validation, which we do not expect.

Do you have any suggestions?

LuEason avatar Aug 23 '22 07:08 LuEason

It looks like a bug. In the meantime, you can replace .unknown(false) by .options({ allowUnknown: false }), just beware that it's inherited by children schemas.

Marsup avatar Aug 23 '22 08:08 Marsup

Thanks Marsup. By the way, I look up the source code and found where is this error caused. It is in /lib/base.js.

$_setFlag(name, value, options = {}) {

        Assert(name[0] === '_' || !this._inRuleset(), 'Cannot set flag inside a ruleset');

        const flag = this._definition.flags[name] || {};
        if (DeepEqual(value, flag.default)) {
            value = undefined;
        }

        if (DeepEqual(value, this._flags[name])) {
            return this;
        }
}

It is so weird to set the value to undefined when value same as the default value of flag. But once I delete this if statement, many tests failed. Please help have a check.

LuEason avatar Aug 24 '22 07:08 LuEason

Hi folks. Could I work on that issue?

geeksilva97 avatar Sep 30 '22 13:09 geeksilva97

It's not the easiest one but sure.

Marsup avatar Sep 30 '22 14:09 Marsup

Thanks @Marsup .

geeksilva97 avatar Oct 05 '22 01:10 geeksilva97

Hi, @LuEason I tested this code you sent using Joi 17.6.1 and it looks like it works as you expect.

It doesn't have any errors. When I print the result I get the following:

{
  value: { a: '123', b: { c: '123', f: 123 }, d: { e: '12', g: 123 }, h: 123 }
}

Maybe it was already fixed @Marsup .

geeksilva97 avatar Oct 05 '22 01:10 geeksilva97

@geeksilva97 that's precisely the problem, it should fail on object d because g is not allowed.

Marsup avatar Oct 11 '22 09:10 Marsup

@marsup

It looks like a bug

This is expected:

from the docs

allowUnknown - when true, allows object to contain unknown keys which are ignored. Defaults to false.

allowUknown will override any .unknown(false) settings you have

const schema = joi.object().keys({
    foo: joi.object().keys({
        bar: joi.string()
    }).unknown(false)
})

console.log(
    schema.validate({
        foo: {
            bar: "bar",
            baaz: "baaz"
        }
    })
) // "foo.baaz" is not allowed

console.log(
    schema.validate({
        foo: {
            bar: "bar",
            baaz: "baaz"
        }
    }, {allowUnknown: true})
) // valid

@LuEason

you can turn on this feature by not passing allowUnknown: true and instead add granular uknown(false) entries to the your desired keys

kamweti avatar Nov 15 '22 14:11 kamweti

Local should always override global, it is imo a bug.

Marsup avatar Nov 15 '22 14:11 Marsup

fair enough, i now see this is what happens in the yup lib as well

const schema = yup.object().shape({
    foo: yup.object({
        bar: yup.string()
    }).noUnknown(true)
})

console.log(
    schema.validate({
        foo: {
            bar: "bar",
            baaz: "baaz"
        }
    }, {stripUnknown: false})
) // foo field has unspecified keys: baaz

kamweti avatar Nov 15 '22 15:11 kamweti