vest 3.2.8-dev-6d7c74 → 3.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -0
- package/any.d.ts +3 -0
- package/any.js +1 -0
- package/classNames.d.ts +14 -0
- package/classNames.js +1 -0
- package/docs/.nojekyll +0 -0
- package/docs/README.md +115 -0
- package/docs/_assets/favicon.ico +0 -0
- package/docs/_assets/vest-logo.png +0 -0
- package/docs/_sidebar.md +19 -0
- package/docs/_sidebar.md.bak +14 -0
- package/docs/cross_field_validations.md +79 -0
- package/docs/enforce.md +18 -0
- package/docs/enforce.md.bak +13 -0
- package/docs/exclusion.md +128 -0
- package/docs/getting_started.md +79 -0
- package/docs/group.md +142 -0
- package/docs/index.html +41 -0
- package/docs/migration.md +107 -0
- package/docs/n4s/compound.md +187 -0
- package/docs/n4s/custom.md +52 -0
- package/docs/n4s/external.md +54 -0
- package/docs/n4s/rules.md +1282 -0
- package/docs/n4s/template.md +53 -0
- package/docs/node.md +43 -0
- package/docs/optional.md +51 -0
- package/docs/result.md +238 -0
- package/docs/state.md +102 -0
- package/docs/test.md +172 -0
- package/docs/utilities.md +105 -0
- package/docs/warn.md +82 -0
- package/enforce.d.ts +230 -0
- package/esm/package.json +1 -0
- package/esm/vest.es.development.js +2493 -0
- package/esm/vest.es.production.js +2490 -0
- package/esm/vest.es.production.min.js +1 -0
- package/package.json +65 -12
- package/promisify.d.ts +7 -0
- package/{dist/umd/promisify.production.js → promisify.js} +1 -1
- package/schema.d.ts +26 -0
- package/schema.js +1 -0
- package/vest.cjs.development.js +2494 -0
- package/vest.cjs.production.js +2491 -0
- package/vest.cjs.production.min.js +1 -0
- package/vest.d.ts +254 -0
- package/vest.js +7 -0
- package/vest.umd.development.js +2711 -0
- package/vest.umd.production.js +2708 -0
- package/vest.umd.production.min.js +1 -0
- package/vestResult.d.ts +105 -0
- package/CHANGELOG.md +0 -94
- package/LICENSE +0 -21
- package/classnames/index.js +0 -7
- package/classnames/package.json +0 -1
- package/dist/cjs/classnames.development.js +0 -67
- package/dist/cjs/classnames.production.js +0 -1
- package/dist/cjs/promisify.development.js +0 -20
- package/dist/cjs/promisify.production.js +0 -1
- package/dist/cjs/vest.development.js +0 -1616
- package/dist/cjs/vest.production.js +0 -1
- package/dist/es/classnames.development.js +0 -65
- package/dist/es/classnames.production.js +0 -1
- package/dist/es/promisify.development.js +0 -18
- package/dist/es/promisify.production.js +0 -1
- package/dist/es/vest.development.js +0 -1604
- package/dist/es/vest.production.js +0 -1
- package/dist/umd/classnames.development.js +0 -73
- package/dist/umd/classnames.production.js +0 -1
- package/dist/umd/promisify.development.js +0 -26
- package/dist/umd/vest.development.js +0 -1622
- package/dist/umd/vest.production.js +0 -1
- package/index.js +0 -7
- package/promisify/index.js +0 -7
- package/promisify/package.json +0 -1
- package/types/classnames.d.ts +0 -71
- package/types/classnames.d.ts.map +0 -1
- package/types/promisify.d.ts +0 -61
- package/types/promisify.d.ts.map +0 -1
- package/types/vest.d.ts +0 -271
- package/types/vest.d.ts.map +0 -1
package/docs/group.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Grouping tests
|
|
2
|
+
|
|
3
|
+
In many cases it can be helpful to group tests together so you can include or exclude a portion of the suite with a single condition.
|
|
4
|
+
Similar to the `describe` and `context` features provided by unit testing frameworks, Vest provides `group`.
|
|
5
|
+
|
|
6
|
+
[Try on CodeSandbox (React)](https://codesandbox.io/s/vest-group-example-react-4i2ne)
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
import { create, test, group, enforce, skip } from 'vest';
|
|
10
|
+
|
|
11
|
+
create('authentication_form', data => {
|
|
12
|
+
skip.group(data.userExists ? 'signUp' : 'signIn');
|
|
13
|
+
|
|
14
|
+
test('userName', "Can't be empty", () => {
|
|
15
|
+
enforce(data.username).isNotEmpty();
|
|
16
|
+
});
|
|
17
|
+
test('password', "Can't be empty", () => {
|
|
18
|
+
enforce(data.password).isNotEmpty();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
group('signIn', () => {
|
|
22
|
+
test(
|
|
23
|
+
'userName',
|
|
24
|
+
'User not found. Please check if you typed it correctly.',
|
|
25
|
+
findUserName(data.username)
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
group('signUp', () => {
|
|
30
|
+
test('email', 'Email already registered', isEmailRegistered(data.email));
|
|
31
|
+
|
|
32
|
+
test('age', 'You must be at least 18 years old to join', () => {
|
|
33
|
+
enforce(data.age).largerThanOrEquals(18);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Why use `group` and not just wrap the tests with an `if` statement?
|
|
40
|
+
|
|
41
|
+
In many cases it is sufficient to just use an `if` statement. The benefit of using `group` is that when skipping (either using [skip or only](./exclusion)), Vest will merge the previous group result with the current suite. This is mostly suitable for cases like demonstrated in the first example of the multi stage form.
|
|
42
|
+
|
|
43
|
+
## Use cases
|
|
44
|
+
|
|
45
|
+
### 1. Multi stage form
|
|
46
|
+
|
|
47
|
+
You may have in your application a multi-screen form, in which you want to validate each screen individually, but submit it all at once.
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
// suite.js
|
|
51
|
+
import { create, test, group, enforce, only } from 'vest';
|
|
52
|
+
|
|
53
|
+
const suite = create('product-create', (data, currentTab) => {
|
|
54
|
+
only.group(currentScreen);
|
|
55
|
+
|
|
56
|
+
group('overview_tab', () => {
|
|
57
|
+
test('productTitle', 'Must be at least 5 chars.', () => {
|
|
58
|
+
enforce(data.productTitle).longerThanOrEquals(5);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('productDescription', "Can't be longer than 2500 chars.", () => {
|
|
62
|
+
enforce(data.productDescription).shorterThanOrEquals(2500);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('productTags', 'Please provide up to 5 tags', () => {
|
|
66
|
+
enforce(data.tags).lengthEquals(5);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
group('pricing_tab', () => {
|
|
71
|
+
test('price', '5$ or more.', () => {
|
|
72
|
+
enforce(data.price).lte(5);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('productExtras', "Can't be empty.", () => {
|
|
76
|
+
enforce(data.extras).isNotEmpty();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
export default suite;
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
// myFeature.js
|
|
86
|
+
|
|
87
|
+
suite(data, 'overview_tab'); // will only validate 'overview_tab' group
|
|
88
|
+
suite(data, 'pricing_tab'); // will only validate 'pricing_tab' group
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 2. Skipping tests with shared fields
|
|
92
|
+
|
|
93
|
+
You sometimes want to skip some tests on a certain condition, but still run other tests with the same field-name.
|
|
94
|
+
|
|
95
|
+
In the example below, we don't mind skipping the `balance` field directly, but if we skip the `quantity` field directly, it won't be tested at all - even though it has one test outside of the group. That's why we skip the `used_promo`.
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
import { create, test, group, enforce, skip } from 'vest';
|
|
99
|
+
|
|
100
|
+
const suite = create('checkout_form', data => {
|
|
101
|
+
if (!data.usedPromo) skip.group('used_promo');
|
|
102
|
+
if (!data.paysWithBalance) skip.group('balance');
|
|
103
|
+
|
|
104
|
+
test(
|
|
105
|
+
'balance',
|
|
106
|
+
'Balance is lower than product price',
|
|
107
|
+
hasSufficientFunds(data.productId)
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
test('quantity', `Quantity on this item is limited to ${data.limit}`, () => {
|
|
111
|
+
enforce(data.quantity).lessThanOrEquals(data.limit);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
group('used_promo', () => {
|
|
115
|
+
test(
|
|
116
|
+
'quantity',
|
|
117
|
+
'promo code purchases are limited to one item only',
|
|
118
|
+
() => {
|
|
119
|
+
enforce(data.quantity).equals(1);
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
test(
|
|
124
|
+
'promoCode',
|
|
125
|
+
'Promo code can only be used once',
|
|
126
|
+
isPromoCodeUsed(data.usedPromo)
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Querying the result object for groups
|
|
133
|
+
|
|
134
|
+
Groups represent a portion of your validation suite, so when using `group`, you are likely to need to get the group-specific validation results.
|
|
135
|
+
Your result object exposes the following methods:
|
|
136
|
+
|
|
137
|
+
- [_hasErrorsByGroup_](./result#haserrorsbygroup-and-haswarningsbygroup-functions)
|
|
138
|
+
- [_hasWarningsByGroup_](./result#haserrorsbygroup-and-haswarningsbygroup-functions)
|
|
139
|
+
- [_hasErrorsByGroup_](./result#geterrorsbygroup-and-getwarningsbygroup-functions)
|
|
140
|
+
- [_hasWarningsByGroup_](./result#geterrorsbygroup-and-getwarningsbygroup-functions)
|
|
141
|
+
|
|
142
|
+
Read more about these methods in [the result object](./result).
|
package/docs/index.html
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>Vest - Declarative Validations</title>
|
|
6
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
|
7
|
+
<meta name="description" content="Validation Test" />
|
|
8
|
+
<meta
|
|
9
|
+
name="viewport"
|
|
10
|
+
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
|
|
11
|
+
/>
|
|
12
|
+
<link rel="icon" href="./_assets/favicon.ico" />
|
|
13
|
+
<link
|
|
14
|
+
rel="stylesheet"
|
|
15
|
+
href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple.css"
|
|
16
|
+
/>
|
|
17
|
+
<link
|
|
18
|
+
rel="stylesheet"
|
|
19
|
+
href="https://cdn.jsdelivr.net/npm/prismjs@1.22.0/themes/prism-tomorrow.css"
|
|
20
|
+
/>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<div id="app"></div>
|
|
24
|
+
<script>
|
|
25
|
+
window.$docsify = {
|
|
26
|
+
name: 'vest',
|
|
27
|
+
logo: '/_assets/vest-logo.png',
|
|
28
|
+
repo: 'https://github.com/ealush/vest',
|
|
29
|
+
search: 'auto',
|
|
30
|
+
loadSidebar: true,
|
|
31
|
+
subMaxLevel: 2,
|
|
32
|
+
themeColor: '#5BA9D8',
|
|
33
|
+
};
|
|
34
|
+
</script>
|
|
35
|
+
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
|
|
36
|
+
<script src="https://cdn.jsdelivr.net/npm/docsify-themeable@0"></script>
|
|
37
|
+
<script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script>
|
|
38
|
+
<script src="//unpkg.com/vest"></script>
|
|
39
|
+
<script src="./_assets/index.js"></script>
|
|
40
|
+
</body>
|
|
41
|
+
</html>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Migration guides
|
|
2
|
+
|
|
3
|
+
## V2 to V3
|
|
4
|
+
|
|
5
|
+
Vest version 3 comes with many new features, yet with a reduced bundle size. To achieve this, some redundant interfaces were removed. All v2 capabilities still exist, but the way to use some of them changed.
|
|
6
|
+
|
|
7
|
+
**Replaced interfaces**
|
|
8
|
+
|
|
9
|
+
### Removed: vest.get()
|
|
10
|
+
|
|
11
|
+
From now on, use suite.get() to get the latest validation result.
|
|
12
|
+
|
|
13
|
+
#### v2
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
const suite = vest.create('user_form', () => {
|
|
17
|
+
/*...*/
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
vest.get('user_form'); // Returns the most recent validation result
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
#### v3
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
const suite = create(() => {
|
|
27
|
+
/*...*/
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
suite.get(); // Returns the most recent validation result
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Removed: vest.reset() // To reset suite state
|
|
34
|
+
|
|
35
|
+
From now on, use suite.reset() to reset the validation result.
|
|
36
|
+
|
|
37
|
+
#### v2
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
const suite = vest.create('user_form', () => {
|
|
41
|
+
/*...*/
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
vest.reset('user_form'); // Resets the validity state
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
#### v3
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
const suite = create(() => {
|
|
51
|
+
/*...*/
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
suite.reset(); // Resets the validity state
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Removed: vest.draft() // To retrieve intermediate result
|
|
58
|
+
|
|
59
|
+
From now on, use suite.get() to get the validation result.
|
|
60
|
+
|
|
61
|
+
#### v2
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
const suite = vest.create('user_form', () => {
|
|
65
|
+
if (vest.draft().hasErrors('username')) {
|
|
66
|
+
/* ... */
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
#### v3
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
const suite = create('user_form', () => {
|
|
75
|
+
skipWhen(suite.get().hasErrors('username'), () => {
|
|
76
|
+
/* ... */
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Removed: validate() // For non persistent validations
|
|
82
|
+
|
|
83
|
+
The stateless validate export is not needed anymore due to a change in the state structure.
|
|
84
|
+
|
|
85
|
+
#### v2
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
import { validate } from 'vest';
|
|
89
|
+
|
|
90
|
+
const result = data =>
|
|
91
|
+
validate('user_form', () => {
|
|
92
|
+
/*...*/
|
|
93
|
+
})();
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### v3
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
import { create } from 'vest';
|
|
100
|
+
|
|
101
|
+
const suite = data =>
|
|
102
|
+
create(() => {
|
|
103
|
+
/* ... */
|
|
104
|
+
})();
|
|
105
|
+
|
|
106
|
+
const result = suite({ username: 'example' });
|
|
107
|
+
```
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Shape and schema validation
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
- [enforce.anyOf() - either/or validations](#anyof)
|
|
6
|
+
- [enforce.allOf() - all/and validations](#allof)
|
|
7
|
+
- [enforce.shape() - Object's shape matching](#shape)
|
|
8
|
+
- [enforce.optional() - nullable keys](#optional)
|
|
9
|
+
- [enforec.loose() - loose shape matching](#loose)
|
|
10
|
+
- [enforce.isArrayOf() - array shape matching](#isarrayof)
|
|
11
|
+
|
|
12
|
+
## enforce.anyOf() - either/or validations :id=anyof
|
|
13
|
+
|
|
14
|
+
Sometimes a value has more than one valid possibilities, `any` lets us validate that a value passes _at least_ one of the supplied rules.
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
enforce(value).anyOf(enforce.isString(), enforce.isArray()).isNotEmpty();
|
|
18
|
+
// A valid value would either an array or a string.
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## enforce.allOf() - all/and validations :id=allof
|
|
22
|
+
|
|
23
|
+
`allOf` lets us validate that a value passes _all_ of the supplied rules or templates.
|
|
24
|
+
|
|
25
|
+
enforce(value).allOf(
|
|
26
|
+
enforce.isArray(),
|
|
27
|
+
enforce.longerThan(2)
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
This can be even more useful when combined with shapes and templates:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
const User = enforce.template(
|
|
34
|
+
enforce.loose({
|
|
35
|
+
id: enforce.isNumber()
|
|
36
|
+
name: enforce.shape({
|
|
37
|
+
first: enforce.isString(),
|
|
38
|
+
last: enforce.isString(),
|
|
39
|
+
middle: enforce.optional(enforce.isString()),
|
|
40
|
+
}),
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const DisabledAccount = enforce.template(
|
|
45
|
+
enforce.loose({
|
|
46
|
+
disabled: enforce.equals(true)
|
|
47
|
+
})
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
enforce(value).allOf(
|
|
51
|
+
User,
|
|
52
|
+
DisabledAccount
|
|
53
|
+
);
|
|
54
|
+
// A valid is string and longer then 5.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## enforce.shape() - Lean schema validation. :id=shape
|
|
58
|
+
|
|
59
|
+
`enforce.shape()` validates the structure of an object.
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
enforce({
|
|
63
|
+
firstName: 'Rick',
|
|
64
|
+
lastName: 'Sanchez',
|
|
65
|
+
age: 70,
|
|
66
|
+
}).shape({
|
|
67
|
+
firstName: enforce.isString(),
|
|
68
|
+
lastName: enforce.isString(),
|
|
69
|
+
age: enforce.isNumber(),
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
You may also chain your validation rules:
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
enforce({
|
|
77
|
+
age: 22,
|
|
78
|
+
}).shape({
|
|
79
|
+
age: enforce.isNumber().isBetween(0, 150),
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
You may also nest calls to shape in order to validate a deeply nested object.
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
enforce({
|
|
87
|
+
user: {
|
|
88
|
+
name: {
|
|
89
|
+
first: 'Joseph',
|
|
90
|
+
last: 'Weil',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
}).shape({
|
|
94
|
+
user: enforce.shape({
|
|
95
|
+
name: enforce.shape({
|
|
96
|
+
first: enforce.isString(),
|
|
97
|
+
last: enforce.isString(),
|
|
98
|
+
}),
|
|
99
|
+
}),
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### enforce.optional() - nullable keys :id=optional
|
|
104
|
+
|
|
105
|
+
-- Optional can only be used within enforce.shape().
|
|
106
|
+
|
|
107
|
+
In regular cases, a missing key in the data object would cause an error to be thrown. To prevent that from happening, mark your optional keys with `enforce.optional`.
|
|
108
|
+
|
|
109
|
+
enforce.optional will pass validations of a key that's either not defined, undefined or null.
|
|
110
|
+
|
|
111
|
+
`enforce.optional` takes as its arguments all the rules that the value must pass.
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
enforce({
|
|
115
|
+
user: {
|
|
116
|
+
name: {
|
|
117
|
+
first: 'Joseph',
|
|
118
|
+
last: 'Weil',
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
}).shape({
|
|
122
|
+
user: enforce.shape({
|
|
123
|
+
name: enforce.shape({
|
|
124
|
+
first: enforce.isString(),
|
|
125
|
+
last: enforce.isString(),
|
|
126
|
+
middle: enforce.optional(enforce.isString(), enforce.longerThan(3)),
|
|
127
|
+
}),
|
|
128
|
+
}),
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## enforec.loose() - loose shape matching :id=loose
|
|
133
|
+
|
|
134
|
+
By default, shape will treat excess keys in your data object as validation errors. If you wish to allow support for excess keys in your object's shape, you can use `enforce.loose()` which is a shorthand to `enforce.shape(data, shape, { loose: true })`.
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
enforce({ name: 'Laura', code: 'x23' }).shape({ name: enforce.isString() });
|
|
138
|
+
// 🚨 This will throw an error because `code` is not defined in the shape
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
enforce({ name: 'Laura', code: 'x23' }).loose({ name: enforce.isString() });
|
|
143
|
+
// ✅ This will pass with `code` not being validated
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## enforce.isArrayOf() - array shape matching :id=isarrayof
|
|
147
|
+
|
|
148
|
+
enforce.isArrayOf can be used to determine the allowed types and values within an array. It will run against each element in the array, and will only pass if all items meet at least one of the validation rules.
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
enforce([1, 2, 'hello!']).isArrayOf(enforce.isString(), enforce.isNumber());
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
You can also combine `isArrayOf` with other rules to validate other array properties:
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
enforce(someArrayValue)
|
|
158
|
+
.isArrayOf(enforce.isString(), enforce.isNumber().lessThan(3))
|
|
159
|
+
.longerThan(2);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
And as part of shape:
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
enforce({ data: [1, 2, 3] }).shape({
|
|
166
|
+
data: enforce.isArrayOf(enforce.isNumber()),
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## enforce.oneOf()
|
|
171
|
+
|
|
172
|
+
enforce.oneOf can be used to determine if _exactly_ one of the rules applies. It will run against rule in the array, and will only pass if exactly one rule applies.
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
enforce(value).oneOf(
|
|
176
|
+
enforce.isString(),
|
|
177
|
+
enforce.isNumber(),
|
|
178
|
+
enforce.longerThan(1)
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
/*
|
|
182
|
+
value = 1 -> ✅ (value is a number)
|
|
183
|
+
value = "1" -> ✅ (value is string)
|
|
184
|
+
value = [1, 2] -> ✅ (value is longer than 1)
|
|
185
|
+
value = "12" -> 🚨 (value is both a string and longer than 1)
|
|
186
|
+
*/
|
|
187
|
+
```
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Creating Custom Rules
|
|
2
|
+
|
|
3
|
+
To make it easier to reuse logic across your application, sometimes you would want to encapsulate bits of logic in rules that you can use later on, for example, "what's considered a valid email".
|
|
4
|
+
|
|
5
|
+
Rules are called with the argument passed to enforce(x) followed by the arguments passed to `.yourRule(y, z)`.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
enforce.extend({
|
|
9
|
+
yourRule(x, y, z) {
|
|
10
|
+
return {
|
|
11
|
+
pass: true,
|
|
12
|
+
message: () => '',
|
|
13
|
+
};
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
enforce.extend({
|
|
20
|
+
isValidEmail: value => value.indexOf('@') > -1,
|
|
21
|
+
hasKey: (value, key) => value.hasOwnProperty(key),
|
|
22
|
+
passwordsMatch: (passConfirm, options) =>
|
|
23
|
+
passConfirm === options.passConfirm && options.passIsValid,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
enforce(user.email).isValidEmail();
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Custom rules return value
|
|
30
|
+
|
|
31
|
+
Rules can either return boolean indicating success or failure, or an object with two keys. `pass` indicates whether the validation is successful or not, and message provides a function with no arguments that return an error message in case of failure. Thus, when pass is false, message should return the error message for when enforce(x).yourRule() fails.
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
enforce.extend({
|
|
35
|
+
isWithinRange(received, floor, ceiling) {
|
|
36
|
+
const pass = received >= floor && received <= ceiling;
|
|
37
|
+
if (pass) {
|
|
38
|
+
return {
|
|
39
|
+
message: () =>
|
|
40
|
+
`expected ${received} not to be within range ${floor} - ${ceiling}`,
|
|
41
|
+
pass: true,
|
|
42
|
+
};
|
|
43
|
+
} else {
|
|
44
|
+
return {
|
|
45
|
+
message: () =>
|
|
46
|
+
`expected ${received} to be within range ${floor} - ${ceiling}`,
|
|
47
|
+
pass: false,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
```
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Consuming external rules
|
|
2
|
+
|
|
3
|
+
Enforce comes with the bare minimum of rules needed for input validation, not assuming your business logic constraints.
|
|
4
|
+
|
|
5
|
+
In some cases you might require more validations such as `isEmail` or `isPhoneNumber`. Enforce intentionally does not include those, since those validations may not necessarily reflect the way those validations should work in your app.
|
|
6
|
+
|
|
7
|
+
Luckily, there are numerous packages that can be used along with enforce to add those validations. One of the most popular, and most compatible is `validator.js`.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
npm i validator
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Validator.js is a pretty big package. To prevent it from unnecessarily increasing your bundle size for rules you don't use, import the ones you use individually.
|
|
14
|
+
|
|
15
|
+
Then add those rules with `enforce.extend`:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
import isEmail from 'validator/es/lib/isEmail';
|
|
19
|
+
import isMobilePhone from 'validator/es/lib/isMobilePhone';
|
|
20
|
+
|
|
21
|
+
enforce.extend({ isEmail, isMobilePhone });
|
|
22
|
+
|
|
23
|
+
enforce('example@example.com').isEmail(); // ✅
|
|
24
|
+
enforce('example[at]example[dot]com').isEmail(); // 🚨
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
A full list of the supported validator.js rules can be found on [npmjs.com/package/validator](https://www.npmjs.com/package/validator). Some common rules are:
|
|
28
|
+
|
|
29
|
+
- isAfter
|
|
30
|
+
- isBefore
|
|
31
|
+
- isBtcAddress
|
|
32
|
+
- isCreditCard
|
|
33
|
+
- isCurrency
|
|
34
|
+
- isDate
|
|
35
|
+
- isEmail
|
|
36
|
+
- isFQDN
|
|
37
|
+
- isIBAN
|
|
38
|
+
- isIdentityCard
|
|
39
|
+
- isIP
|
|
40
|
+
- isIPRange
|
|
41
|
+
- isJSON
|
|
42
|
+
- isJWT
|
|
43
|
+
- isMACAddress
|
|
44
|
+
- isMD5
|
|
45
|
+
- isMimeType
|
|
46
|
+
- isMobilePhone
|
|
47
|
+
- isMongoId
|
|
48
|
+
- isPassportNumber
|
|
49
|
+
- isPort
|
|
50
|
+
- isPostalCode
|
|
51
|
+
- isSlug
|
|
52
|
+
- isURL
|
|
53
|
+
- isTaxID
|
|
54
|
+
- isUUID
|