From f399e3c921ae6a95964cea2bafcfd1aa05dc27ad Mon Sep 17 00:00:00 2001 From: Josh Date: Wed, 16 Feb 2022 13:33:26 -0500 Subject: [PATCH] WIP: Better unknown handling configuration --- lib/index.d.ts | 7 ++++++- lib/types/keys.js | 18 +++++++++++++++--- test/validator.js | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/lib/index.d.ts b/lib/index.d.ts index d6218c660..e3f662858 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -30,6 +30,8 @@ declare namespace Joi { type PresenceMode = 'optional' | 'required' | 'forbidden'; + type AllowMode = boolean | 'allowed' | 'forbidden' | 'warn'; + interface ErrorFormattingOptions { /** * when true, error message templates will escape special characters to HTML entities, for security purposes. @@ -104,7 +106,10 @@ declare namespace Joi { * * @default false */ - allowUnknown?: boolean; + allowUnknown?: AllowMode | { + mode: AllowMode, + type?: string, + }; /** * when true, schema caching is enabled (for schemas with explicit caching rules). * diff --git a/lib/types/keys.js b/lib/types/keys.js index 8f8a950f2..416eaeaa6 100755 --- a/lib/types/keys.js +++ b/lib/types/keys.js @@ -975,11 +975,14 @@ internals.unknown = function (schema, value, unprocessed, errors, state, prefs) } } - const forbidUnknown = !Common.default(schema._flags.unknown, prefs.allowUnknown); - if (forbidUnknown) { + const allowUnknown = Common.default(schema._flags.unknown, prefs.allowUnknown); + const allowUnknownIsObject = typeof allowUnknown === 'object' && !Array.isArray(allowUnknown) && allowUnknown !== null; + const type = (allowUnknownIsObject ? allowUnknown.type : undefined) || 'object.unknown'; + + if (allowUnknown === false || allowUnknown === undefined || allowUnknown === 'forbidden' || (allowUnknownIsObject && allowUnknown.mode === 'forbidden')) { for (const unprocessedKey of unprocessed) { const localState = state.localize([...state.path, unprocessedKey], []); - const report = schema.$_createError('object.unknown', value[unprocessedKey], { child: unprocessedKey }, localState, prefs, { flags: false }); + const report = schema.$_createError(type, value[unprocessedKey], { child: unprocessedKey }, localState, prefs, { flags: false }); if (prefs.abortEarly) { return { value, errors: report }; } @@ -987,6 +990,15 @@ internals.unknown = function (schema, value, unprocessed, errors, state, prefs) errors.push(report); } } + else if (allowUnknown === 'warn' || (allowUnknownIsObject && allowUnknown.mode === 'warn')) { + + for (const unprocessedKey of unprocessed) { + const localState = state.localize([...state.path, unprocessedKey], []); + const report = schema.$_createError(type, value[unprocessedKey], { child: unprocessedKey }, localState, prefs, { flags: false }); + + state.mainstay.warnings.push(report); + } + } }; diff --git a/test/validator.js b/test/validator.js index f4bc23269..cf78c9e49 100755 --- a/test/validator.js +++ b/test/validator.js @@ -508,6 +508,47 @@ describe('Validator', () => { } }); }); + + it('reports warnings with nested options', () => { + + const schema = Joi.object({ + nested: Joi.object({ + validKey: Joi.string() + }) + }).warning('custom.x').message('test'); + + const allowUnknown = { + mode: 'warn', + type: 'custom.x' + }; + + const input = { + nested: { + validKey: 'some string', + invalidKey: 'oh no!' + } + }; + + const { value, error, warning } = schema.validate(input, { allowUnknown }); + expect(value).to.equal(input); + expect(error).to.not.exist(); + expect(warning).to.equal({ + message: 'test', + details: [ + { + message: 'test', + path: ['nested', 'invalidKey'], + type: 'custom.x', + context: { + child: 'invalidKey', + key: 'invalidKey', + label: 'nested.invalidKey', + value: 'oh no!' + } + } + ] + }); + }); }); describe('Shadow', () => {