From 72912af76341e537d273986d816285f5312ffb3f Mon Sep 17 00:00:00 2001 From: Facon Nicolas <84249866+FACON-Nicolas@users.noreply.github.com> Date: Mon, 20 May 2024 16:33:02 +0200 Subject: [PATCH] feat: add in rule for number (#53) --- src/defaults.ts | 1 + src/schema/number/main.ts | 8 +++++++ src/schema/number/rules.ts | 16 ++++++++++++++ tests/unit/rules/number.spec.ts | 37 ++++++++++++++++++++++++++++++++ tests/unit/schema/number.spec.ts | 34 +++++++++++++++++++++++++++++ 5 files changed, 96 insertions(+) diff --git a/src/defaults.ts b/src/defaults.ts index a377bf1..c77e05a 100644 --- a/src/defaults.ts +++ b/src/defaults.ts @@ -45,6 +45,7 @@ export const messages = { 'boolean': 'The value must be a boolean', 'number': 'The {{ field }} field must be a number', + 'number.in': 'The selected {{ field }} is not in {{ values }}', 'min': 'The {{ field }} field must be at least {{ min }}', 'max': 'The {{ field }} field must not be greater than {{ max }}', 'range': 'The {{ field }} field must be between {{ min }} and {{ max }}', diff --git a/src/schema/number/main.ts b/src/schema/number/main.ts index 850a19d..261bf30 100644 --- a/src/schema/number/main.ts +++ b/src/schema/number/main.ts @@ -21,6 +21,7 @@ import { negativeRule, positiveRule, withoutDecimalsRule, + inRule, } from './rules.js' /** @@ -121,4 +122,11 @@ export class VineNumber extends BaseLiteralType clone(): this { return new VineNumber(this.cloneOptions(), this.cloneValidations()) as this } + + /** + * Enforce the value to be in a list of allowed values + */ + in(values: number[]) { + return this.use(inRule({ values })) + } } diff --git a/src/schema/number/rules.ts b/src/schema/number/rules.ts index 725c8dc..9f541ff 100644 --- a/src/schema/number/rules.ts +++ b/src/schema/number/rules.ts @@ -147,3 +147,19 @@ export const withoutDecimalsRule = createRule((value, _, field) => { field.report(messages.withoutDecimals, 'withoutDecimals', field) } }) + +/** + * Enforce the value to be in a list of allowed values + */ +export const inRule = createRule<{ values: number[] }>((value, options, field) => { + /** + * Skip if the field is not valid. + */ + if (!field.isValid) { + return + } + + if (!options.values.includes(value as number)) { + field.report(messages['number.in'], 'in', field, options) + } +}) diff --git a/tests/unit/rules/number.spec.ts b/tests/unit/rules/number.spec.ts index 5cf01ec..af9d230 100644 --- a/tests/unit/rules/number.spec.ts +++ b/tests/unit/rules/number.spec.ts @@ -10,6 +10,7 @@ import { test } from '@japa/runner' import { validator } from '../../../factories/main.js' import { + inRule, minRule, maxRule, rangeRule, @@ -395,3 +396,39 @@ test.group('Number | withoutDecimals', () => { validated.assertOutput(18) }) }) + +test.group('Number | in', () => { + test('skip validation when value is not a number', () => { + const number = numberRule({}) + const inArrayRule = inRule({ values: [1, 4] }) + const validated = validator.execute([number, inArrayRule], 'foo') + + validated.assertErrorsCount(1) + validated.assertError('The dummy field must be a number') + }) + + test('skip validation when value is not a number with bail mode disabled', () => { + const number = numberRule({}) + const inArrayRule = inRule({ values: [1, 4] }) + const validated = validator.bail(false).execute([number, inArrayRule], 'foo') + + validated.assertErrorsCount(1) + validated.assertError('The dummy field must be a number') + }) + + test('work fine when value is in an array', () => { + const number = numberRule({}) + const inArrayRule = inRule({ values: [1, 4] }) + const validated = validator.execute([number, inArrayRule], 4) + + validated.assertSucceeded() + }) + + test('fails when value is not in an array', () => { + const number = numberRule({}) + const inArrayRule = inRule({ values: [1, 4] }) + const validated = validator.execute([number, inArrayRule], 3) + + validated.assertErrorsCount(1) + }) +}) diff --git a/tests/unit/schema/number.spec.ts b/tests/unit/schema/number.spec.ts index dfe5547..99ecbec 100644 --- a/tests/unit/schema/number.spec.ts +++ b/tests/unit/schema/number.spec.ts @@ -13,6 +13,7 @@ import { refsBuilder } from '@vinejs/compiler' import { Vine } from '../../../src/vine/main.js' import { IS_OF_TYPE, PARSE } from '../../../src/symbols.js' import { + inRule, maxRule, minRule, rangeRule, @@ -743,4 +744,37 @@ test.group('VineNumber | applying rules', () => { options: withoutDecimals.options, }) }) + + test('apply in rule', ({ assert }) => { + const refs = refsBuilder() + const schema = vine.number().in([1, 2, 3]) + + assert.deepEqual(schema[PARSE]('*', refs, { toCamelCase: false }), { + type: 'literal', + fieldName: '*', + propertyName: '*', + bail: true, + allowNull: false, + isOptional: false, + parseFnId: undefined, + validations: [ + { + implicit: false, + isAsync: false, + ruleFnId: 'ref://1', + }, + { + implicit: false, + isAsync: false, + ruleFnId: 'ref://2', + }, + ], + }) + + const inArrayRule = inRule({ values: [1, 2, 3] }) + assert.deepEqual(refs.toJSON()['ref://2'], { + validator: inArrayRule.rule.validator, + options: inArrayRule.options, + }) + }) })