diff --git a/jsconfig.json b/jsconfig.json index dde6a6f79..260e6b493 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -20,6 +20,7 @@ "runAnyoneMethods": ["./packages/anyone/src/runner/runAnyoneMethods.js"], "bindLazyRule": ["./packages/n4s/src/enforce/bindLazyRule.js"], "anyOf": ["./packages/n4s/src/enforce/compounds/anyOf.js"], + "allOf": ["./packages/n4s/src/enforce/compounds/allOf.js"], "compounds": ["./packages/n4s/src/enforce/compounds/compounds.js"], "isArrayOf": ["./packages/n4s/src/enforce/compounds/isArrayOf.js"], "optional": ["./packages/n4s/src/enforce/compounds/optional.js"], diff --git a/package.json b/package.json index c59af73ee..f87e3c6de 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "prettier-watch": "onchange '**/*.js' '**/*.json' -- prettier --write {{changed}}", "dev": "onchange -i './packages/**/src/**/*.js' -- yarn genJSConfig", "genJSConfig": "node ./scripts/genJsconfig", - "test": "jest --projects ./packages/*", + "test": "jest --projects ./packages/n4s", "lint": "eslint . --ignore-path .gitignore", "pretest": "yarn genJSConfig", "docs": "node scripts/release/steps/updateDocs" diff --git a/packages/n4s/docs/compound.md b/packages/n4s/docs/compound.md index e0d52206b..e367fe51a 100644 --- a/packages/n4s/docs/compound.md +++ b/packages/n4s/docs/compound.md @@ -3,8 +3,9 @@ Alongside the list of rules that only accept data provided by the user, enforce also supports compound rules - these are rules that accept other rules as their arguments. These rules let you validate more complex scenarios with the ergonomics of enforce. - [enforce.anyOf() - either/or validations](#anyof) +- [enforce.allOf() - all/and validations](#anyof) - [enforce.shape() - Object's shape matching](#shape) - - [enforce.optional() - nullable keys](#optional) +- [enforce.optional() - nullable keys](#optional) - [enforec.loose() - loose shape matching](#loose) - [enforce.isArrayOf() - array shape matching](#isarrayof) @@ -16,6 +17,14 @@ Sometimes a value has more than one valid possibilities, `any` lets us validate enforce(value).anyOf(enforce.isString(), enforce.isArray()).isNotEmpty(); // A valid value would either an array or a string. ``` +## enforce.allOf() - all/and validations :id=allOf + +Sometimes we need to make sure that a value for a set of rules, `all` lets us validate that a value passes _all_ of the supplied rules. + +```js +enforce(value).allOf(enforce.isString(), enforce.longerThen(5)); +// A valid is string and longer then 5. +``` ## enforce.shape() - Lean schema validation. :id=shape diff --git a/packages/n4s/docs/template.md b/packages/n4s/docs/template.md index cf029b53b..302da59fe 100644 --- a/packages/n4s/docs/template.md +++ b/packages/n4s/docs/template.md @@ -26,7 +26,7 @@ Username('1234'); // throws Username('ab'); // throws ``` -You can also use templates inside other compound rules, such as `shape`, `isArrayOf` or `anyOf`. +You can also use templates inside other compound rules, such as `shape`, `isArrayOf` ,`anyOf` or `allOf`. ```js enforce({ diff --git a/packages/n4s/src/enforce/compounds/__tests__/allOf.test.js b/packages/n4s/src/enforce/compounds/__tests__/allOf.test.js new file mode 100644 index 000000000..a7fb7ca79 --- /dev/null +++ b/packages/n4s/src/enforce/compounds/__tests__/allOf.test.js @@ -0,0 +1,62 @@ +import allOf from 'allOf'; +import enforce from 'enforce'; + +describe('allOf validation', () => { + describe('Base behavior', () => { + it('Should fail when at least one rule fail', () => { + expect(allOf('test', enforce.isString(), enforce.longerThan(10))).toBe( + false + ); + }); + it('Should succeed when all of the rules applies', () => { + expect( + allOf('test', enforce.isString(), enforce.longerThan(3)) + ).toBe(true); + }); + it('Should fail with no rules', () => { + expect(allOf(3)).toBe(true); + }); + }); + + describe('As part of enforce', () => { + const User = enforce.template( + enforce.loose({ + id: enforce.isNumber(), + name: enforce.shape({ + first: enforce.isString(), + last: enforce.isString(), + middle: enforce.optional(enforce.isString()), + }), + }) + ); + + const DisabledAccount = enforce.template( + enforce.loose({ + disabled: enforce.equals(true) + }) + ) + + it('Should validate allof the rules correctly', () => { + enforce(value).allOf( + { id: 123, name : { first: 'Albert', last: 'Einstein' } }, + { disabled: true } + ); + }); + + it('Should throw if one of the rules fail', () =>{ + expect( () => { + enforce(value).allOf( + { id: 123, name : { first: 'Albert', last: 0 } }, + { disabled: true } + ); + }).toThrow(); + + expect( () => { + enforce(value).allOf( + { id: 123, name : { first: 'Albert', last: 'Einstein' } }, + { disabled: false } + ); + }).toThrow(); + } ) + }); +}); diff --git a/packages/n4s/src/enforce/compounds/allOf.js b/packages/n4s/src/enforce/compounds/allOf.js new file mode 100644 index 000000000..ea9a01ff9 --- /dev/null +++ b/packages/n4s/src/enforce/compounds/allOf.js @@ -0,0 +1,10 @@ +import runLazyRules from 'runLazyRules'; +import { withFirst } from 'withArgs'; + +function allOf(value, rules) { + return ( + !rules.length || rules.every(ruleGroup => runLazyRules(ruleGroup, value)) + ); +} + +export default withFirst(anyOf); diff --git a/packages/n4s/src/enforce/compounds/compounds.js b/packages/n4s/src/enforce/compounds/compounds.js index 039b44537..68c1879bb 100644 --- a/packages/n4s/src/enforce/compounds/compounds.js +++ b/packages/n4s/src/enforce/compounds/compounds.js @@ -1,10 +1,12 @@ import anyOf from 'anyOf'; +import allOf from 'allOf'; import isArrayOf from 'isArrayOf'; import optional from 'optional'; import { shape, loose } from 'shape'; export default { anyOf, + allOf, isArrayOf, loose, optional, diff --git a/packages/vest/src/__tests__/test_types/fixtures/enforce.ts b/packages/vest/src/__tests__/test_types/fixtures/enforce.ts index 416f8a789..79b382d20 100644 --- a/packages/vest/src/__tests__/test_types/fixtures/enforce.ts +++ b/packages/vest/src/__tests__/test_types/fixtures/enforce.ts @@ -88,3 +88,7 @@ enforce // anyOf enforce(1).anyOf(enforce.isNumber(), enforce.isArray()); enforce.anyOf(enforce.isNumber(), enforce.isArray()).test(1); + +// allOf +enforce('hello').allOf(enforce.isString(), enforce.longerThan(3)); +enforce.allOf(enforce.isString(), enforce.longerThan(3)).test('hello'); \ No newline at end of file diff --git a/packages/vest/src/typings/enforce.d.ts b/packages/vest/src/typings/enforce.d.ts index 729f25279..8c1eac388 100644 --- a/packages/vest/src/typings/enforce.d.ts +++ b/packages/vest/src/typings/enforce.d.ts @@ -88,6 +88,7 @@ interface IEnforceRules { ) => RuleReturn; isArrayOf: CompoundListOfRules; anyOf: CompoundListOfRules; + allOf: CompoundListOfRules; } interface IEnforce { @@ -194,6 +195,7 @@ type TEnforceLazy = { optional: LazyCopmoundListOfRules; isArrayOf: LazyCopmoundListOfRules; anyOf: LazyCopmoundListOfRules; + allOf: LazyCopmoundListOfRules; }; export type TEnforce = IEnforce & TEnforceLazyReturn;