From f54cb843c5652392dd8ad6813cb76fe0a76a3b0d Mon Sep 17 00:00:00 2001 From: Tai Vo Date: Wed, 15 Feb 2023 10:37:30 -0800 Subject: [PATCH] 7482/validators exclude include caseinsensitive (#7573) * fixed exclusion and inclusion types to accept Array * fix 'format' validator types to accept RegExp as expected in unit tests * remove excess parens to make sure both regexp test cases are run * validations: add 'caseSensitive' boolean option to exclusion and inclusion * incluse caseSensitive option in service validation docs --- docs/docs/services.md | 2 + .../validations/__tests__/validations.test.js | 58 +++++++++++++++++++ packages/api/src/validations/validations.ts | 34 +++++++++-- 3 files changed, 88 insertions(+), 6 deletions(-) diff --git a/docs/docs/services.md b/docs/docs/services.md index 4fbaef8e9027..4637c540c691 100644 --- a/docs/docs/services.md +++ b/docs/docs/services.md @@ -276,6 +276,7 @@ validate(input.name, 'Name', { ##### Options * `in`: the list of values that cannot be used +* `caseSensitive`: toggles case sensitivity; default: `true` ```jsx validate(input.name, 'Name', { @@ -339,6 +340,7 @@ validate(input.role, 'Role', { ##### Options * `in`: the list of values that can be used +* `caseSensitive`: toggles case sensitivity; default: `true` ```jsx validate(input.role, 'Role', { diff --git a/packages/api/src/validations/__tests__/validations.test.js b/packages/api/src/validations/__tests__/validations.test.js index 64a0e185f99d..d450d26a0037 100644 --- a/packages/api/src/validations/__tests__/validations.test.js +++ b/packages/api/src/validations/__tests__/validations.test.js @@ -177,6 +177,11 @@ describe('validate exclusion', () => { expect(() => validate('bar', 'selection', { exclusion: { in: ['foo', 'bar'] } }) ).toThrow(ValidationErrors.ExclusionValidationError) + expect(() => + validate('bar', 'selection', { + exclusion: { in: ['foo', 'bar'], caseSensitive: true }, + }) + ).toThrow(ValidationErrors.ExclusionValidationError) expect(() => validate('qux', 'selection', { exclusion: ['foo', 'bar'] }) @@ -184,6 +189,30 @@ describe('validate exclusion', () => { expect(() => validate('qux', 'selection', { exclusion: { in: ['foo', 'bar'] } }) ).not.toThrow() + expect(() => + validate('qux', 'selection', { + exclusion: { in: ['foo', 'bar'], caseSensitive: true }, + }) + ).not.toThrow() + }) + + it('checks for case-insensitive exclusion', () => { + expect(() => + validate('Bar', 'selection', { + exclusion: { in: ['foo', 'bar'], caseSensitive: false }, + }) + ).toThrow(ValidationErrors.ExclusionValidationError) + expect(() => + validate('bar', 'selection', { + exclusion: { in: ['foo', 'Bar'], caseSensitive: false }, + }) + ).toThrow(ValidationErrors.ExclusionValidationError) + + expect(() => + validate('qux', 'selection', { + exclusion: { in: ['foo', 'bar'], caseSensitive: false }, + }) + ).not.toThrow() }) it('throws with a default message', () => { @@ -325,6 +354,11 @@ describe('validate inclusion', () => { expect(() => validate('quux', 'selection', { inclusion: { in: ['foo', 'bar'] } }) ).toThrow(ValidationErrors.InclusionValidationError) + expect(() => + validate('QUUX', 'selection', { + inclusion: { in: ['foo', 'bar'], caseSensitive: true }, + }) + ).toThrow(ValidationErrors.InclusionValidationError) expect(() => validate('foo', 'selection', { inclusion: ['foo', 'bar'] }) @@ -332,6 +366,30 @@ describe('validate inclusion', () => { expect(() => validate('foo', 'selection', { inclusion: { in: ['foo', 'bar'] } }) ).not.toThrow() + expect(() => + validate('foo', 'selection', { + inclusion: { in: ['foo', 'bar'], caseSensitive: true }, + }) + ).not.toThrow() + }) + + it('checks for case-insensitive inclusion', () => { + expect(() => + validate('quux', 'selection', { + inclusion: { in: ['foo', 'bar'], caseSensitive: false }, + }) + ).toThrow(ValidationErrors.InclusionValidationError) + + expect(() => + validate('Foo', 'selection', { + inclusion: { in: ['foo', 'bar'], caseSensitive: false }, + }) + ).not.toThrow() + expect(() => + validate('foo', 'selection', { + inclusion: { in: ['FOO', 'bar'], caseSensitive: false }, + }) + ).not.toThrow() }) it('throws with a default message', () => { diff --git a/packages/api/src/validations/validations.ts b/packages/api/src/validations/validations.ts index 60498b5dd4bb..7e1a4c6963f3 100644 --- a/packages/api/src/validations/validations.ts +++ b/packages/api/src/validations/validations.ts @@ -35,6 +35,7 @@ interface ExclusionValidatorOptions extends WithOptionalMessage { * The list of values that cannot be used. */ in?: Array + caseSensitive?: boolean } interface FormatValidatorOptions extends WithOptionalMessage { @@ -49,6 +50,7 @@ interface InclusionValidatorOptions extends WithOptionalMessage { * The list of values that can be used. */ in?: Array + caseSensitive?: boolean } interface LengthValidatorOptions extends WithOptionalMessage { @@ -309,10 +311,9 @@ const VALIDATORS = { name: string, options: Array | ExclusionValidatorOptions ) => { - const exclusionList = - (Array.isArray(options) && options) || options.in || [] + const [exclusionList, val] = prepareExclusionInclusion(value, options) - if (exclusionList.includes(value)) { + if (exclusionList.includes(val)) { validationError('exclusion', name, options) } }, @@ -349,10 +350,9 @@ const VALIDATORS = { name: string, options: Array | InclusionValidatorOptions ) => { - const inclusionList = - (Array.isArray(options) && options) || options.in || [] + const [inclusionList, val] = prepareExclusionInclusion(value, options) - if (!inclusionList.includes(value)) { + if (!inclusionList.includes(val)) { validationError('inclusion', name, options) } }, @@ -549,6 +549,28 @@ const validationError = ( throw new ErrorClass(name, errorMessage, substitutions) } +// Generate the final list and value used for exclusion/inclusion by taking +// case-sensitivity into consideration. The returned array and value then +// can simply be used with Array.includes to perform exclusion/inclusion checks. +const prepareExclusionInclusion = ( + value: unknown, + options: + | Array + | InclusionValidatorOptions + | ExclusionValidatorOptions +): [Array, unknown] => { + const inputList = (Array.isArray(options) && options) || options.in || [] + + // default case sensitivity to true + const caseSensitive = Array.isArray(options) + ? true + : options.caseSensitive ?? true + + return caseSensitive + ? [inputList, value] + : [inputList.map((s) => s.toLowerCase()), (value as string).toLowerCase()] +} + // Main validation function, `directives` decides which actual validators // above to use //