hapijs / joi

The most powerful data validation library for JS
Other
20.89k stars 1.51k forks source link

fix: `stripUnknown` should honor local explicit `.unknown(false)` #3037

Closed afharo closed 3 months ago

afharo commented 4 months ago

When explicitly specifying .unknown(false), running validations with stripUnknown: true should respect the local override.

Marsup commented 3 months ago

Thanks, sorry for the delay.

papandreou commented 3 months ago

This change seems to break a construct that I'm using in a lot of places. When I want to allow an object to contain additional properties, but have Joi strip them out, I have:

const Joi = require('joi');
const schema = Joi.object({ foo: Joi.string()} )
  .unknown(false)
  .options({ stripUnknown:true });

console.log(schema.validate({foo: 'abc', bar: 'def'}));

With this change the validation fails:

{
  value: { foo: 'abc', bar: 'def' },
  error: [Error [ValidationError]: "bar" is not allowed] {
    _original: { foo: 'abc', bar: 'def' },
    details: [ [Object] ]
  }
}

... whereas before it succeeded and stripped out bar from the value:

{ value: { foo: 'abc' } }

I guess the reason why I'm using that construct is that I've liberally added .unknown(false).options({ stripUnknown: true }) to the top-level Joi.object schemas, because I've run into the problem that this is fixing 😅

I'll adapt my code, but this change probably shouldn't have gone out in a patch.

Marsup commented 3 months ago

I feared this would happen, but your schema is a bit self-contradictory, explicitly denying unknowns with the expectation that it would be stripped is in my opinion a bug, as local overrides should always have priority over options. I need to think about it a bit more before reverting (or not), but it really looks like you just got lucky that this bug was in your favor.

papandreou commented 3 months ago

Yeah, I agree. I think it ended up like that because (a few years back) I found that it was the only way to get Joi.object to work the way I needed it to :sweat_smile:

I've adopted the new version in all our projects now, so all good from me.

Alexander-Muylaert commented 1 month ago

Hi there

I think the stripUnknown is broken since this fix. I have gone through the commit and I think the tests are wrong.

  1. It would be good to add a root level property "a2" on the object.
  2. StripUnknown is set to true, so I would expect to strip unknown properties from obj.
  3. b2 isn't existing in the schema, so it should be stripped
  4. same for c2, this should also be stripped

image

I have added a few additional tests (see zip attached), they all fail now, but this is how I interprete the manual and parameters. It is also closer to behavior <= 17.13.1

joibug.test.zip

kind regards

Alexander

afharo commented 1 month ago

Just sharing my 2 cents, but I'll leave it to the owners to keep me honest.

  1. Agreed! The tests are missing an extra root property to prove that it's removed due to the global stripUnknown: true option.

However, I tend to disagree on 3 and 4. Those objects explicitly allow extra properties to go through. The global option shouldn't affect those explicitly set flags.

A use case I can think of: A logger requires message and a meta object containing any additional context to the logs. Inside the meta, meta.trace_id is required to be an alphanum of 16 chars.

You want your schema to define message as string, and meta as an object with the property trace_id and an explicit local .unknown() to allow extra fields to go through the validation.

I hope it makes sense.

Marsup commented 1 month ago

I agree with this, as I previously mentioned, local overrides should always have priority over options, and this is what happens here, but we can indeed add a root property to demonstrate that fully.

Alexander-Muylaert commented 1 month ago

Hi all

I disagree, I do not understand the use of stripUnknown if it never strips unknown properties. Neither with unknown(true) or unknown(false). Am I missing something obvious here?

kind regards

Alexander

Marsup commented 1 month ago

It does when you don't give specific instructions for that schema, basically the default state. There is currently no way to go back to the default state but I think a unknown(null) is not super hard to implement if that's your need.