@ciwergrp/nuxid 1.5.2 → 1.5.4
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 +29 -0
- package/console/request.mjs +56 -0
- package/dist/module.d.mts +5 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +5 -0
- package/dist/runtime/lodash-factory.d.ts +2 -0
- package/dist/runtime/lodash-factory.js +4 -0
- package/dist/runtime/validator.d.ts +1 -1
- package/dist/runtime/validator.js +750 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -160,6 +160,35 @@ export default defineNuxtConfig({
|
|
|
160
160
|
|
|
161
161
|
Disable any feature by setting it to `false` (e.g. `nuxid: { lodash: false }`).
|
|
162
162
|
|
|
163
|
+
## Structure Directory
|
|
164
|
+
|
|
165
|
+
This module is designed to work best in a Nuxt + pnpm monorepo, where each product or domain is its own Nuxt app under `apps/`, and shared building blocks live in `layers/` as Nuxt layers. The root of the repo acts as the single workspace: dependency versions, lint config, TypeScript config, and tooling live at the top level, while apps and layers are composed via Nuxt’s layer system. This keeps per-domain apps small and focused, encourages reuse without copy/paste, and makes it easy to scale multiple teams or products inside one repo.
|
|
166
|
+
|
|
167
|
+
Recommended top-level structure:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
% ls -l
|
|
171
|
+
total 1008
|
|
172
|
+
-rw-r--r--@ 1 bagusvnt staff 14838 Jan 12 13:24 AGENTS.md
|
|
173
|
+
-rw-r--r-- 1 bagusvnt staff 371 Jan 7 10:45 README.md
|
|
174
|
+
drwxr-xr-x 5 bagusvnt staff 160 Jan 7 11:46 apps
|
|
175
|
+
-rw-r--r--@ 1 bagusvnt staff 134 Jan 7 12:26 docker-compose.yml
|
|
176
|
+
-rw-r--r-- 1 bagusvnt staff 1103 Jan 7 10:45 eslint.config.mjs
|
|
177
|
+
drwxr-xr-x 4 bagusvnt staff 128 Jan 7 10:45 layers
|
|
178
|
+
drwxr-xr-x@ 781 bagusvnt staff 24992 Jan 15 10:06 node_modules
|
|
179
|
+
-rw-r--r--@ 1 bagusvnt staff 1158 Jan 15 10:06 package.json
|
|
180
|
+
-rw-r--r--@ 1 bagusvnt staff 474254 Jan 15 10:06 pnpm-lock.yaml
|
|
181
|
+
-rw-r--r-- 1 bagusvnt staff 38 Jan 7 10:45 pnpm-workspace.yaml
|
|
182
|
+
-rw-r--r-- 1 bagusvnt staff 566 Jan 7 10:45 tsconfig.json
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Inside `apps/`, each app represents a domain or product boundary (for example `apps/admin/`) and owns its `nuxt.config.ts`, routing, and app-specific modules. Inside `layers/`, you keep shared pieces such as UI components, composables, config, and utilities that can be layered into any app. Each layer is also a Nuxt project with its own `nuxt.config.ts`, so you can expose components, auto-imports, and module options from that layer while keeping a clean separation of concerns.
|
|
186
|
+
|
|
187
|
+
Examples:
|
|
188
|
+
|
|
189
|
+
- `apps/admin/nuxt.config.ts`
|
|
190
|
+
- `layers/ui/nuxt.config.ts`
|
|
191
|
+
|
|
163
192
|
## Development
|
|
164
193
|
|
|
165
194
|
```bash
|
package/console/request.mjs
CHANGED
|
@@ -18,6 +18,21 @@ const availableRules = [
|
|
|
18
18
|
'string',
|
|
19
19
|
'nullable',
|
|
20
20
|
'email',
|
|
21
|
+
'accepted',
|
|
22
|
+
'accepted_if',
|
|
23
|
+
'boolean',
|
|
24
|
+
'filled',
|
|
25
|
+
'present',
|
|
26
|
+
'required_if',
|
|
27
|
+
'required_unless',
|
|
28
|
+
'required_with',
|
|
29
|
+
'required_with_all',
|
|
30
|
+
'required_without',
|
|
31
|
+
'required_without_all',
|
|
32
|
+
'prohibited',
|
|
33
|
+
'prohibited_if',
|
|
34
|
+
'prohibited_unless',
|
|
35
|
+
'prohibits',
|
|
21
36
|
'alpha',
|
|
22
37
|
'alpha_num',
|
|
23
38
|
'alpha_dash',
|
|
@@ -25,16 +40,33 @@ const availableRules = [
|
|
|
25
40
|
'alpha_space',
|
|
26
41
|
'numeric',
|
|
27
42
|
'integer',
|
|
43
|
+
'confirmed',
|
|
44
|
+
'digits',
|
|
45
|
+
'digits_between',
|
|
28
46
|
'bail',
|
|
47
|
+
'date_format',
|
|
29
48
|
'date',
|
|
30
49
|
'before',
|
|
31
50
|
'after',
|
|
51
|
+
'before_or_equal',
|
|
52
|
+
'after_or_equal',
|
|
32
53
|
'array',
|
|
54
|
+
'distinct',
|
|
55
|
+
'in_array',
|
|
56
|
+
'required_array_keys',
|
|
57
|
+
'list',
|
|
33
58
|
'url',
|
|
34
59
|
'ip',
|
|
35
60
|
'hex_color',
|
|
61
|
+
'lowercase',
|
|
62
|
+
'uppercase',
|
|
63
|
+
'uuid',
|
|
64
|
+
'ulid',
|
|
65
|
+
'timezone',
|
|
36
66
|
'min',
|
|
37
67
|
'max',
|
|
68
|
+
'between',
|
|
69
|
+
'size',
|
|
38
70
|
'same',
|
|
39
71
|
'different',
|
|
40
72
|
'gt',
|
|
@@ -42,26 +74,50 @@ const availableRules = [
|
|
|
42
74
|
'lt',
|
|
43
75
|
'lte',
|
|
44
76
|
'in',
|
|
77
|
+
'multiple_of',
|
|
78
|
+
'decimal',
|
|
45
79
|
'starts_with',
|
|
46
80
|
'ends_with',
|
|
47
81
|
'regex',
|
|
48
82
|
];
|
|
49
83
|
|
|
50
84
|
const parameterizedRules = new Set([
|
|
85
|
+
'accepted_if',
|
|
86
|
+
'required_if',
|
|
87
|
+
'required_unless',
|
|
88
|
+
'required_with',
|
|
89
|
+
'required_with_all',
|
|
90
|
+
'required_without',
|
|
91
|
+
'required_without_all',
|
|
92
|
+
'prohibited_if',
|
|
93
|
+
'prohibited_unless',
|
|
94
|
+
'prohibits',
|
|
51
95
|
'min',
|
|
52
96
|
'max',
|
|
97
|
+
'between',
|
|
98
|
+
'size',
|
|
53
99
|
'same',
|
|
54
100
|
'different',
|
|
55
101
|
'gt',
|
|
56
102
|
'gte',
|
|
57
103
|
'lt',
|
|
58
104
|
'lte',
|
|
105
|
+
'digits',
|
|
106
|
+
'digits_between',
|
|
59
107
|
'starts_with',
|
|
60
108
|
'ends_with',
|
|
61
109
|
'regex',
|
|
62
110
|
'before',
|
|
63
111
|
'after',
|
|
112
|
+
'before_or_equal',
|
|
113
|
+
'after_or_equal',
|
|
114
|
+
'date_format',
|
|
64
115
|
'in',
|
|
116
|
+
'in_array',
|
|
117
|
+
'required_array_keys',
|
|
118
|
+
'multiple_of',
|
|
119
|
+
'decimal',
|
|
120
|
+
'timezone',
|
|
65
121
|
]);
|
|
66
122
|
|
|
67
123
|
function toCamelCase(str) {
|
package/dist/module.d.mts
CHANGED
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -34,6 +34,7 @@ const lodashExcludes = [
|
|
|
34
34
|
|
|
35
35
|
const lodashDefaults = {
|
|
36
36
|
enabled: false,
|
|
37
|
+
mode: "factory",
|
|
37
38
|
// prefix: false,
|
|
38
39
|
prefix: "ki",
|
|
39
40
|
prefixSkip: "is",
|
|
@@ -63,6 +64,10 @@ function registerLodashFeature(options, resolve) {
|
|
|
63
64
|
if (!options.enabled) {
|
|
64
65
|
return;
|
|
65
66
|
}
|
|
67
|
+
if (options.mode === "factory") {
|
|
68
|
+
addImports({ name: "lodash", from: resolve("./runtime/lodash-factory") });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
66
71
|
const aliasMap = new Map(options.alias);
|
|
67
72
|
const excludes = new Set(options.exclude);
|
|
68
73
|
const skipPrefixes = toArray(options.prefixSkip);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FormRules } from 'element-plus';
|
|
2
|
-
export type ValidationRule = 'required' | 'string' | 'nullable' | 'email' | `min:${number}` | `max:${number}` | `same:${string}` | 'alpha' | 'alpha_num' | 'alpha_dash' | 'alphanumeric_space' | 'alpha_space' | 'numeric' | 'integer' | 'bail' | `different:${string}` | `gt:${string}` | `gte:${string}` | `lt:${string}` | `lte:${string}` | `starts_with:${string}` | `ends_with:${string}` | `regex:${string}` | 'date' | `before:${string}` | `after:${string}` | 'array' | 'url' | 'ip' | 'hex_color' | `in:${string}
|
|
2
|
+
export type ValidationRule = 'required' | 'string' | 'nullable' | 'email' | 'accepted' | `accepted_if:${string}` | 'boolean' | 'filled' | 'present' | `required_if:${string}` | `required_unless:${string}` | `required_with:${string}` | `required_with_all:${string}` | `required_without:${string}` | `required_without_all:${string}` | 'prohibited' | `prohibited_if:${string}` | `prohibited_unless:${string}` | `prohibits:${string}` | `min:${number}` | `max:${number}` | `between:${number},${number}` | `size:${number}` | `same:${string}` | 'alpha' | 'alpha_num' | 'alpha_dash' | 'alphanumeric_space' | 'alpha_space' | 'numeric' | 'integer' | 'confirmed' | `digits:${number}` | `digits_between:${number},${number}` | 'bail' | `different:${string}` | `gt:${string}` | `gte:${string}` | `lt:${string}` | `lte:${string}` | `starts_with:${string}` | `ends_with:${string}` | `regex:${string}` | `date_format:${string}` | 'date' | `before:${string}` | `after:${string}` | `before_or_equal:${string}` | `after_or_equal:${string}` | 'array' | 'distinct' | `in_array:${string}` | `required_array_keys:${string}` | 'list' | 'url' | 'ip' | 'hex_color' | `in:${string}` | `multiple_of:${number}` | `decimal:${number}` | `decimal:${number},${number}` | 'lowercase' | 'uppercase' | 'uuid' | 'ulid' | `timezone:${string}` | 'timezone';
|
|
3
3
|
export interface ValidationOptions {
|
|
4
4
|
messages?: Record<string, string>;
|
|
5
5
|
attributes?: Record<string, string>;
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
const createSameAsValidator = (otherField, formState, message) => {
|
|
2
2
|
return (_rule, value, callback) => {
|
|
3
|
-
const getNestedValue = (obj, path) => {
|
|
4
|
-
return path.split(".").reduce((current, key) => current?.[key], obj);
|
|
5
|
-
};
|
|
6
3
|
const otherValue = getNestedValue(formState, otherField);
|
|
7
4
|
if (value !== otherValue) {
|
|
8
5
|
callback(new Error(message));
|
|
@@ -11,6 +8,117 @@ const createSameAsValidator = (otherField, formState, message) => {
|
|
|
11
8
|
}
|
|
12
9
|
};
|
|
13
10
|
};
|
|
11
|
+
const getNestedValue = (obj, path) => {
|
|
12
|
+
return path.split(".").reduce((current, key) => current?.[key], obj);
|
|
13
|
+
};
|
|
14
|
+
const isEmptyValue = (value) => value === null || value === void 0 || value === "";
|
|
15
|
+
const isFilledValue = (value) => {
|
|
16
|
+
if (isEmptyValue(value)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
return value.length > 0;
|
|
21
|
+
}
|
|
22
|
+
if (typeof value === "object") {
|
|
23
|
+
return Object.keys(value).length > 0;
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
};
|
|
27
|
+
const hasNestedKey = (obj, path) => {
|
|
28
|
+
return path.split(".").every((key) => {
|
|
29
|
+
if (obj === null || obj === void 0) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
33
|
+
obj = obj[key];
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
const normalizeComparisonValue = (value, expected) => {
|
|
40
|
+
if (expected === "null") {
|
|
41
|
+
return value === null || value === void 0;
|
|
42
|
+
}
|
|
43
|
+
if (expected === "true") {
|
|
44
|
+
return value === true || value === "true" || value === 1 || value === "1";
|
|
45
|
+
}
|
|
46
|
+
if (expected === "false") {
|
|
47
|
+
return value === false || value === "false" || value === 0 || value === "0";
|
|
48
|
+
}
|
|
49
|
+
return String(value) === expected;
|
|
50
|
+
};
|
|
51
|
+
const hasAnyFilled = (formState, fields) => {
|
|
52
|
+
return fields.some((field) => isFilledValue(getNestedValue(formState, field)));
|
|
53
|
+
};
|
|
54
|
+
const hasAllFilled = (formState, fields) => {
|
|
55
|
+
return fields.every((field) => isFilledValue(getNestedValue(formState, field)));
|
|
56
|
+
};
|
|
57
|
+
const getConfirmationField = (fieldName) => {
|
|
58
|
+
const segments = fieldName.split(".");
|
|
59
|
+
const last = segments.pop() || fieldName;
|
|
60
|
+
segments.push(`${last}_confirmation`);
|
|
61
|
+
return segments.join(".");
|
|
62
|
+
};
|
|
63
|
+
const getSizeValue = (value) => {
|
|
64
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
if (typeof value === "string") {
|
|
68
|
+
return value.length;
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(value)) {
|
|
71
|
+
return value.length;
|
|
72
|
+
}
|
|
73
|
+
if (value && typeof value === "object") {
|
|
74
|
+
return Object.keys(value).length;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
};
|
|
78
|
+
const isAcceptedValue = (value) => {
|
|
79
|
+
if (typeof value === "string") {
|
|
80
|
+
return ["yes", "on", "1", "true"].includes(value.toLowerCase());
|
|
81
|
+
}
|
|
82
|
+
return value === true || value === 1;
|
|
83
|
+
};
|
|
84
|
+
const isBooleanValue = (value) => {
|
|
85
|
+
return value === true || value === false || value === 0 || value === 1 || value === "0" || value === "1";
|
|
86
|
+
};
|
|
87
|
+
const createDateFormatValidator = (format, message) => {
|
|
88
|
+
const tokenMap = {
|
|
89
|
+
Y: "\\d{4}",
|
|
90
|
+
m: "(0[1-9]|1[0-2])",
|
|
91
|
+
d: "(0[1-9]|[12]\\d|3[01])",
|
|
92
|
+
H: "([01]\\d|2[0-3])",
|
|
93
|
+
i: "([0-5]\\d)",
|
|
94
|
+
s: "([0-5]\\d)"
|
|
95
|
+
};
|
|
96
|
+
let pattern = "^";
|
|
97
|
+
for (let i = 0; i < format.length; i += 1) {
|
|
98
|
+
const char = format[i];
|
|
99
|
+
if (char === "\\" && i + 1 < format.length) {
|
|
100
|
+
pattern += format[i + 1].replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
101
|
+
i += 1;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (tokenMap[char]) {
|
|
105
|
+
pattern += tokenMap[char];
|
|
106
|
+
} else {
|
|
107
|
+
pattern += char.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
pattern += "$";
|
|
111
|
+
const regex = new RegExp(pattern);
|
|
112
|
+
return (_rule, value, callback) => {
|
|
113
|
+
if (isEmptyValue(value)) {
|
|
114
|
+
return callback();
|
|
115
|
+
}
|
|
116
|
+
if (typeof value !== "string" || !regex.test(value)) {
|
|
117
|
+
return callback(new Error(message));
|
|
118
|
+
}
|
|
119
|
+
callback();
|
|
120
|
+
};
|
|
121
|
+
};
|
|
14
122
|
const createPatternValidator = (pattern, message) => {
|
|
15
123
|
return (_rule, value, callback) => {
|
|
16
124
|
if (value && !pattern.test(value)) {
|
|
@@ -186,6 +294,16 @@ export const createValidationRules = (definitions, formState, options = {}) => {
|
|
|
186
294
|
} else {
|
|
187
295
|
laravelRules = fieldDefinition;
|
|
188
296
|
}
|
|
297
|
+
const hasNullable = laravelRules.includes("nullable");
|
|
298
|
+
const hasRequired = laravelRules.includes("required");
|
|
299
|
+
if (hasNullable && hasRequired) {
|
|
300
|
+
throw new Error(`Invalid validation rules for "${fieldName}": "nullable" cannot be combined with "required".`);
|
|
301
|
+
}
|
|
302
|
+
const fieldValue = getNestedValue(dataToValidate, fieldName);
|
|
303
|
+
if (hasNullable && !hasRequired && isEmptyValue(fieldValue)) {
|
|
304
|
+
rules[fieldName] = [];
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
189
307
|
const fieldRules = [];
|
|
190
308
|
const attributeName = attributes[fieldName] || fieldName;
|
|
191
309
|
laravelRules.forEach((ruleString) => {
|
|
@@ -200,6 +318,289 @@ export const createValidationRules = (definitions, formState, options = {}) => {
|
|
|
200
318
|
trigger: pickTrigger(fieldName, "blur")
|
|
201
319
|
});
|
|
202
320
|
break;
|
|
321
|
+
case "accepted":
|
|
322
|
+
fieldRules.push({
|
|
323
|
+
validator: (_rule, value, callback) => {
|
|
324
|
+
if (isAcceptedValue(value)) {
|
|
325
|
+
return callback();
|
|
326
|
+
}
|
|
327
|
+
callback(new Error(getMessage(fieldName, "accepted", `${attributeName} must be accepted`)));
|
|
328
|
+
},
|
|
329
|
+
trigger: pickTrigger(fieldName, "change")
|
|
330
|
+
});
|
|
331
|
+
break;
|
|
332
|
+
case "accepted_if": {
|
|
333
|
+
if (!paramValues.length) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const otherField = firstParam;
|
|
337
|
+
const expectedValues = paramValues.slice(1);
|
|
338
|
+
fieldRules.push({
|
|
339
|
+
validator: (_rule, value, callback) => {
|
|
340
|
+
const otherValue = getNestedValue(dataToValidate, otherField);
|
|
341
|
+
const shouldRequire = expectedValues.some((expected) => normalizeComparisonValue(otherValue, expected));
|
|
342
|
+
if (!shouldRequire) {
|
|
343
|
+
return callback();
|
|
344
|
+
}
|
|
345
|
+
if (!isAcceptedValue(value)) {
|
|
346
|
+
return callback(new Error(getMessage(fieldName, "accepted_if", `${attributeName} must be accepted`)));
|
|
347
|
+
}
|
|
348
|
+
callback();
|
|
349
|
+
},
|
|
350
|
+
trigger: pickTrigger(fieldName, "change")
|
|
351
|
+
});
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
case "boolean":
|
|
355
|
+
fieldRules.push({
|
|
356
|
+
validator: (_rule, value, callback) => {
|
|
357
|
+
if (isEmptyValue(value)) {
|
|
358
|
+
return callback();
|
|
359
|
+
}
|
|
360
|
+
if (!isBooleanValue(value)) {
|
|
361
|
+
return callback(new Error(getMessage(fieldName, "boolean", `${attributeName} must be true or false`)));
|
|
362
|
+
}
|
|
363
|
+
callback();
|
|
364
|
+
},
|
|
365
|
+
trigger: pickTrigger(fieldName, "change")
|
|
366
|
+
});
|
|
367
|
+
break;
|
|
368
|
+
case "filled":
|
|
369
|
+
fieldRules.push({
|
|
370
|
+
validator: (_rule, value, callback) => {
|
|
371
|
+
if (!hasNestedKey(dataToValidate, fieldName)) {
|
|
372
|
+
return callback();
|
|
373
|
+
}
|
|
374
|
+
if (!isFilledValue(value)) {
|
|
375
|
+
return callback(new Error(getMessage(fieldName, "filled", `${attributeName} must not be empty`)));
|
|
376
|
+
}
|
|
377
|
+
callback();
|
|
378
|
+
},
|
|
379
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
380
|
+
});
|
|
381
|
+
break;
|
|
382
|
+
case "present":
|
|
383
|
+
fieldRules.push({
|
|
384
|
+
validator: (_rule, _value, callback) => {
|
|
385
|
+
if (!hasNestedKey(dataToValidate, fieldName)) {
|
|
386
|
+
return callback(new Error(getMessage(fieldName, "present", `${attributeName} must be present`)));
|
|
387
|
+
}
|
|
388
|
+
callback();
|
|
389
|
+
},
|
|
390
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
391
|
+
});
|
|
392
|
+
break;
|
|
393
|
+
case "required_if": {
|
|
394
|
+
if (!paramValues.length) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const otherField = firstParam;
|
|
398
|
+
const expectedValues = paramValues.slice(1);
|
|
399
|
+
fieldRules.push({
|
|
400
|
+
validator: (_rule, value, callback) => {
|
|
401
|
+
const otherValue = getNestedValue(dataToValidate, otherField);
|
|
402
|
+
const shouldRequire = expectedValues.some((expected) => normalizeComparisonValue(otherValue, expected));
|
|
403
|
+
if (!shouldRequire) {
|
|
404
|
+
return callback();
|
|
405
|
+
}
|
|
406
|
+
if (!isFilledValue(value)) {
|
|
407
|
+
return callback(new Error(getMessage(fieldName, "required_if", `${attributeName} is required`)));
|
|
408
|
+
}
|
|
409
|
+
callback();
|
|
410
|
+
},
|
|
411
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
412
|
+
});
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
case "required_unless": {
|
|
416
|
+
if (!paramValues.length) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const otherField = firstParam;
|
|
420
|
+
const expectedValues = paramValues.slice(1);
|
|
421
|
+
fieldRules.push({
|
|
422
|
+
validator: (_rule, value, callback) => {
|
|
423
|
+
const otherValue = getNestedValue(dataToValidate, otherField);
|
|
424
|
+
const isExcluded = expectedValues.some((expected) => normalizeComparisonValue(otherValue, expected));
|
|
425
|
+
if (isExcluded) {
|
|
426
|
+
return callback();
|
|
427
|
+
}
|
|
428
|
+
if (!isFilledValue(value)) {
|
|
429
|
+
return callback(new Error(getMessage(fieldName, "required_unless", `${attributeName} is required`)));
|
|
430
|
+
}
|
|
431
|
+
callback();
|
|
432
|
+
},
|
|
433
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
434
|
+
});
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
case "required_with": {
|
|
438
|
+
if (!paramValues.length) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const otherFields = paramValues;
|
|
442
|
+
fieldRules.push({
|
|
443
|
+
validator: (_rule, value, callback) => {
|
|
444
|
+
if (!hasAnyFilled(dataToValidate, otherFields)) {
|
|
445
|
+
return callback();
|
|
446
|
+
}
|
|
447
|
+
if (!isFilledValue(value)) {
|
|
448
|
+
return callback(new Error(getMessage(fieldName, "required_with", `${attributeName} is required`)));
|
|
449
|
+
}
|
|
450
|
+
callback();
|
|
451
|
+
},
|
|
452
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
453
|
+
});
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
case "required_with_all": {
|
|
457
|
+
if (!paramValues.length) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const otherFields = paramValues;
|
|
461
|
+
fieldRules.push({
|
|
462
|
+
validator: (_rule, value, callback) => {
|
|
463
|
+
if (!hasAllFilled(dataToValidate, otherFields)) {
|
|
464
|
+
return callback();
|
|
465
|
+
}
|
|
466
|
+
if (!isFilledValue(value)) {
|
|
467
|
+
return callback(new Error(getMessage(fieldName, "required_with_all", `${attributeName} is required`)));
|
|
468
|
+
}
|
|
469
|
+
callback();
|
|
470
|
+
},
|
|
471
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
472
|
+
});
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
case "required_without": {
|
|
476
|
+
if (!paramValues.length) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
const otherFields = paramValues;
|
|
480
|
+
fieldRules.push({
|
|
481
|
+
validator: (_rule, value, callback) => {
|
|
482
|
+
if (hasAnyFilled(dataToValidate, otherFields)) {
|
|
483
|
+
return callback();
|
|
484
|
+
}
|
|
485
|
+
if (!isFilledValue(value)) {
|
|
486
|
+
return callback(new Error(getMessage(fieldName, "required_without", `${attributeName} is required`)));
|
|
487
|
+
}
|
|
488
|
+
callback();
|
|
489
|
+
},
|
|
490
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
491
|
+
});
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
case "required_without_all": {
|
|
495
|
+
if (!paramValues.length) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const otherFields = paramValues;
|
|
499
|
+
fieldRules.push({
|
|
500
|
+
validator: (_rule, value, callback) => {
|
|
501
|
+
if (hasAllFilled(dataToValidate, otherFields)) {
|
|
502
|
+
return callback();
|
|
503
|
+
}
|
|
504
|
+
if (!isFilledValue(value)) {
|
|
505
|
+
return callback(new Error(getMessage(fieldName, "required_without_all", `${attributeName} is required`)));
|
|
506
|
+
}
|
|
507
|
+
callback();
|
|
508
|
+
},
|
|
509
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
510
|
+
});
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
case "prohibited":
|
|
514
|
+
fieldRules.push({
|
|
515
|
+
validator: (_rule, value, callback) => {
|
|
516
|
+
if (isFilledValue(value)) {
|
|
517
|
+
return callback(new Error(getMessage(fieldName, "prohibited", `${attributeName} must be empty`)));
|
|
518
|
+
}
|
|
519
|
+
callback();
|
|
520
|
+
},
|
|
521
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
522
|
+
});
|
|
523
|
+
break;
|
|
524
|
+
case "prohibited_if": {
|
|
525
|
+
if (!paramValues.length) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
const otherField = firstParam;
|
|
529
|
+
const expectedValues = paramValues.slice(1);
|
|
530
|
+
fieldRules.push({
|
|
531
|
+
validator: (_rule, value, callback) => {
|
|
532
|
+
const otherValue = getNestedValue(dataToValidate, otherField);
|
|
533
|
+
const shouldProhibit = expectedValues.some((expected) => normalizeComparisonValue(otherValue, expected));
|
|
534
|
+
if (!shouldProhibit) {
|
|
535
|
+
return callback();
|
|
536
|
+
}
|
|
537
|
+
if (isFilledValue(value)) {
|
|
538
|
+
return callback(new Error(getMessage(fieldName, "prohibited_if", `${attributeName} must be empty`)));
|
|
539
|
+
}
|
|
540
|
+
callback();
|
|
541
|
+
},
|
|
542
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
543
|
+
});
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
case "prohibited_unless": {
|
|
547
|
+
if (!paramValues.length) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const otherField = firstParam;
|
|
551
|
+
const expectedValues = paramValues.slice(1);
|
|
552
|
+
fieldRules.push({
|
|
553
|
+
validator: (_rule, value, callback) => {
|
|
554
|
+
const otherValue = getNestedValue(dataToValidate, otherField);
|
|
555
|
+
const isAllowed = expectedValues.some((expected) => normalizeComparisonValue(otherValue, expected));
|
|
556
|
+
if (isAllowed) {
|
|
557
|
+
return callback();
|
|
558
|
+
}
|
|
559
|
+
if (isFilledValue(value)) {
|
|
560
|
+
return callback(new Error(getMessage(fieldName, "prohibited_unless", `${attributeName} must be empty`)));
|
|
561
|
+
}
|
|
562
|
+
callback();
|
|
563
|
+
},
|
|
564
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
565
|
+
});
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
case "prohibits": {
|
|
569
|
+
if (!paramValues.length) {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
const otherFields = paramValues;
|
|
573
|
+
fieldRules.push({
|
|
574
|
+
validator: (_rule, value, callback) => {
|
|
575
|
+
if (!isFilledValue(value)) {
|
|
576
|
+
return callback();
|
|
577
|
+
}
|
|
578
|
+
const hasAny = otherFields.some((field) => isFilledValue(getNestedValue(dataToValidate, field)));
|
|
579
|
+
if (hasAny) {
|
|
580
|
+
return callback(new Error(getMessage(fieldName, "prohibits", `${attributeName} prohibits ${otherFields.join(", ")}`)));
|
|
581
|
+
}
|
|
582
|
+
callback();
|
|
583
|
+
},
|
|
584
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
585
|
+
});
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
case "string":
|
|
589
|
+
fieldRules.push({
|
|
590
|
+
validator: (_rule, value, callback) => {
|
|
591
|
+
if (value === null || value === void 0) {
|
|
592
|
+
return callback();
|
|
593
|
+
}
|
|
594
|
+
if (typeof value !== "string") {
|
|
595
|
+
return callback(new Error(getMessage(fieldName, "string", `${attributeName} must be a string`)));
|
|
596
|
+
}
|
|
597
|
+
callback();
|
|
598
|
+
},
|
|
599
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
600
|
+
});
|
|
601
|
+
break;
|
|
602
|
+
case "nullable":
|
|
603
|
+
break;
|
|
203
604
|
case "email":
|
|
204
605
|
fieldRules.push({
|
|
205
606
|
type: "email",
|
|
@@ -229,6 +630,53 @@ export const createValidationRules = (definitions, formState, options = {}) => {
|
|
|
229
630
|
transform: (value) => value?.trim()
|
|
230
631
|
});
|
|
231
632
|
break;
|
|
633
|
+
case "between": {
|
|
634
|
+
if (paramValues.length < 2) {
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
const minValue = Number(paramValues[0]);
|
|
638
|
+
const maxValue = Number(paramValues[1]);
|
|
639
|
+
fieldRules.push({
|
|
640
|
+
validator: (_rule, value, callback) => {
|
|
641
|
+
if (isEmptyValue(value)) {
|
|
642
|
+
return callback();
|
|
643
|
+
}
|
|
644
|
+
const size = getSizeValue(value);
|
|
645
|
+
if (size === null || Number.isNaN(minValue) || Number.isNaN(maxValue)) {
|
|
646
|
+
return callback(new Error(getMessage(fieldName, "between", `${attributeName} is invalid`)));
|
|
647
|
+
}
|
|
648
|
+
if (size < minValue || size > maxValue) {
|
|
649
|
+
return callback(new Error(getMessage(fieldName, "between", `${attributeName} must be between ${minValue} and ${maxValue}`)));
|
|
650
|
+
}
|
|
651
|
+
callback();
|
|
652
|
+
},
|
|
653
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
654
|
+
});
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
657
|
+
case "size": {
|
|
658
|
+
if (!firstParam) {
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const expected = Number(firstParam);
|
|
662
|
+
fieldRules.push({
|
|
663
|
+
validator: (_rule, value, callback) => {
|
|
664
|
+
if (isEmptyValue(value)) {
|
|
665
|
+
return callback();
|
|
666
|
+
}
|
|
667
|
+
const size = getSizeValue(value);
|
|
668
|
+
if (size === null || Number.isNaN(expected)) {
|
|
669
|
+
return callback(new Error(getMessage(fieldName, "size", `${attributeName} is invalid`)));
|
|
670
|
+
}
|
|
671
|
+
if (size !== expected) {
|
|
672
|
+
return callback(new Error(getMessage(fieldName, "size", `${attributeName} must be ${expected}`)));
|
|
673
|
+
}
|
|
674
|
+
callback();
|
|
675
|
+
},
|
|
676
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
677
|
+
});
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
232
680
|
case "same": {
|
|
233
681
|
if (!firstParam) {
|
|
234
682
|
return;
|
|
@@ -251,6 +699,15 @@ export const createValidationRules = (definitions, formState, options = {}) => {
|
|
|
251
699
|
});
|
|
252
700
|
break;
|
|
253
701
|
}
|
|
702
|
+
case "date_format":
|
|
703
|
+
if (!params) {
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
fieldRules.push({
|
|
707
|
+
validator: createDateFormatValidator(params, getMessage(fieldName, "date_format", `${attributeName} does not match the format ${params}`)),
|
|
708
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
709
|
+
});
|
|
710
|
+
break;
|
|
254
711
|
case "date":
|
|
255
712
|
fieldRules.push({
|
|
256
713
|
validator: createDateValidator(getMessage(fieldName, "date", `${attributeName} must be a valid date in YYYY-MM-DD format`)),
|
|
@@ -283,6 +740,32 @@ export const createValidationRules = (definitions, formState, options = {}) => {
|
|
|
283
740
|
trigger: pickTrigger(fieldName, "blur")
|
|
284
741
|
});
|
|
285
742
|
break;
|
|
743
|
+
case "before_or_equal":
|
|
744
|
+
if (!firstParam) {
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
fieldRules.push({
|
|
748
|
+
validator: createDateComparisonValidator(
|
|
749
|
+
firstParam,
|
|
750
|
+
getMessage(fieldName, "before_or_equal", `${attributeName} must be a date before or equal to ${firstParam}`),
|
|
751
|
+
(valueDate, compareDate) => valueDate <= compareDate
|
|
752
|
+
),
|
|
753
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
754
|
+
});
|
|
755
|
+
break;
|
|
756
|
+
case "after_or_equal":
|
|
757
|
+
if (!firstParam) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
fieldRules.push({
|
|
761
|
+
validator: createDateComparisonValidator(
|
|
762
|
+
firstParam,
|
|
763
|
+
getMessage(fieldName, "after_or_equal", `${attributeName} must be a date after or equal to ${firstParam}`),
|
|
764
|
+
(valueDate, compareDate) => valueDate >= compareDate
|
|
765
|
+
),
|
|
766
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
767
|
+
});
|
|
768
|
+
break;
|
|
286
769
|
case "alpha":
|
|
287
770
|
fieldRules.push({
|
|
288
771
|
validator: createPatternValidator(/^[a-z]+$/i, getMessage(fieldName, "alpha", `${attributeName} may only contain letters`)),
|
|
@@ -319,6 +802,88 @@ export const createValidationRules = (definitions, formState, options = {}) => {
|
|
|
319
802
|
trigger: pickTrigger(fieldName, "change")
|
|
320
803
|
});
|
|
321
804
|
break;
|
|
805
|
+
case "distinct":
|
|
806
|
+
fieldRules.push({
|
|
807
|
+
validator: (_rule, value, callback) => {
|
|
808
|
+
if (isEmptyValue(value)) {
|
|
809
|
+
return callback();
|
|
810
|
+
}
|
|
811
|
+
if (!Array.isArray(value)) {
|
|
812
|
+
return callback(new Error(getMessage(fieldName, "distinct", `${attributeName} must be an array`)));
|
|
813
|
+
}
|
|
814
|
+
const set = new Set(value.map((item) => JSON.stringify(item)));
|
|
815
|
+
if (set.size !== value.length) {
|
|
816
|
+
return callback(new Error(getMessage(fieldName, "distinct", `${attributeName} must not contain duplicates`)));
|
|
817
|
+
}
|
|
818
|
+
callback();
|
|
819
|
+
},
|
|
820
|
+
trigger: pickTrigger(fieldName, "change")
|
|
821
|
+
});
|
|
822
|
+
break;
|
|
823
|
+
case "in_array": {
|
|
824
|
+
if (!firstParam) {
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
const otherField = firstParam;
|
|
828
|
+
fieldRules.push({
|
|
829
|
+
validator: (_rule, value, callback) => {
|
|
830
|
+
if (isEmptyValue(value)) {
|
|
831
|
+
return callback();
|
|
832
|
+
}
|
|
833
|
+
const haystack = getNestedValue(dataToValidate, otherField);
|
|
834
|
+
if (!Array.isArray(haystack)) {
|
|
835
|
+
return callback(new Error(getMessage(fieldName, "in_array", `${attributeName} must be one of the values in ${otherField}`)));
|
|
836
|
+
}
|
|
837
|
+
if (!haystack.includes(value)) {
|
|
838
|
+
return callback(new Error(getMessage(fieldName, "in_array", `${attributeName} must be one of the values in ${otherField}`)));
|
|
839
|
+
}
|
|
840
|
+
callback();
|
|
841
|
+
},
|
|
842
|
+
trigger: pickTrigger(fieldName, "change")
|
|
843
|
+
});
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
case "required_array_keys": {
|
|
847
|
+
if (!paramValues.length) {
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
const requiredKeys = paramValues;
|
|
851
|
+
fieldRules.push({
|
|
852
|
+
validator: (_rule, value, callback) => {
|
|
853
|
+
if (isEmptyValue(value)) {
|
|
854
|
+
return callback(new Error(getMessage(fieldName, "required_array_keys", `${attributeName} must include all required keys`)));
|
|
855
|
+
}
|
|
856
|
+
if (typeof value !== "object") {
|
|
857
|
+
return callback(new Error(getMessage(fieldName, "required_array_keys", `${attributeName} must be an array or object`)));
|
|
858
|
+
}
|
|
859
|
+
const missing = requiredKeys.filter((key) => !Object.prototype.hasOwnProperty.call(value, key));
|
|
860
|
+
if (missing.length) {
|
|
861
|
+
return callback(new Error(getMessage(fieldName, "required_array_keys", `${attributeName} must include keys: ${missing.join(", ")}`)));
|
|
862
|
+
}
|
|
863
|
+
callback();
|
|
864
|
+
},
|
|
865
|
+
trigger: pickTrigger(fieldName, "change")
|
|
866
|
+
});
|
|
867
|
+
break;
|
|
868
|
+
}
|
|
869
|
+
case "list":
|
|
870
|
+
fieldRules.push({
|
|
871
|
+
validator: (_rule, value, callback) => {
|
|
872
|
+
if (isEmptyValue(value)) {
|
|
873
|
+
return callback();
|
|
874
|
+
}
|
|
875
|
+
if (!Array.isArray(value)) {
|
|
876
|
+
return callback(new Error(getMessage(fieldName, "list", `${attributeName} must be a list`)));
|
|
877
|
+
}
|
|
878
|
+
const isSequential = value.every((_, index) => Object.prototype.hasOwnProperty.call(value, index));
|
|
879
|
+
if (!isSequential) {
|
|
880
|
+
return callback(new Error(getMessage(fieldName, "list", `${attributeName} must be a list with sequential indices`)));
|
|
881
|
+
}
|
|
882
|
+
callback();
|
|
883
|
+
},
|
|
884
|
+
trigger: pickTrigger(fieldName, "change")
|
|
885
|
+
});
|
|
886
|
+
break;
|
|
322
887
|
case "starts_with": {
|
|
323
888
|
if (!paramValues.length) {
|
|
324
889
|
return;
|
|
@@ -368,6 +933,85 @@ export const createValidationRules = (definitions, formState, options = {}) => {
|
|
|
368
933
|
trigger: pickTrigger(fieldName, "blur")
|
|
369
934
|
});
|
|
370
935
|
break;
|
|
936
|
+
case "lowercase":
|
|
937
|
+
fieldRules.push({
|
|
938
|
+
validator: (_rule, value, callback) => {
|
|
939
|
+
if (isEmptyValue(value)) {
|
|
940
|
+
return callback();
|
|
941
|
+
}
|
|
942
|
+
if (typeof value !== "string" || value !== value.toLowerCase()) {
|
|
943
|
+
return callback(new Error(getMessage(fieldName, "lowercase", `${attributeName} must be lowercase`)));
|
|
944
|
+
}
|
|
945
|
+
callback();
|
|
946
|
+
},
|
|
947
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
948
|
+
});
|
|
949
|
+
break;
|
|
950
|
+
case "uppercase":
|
|
951
|
+
fieldRules.push({
|
|
952
|
+
validator: (_rule, value, callback) => {
|
|
953
|
+
if (isEmptyValue(value)) {
|
|
954
|
+
return callback();
|
|
955
|
+
}
|
|
956
|
+
if (typeof value !== "string" || value !== value.toUpperCase()) {
|
|
957
|
+
return callback(new Error(getMessage(fieldName, "uppercase", `${attributeName} must be uppercase`)));
|
|
958
|
+
}
|
|
959
|
+
callback();
|
|
960
|
+
},
|
|
961
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
962
|
+
});
|
|
963
|
+
break;
|
|
964
|
+
case "uuid":
|
|
965
|
+
fieldRules.push({
|
|
966
|
+
validator: (_rule, value, callback) => {
|
|
967
|
+
if (isEmptyValue(value)) {
|
|
968
|
+
return callback();
|
|
969
|
+
}
|
|
970
|
+
const text = String(value);
|
|
971
|
+
const pattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
972
|
+
if (!pattern.test(text)) {
|
|
973
|
+
return callback(new Error(getMessage(fieldName, "uuid", `${attributeName} must be a valid UUID`)));
|
|
974
|
+
}
|
|
975
|
+
callback();
|
|
976
|
+
},
|
|
977
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
978
|
+
});
|
|
979
|
+
break;
|
|
980
|
+
case "ulid":
|
|
981
|
+
fieldRules.push({
|
|
982
|
+
validator: (_rule, value, callback) => {
|
|
983
|
+
if (isEmptyValue(value)) {
|
|
984
|
+
return callback();
|
|
985
|
+
}
|
|
986
|
+
const text = String(value);
|
|
987
|
+
const pattern = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
|
|
988
|
+
if (!pattern.test(text)) {
|
|
989
|
+
return callback(new Error(getMessage(fieldName, "ulid", `${attributeName} must be a valid ULID`)));
|
|
990
|
+
}
|
|
991
|
+
callback();
|
|
992
|
+
},
|
|
993
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
994
|
+
});
|
|
995
|
+
break;
|
|
996
|
+
case "timezone":
|
|
997
|
+
fieldRules.push({
|
|
998
|
+
validator: (_rule, value, callback) => {
|
|
999
|
+
if (isEmptyValue(value)) {
|
|
1000
|
+
return callback();
|
|
1001
|
+
}
|
|
1002
|
+
if (typeof value !== "string") {
|
|
1003
|
+
return callback(new Error(getMessage(fieldName, "timezone", `${attributeName} must be a valid timezone`)));
|
|
1004
|
+
}
|
|
1005
|
+
try {
|
|
1006
|
+
Intl.DateTimeFormat("en-US", { timeZone: value }).format();
|
|
1007
|
+
} catch {
|
|
1008
|
+
return callback(new Error(getMessage(fieldName, "timezone", `${attributeName} must be a valid timezone`)));
|
|
1009
|
+
}
|
|
1010
|
+
callback();
|
|
1011
|
+
},
|
|
1012
|
+
trigger: pickTrigger(fieldName, "change")
|
|
1013
|
+
});
|
|
1014
|
+
break;
|
|
371
1015
|
case "in": {
|
|
372
1016
|
if (!paramValues.length) {
|
|
373
1017
|
return;
|
|
@@ -379,6 +1023,51 @@ export const createValidationRules = (definitions, formState, options = {}) => {
|
|
|
379
1023
|
});
|
|
380
1024
|
break;
|
|
381
1025
|
}
|
|
1026
|
+
case "multiple_of": {
|
|
1027
|
+
if (!firstParam) {
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
const factor = Number(firstParam);
|
|
1031
|
+
fieldRules.push({
|
|
1032
|
+
validator: (_rule, value, callback) => {
|
|
1033
|
+
if (isEmptyValue(value)) {
|
|
1034
|
+
return callback();
|
|
1035
|
+
}
|
|
1036
|
+
const numeric = Number(value);
|
|
1037
|
+
if (Number.isNaN(numeric) || Number.isNaN(factor) || factor === 0 || numeric % factor !== 0) {
|
|
1038
|
+
return callback(new Error(getMessage(fieldName, "multiple_of", `${attributeName} must be a multiple of ${factor}`)));
|
|
1039
|
+
}
|
|
1040
|
+
callback();
|
|
1041
|
+
},
|
|
1042
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
1043
|
+
});
|
|
1044
|
+
break;
|
|
1045
|
+
}
|
|
1046
|
+
case "decimal": {
|
|
1047
|
+
if (!paramValues.length) {
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
const minDecimals = Number(paramValues[0]);
|
|
1051
|
+
const maxDecimals = paramValues.length > 1 ? Number(paramValues[1]) : minDecimals;
|
|
1052
|
+
fieldRules.push({
|
|
1053
|
+
validator: (_rule, value, callback) => {
|
|
1054
|
+
if (isEmptyValue(value)) {
|
|
1055
|
+
return callback();
|
|
1056
|
+
}
|
|
1057
|
+
const text = String(value);
|
|
1058
|
+
if (!/^-?\d+(?:\.\d+)?$/.test(text)) {
|
|
1059
|
+
return callback(new Error(getMessage(fieldName, "decimal", `${attributeName} must be a decimal`)));
|
|
1060
|
+
}
|
|
1061
|
+
const decimals = text.includes(".") ? text.split(".")[1].length : 0;
|
|
1062
|
+
if (decimals < minDecimals || decimals > maxDecimals) {
|
|
1063
|
+
return callback(new Error(getMessage(fieldName, "decimal", `${attributeName} must have between ${minDecimals} and ${maxDecimals} decimal places`)));
|
|
1064
|
+
}
|
|
1065
|
+
callback();
|
|
1066
|
+
},
|
|
1067
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
1068
|
+
});
|
|
1069
|
+
break;
|
|
1070
|
+
}
|
|
382
1071
|
case "numeric":
|
|
383
1072
|
fieldRules.push({
|
|
384
1073
|
validator: createNumericValidator(getMessage(fieldName, "numeric", `${attributeName} must be a number`)),
|
|
@@ -391,6 +1080,64 @@ export const createValidationRules = (definitions, formState, options = {}) => {
|
|
|
391
1080
|
trigger: pickTrigger(fieldName, "blur")
|
|
392
1081
|
});
|
|
393
1082
|
break;
|
|
1083
|
+
case "confirmed": {
|
|
1084
|
+
const confirmationField = getConfirmationField(fieldName);
|
|
1085
|
+
fieldRules.push({
|
|
1086
|
+
validator: (_rule, value, callback) => {
|
|
1087
|
+
if (isEmptyValue(value)) {
|
|
1088
|
+
return callback();
|
|
1089
|
+
}
|
|
1090
|
+
const confirmationValue = getNestedValue(dataToValidate, confirmationField);
|
|
1091
|
+
if (value !== confirmationValue) {
|
|
1092
|
+
return callback(new Error(getMessage(fieldName, "confirmed", `${attributeName} confirmation does not match`)));
|
|
1093
|
+
}
|
|
1094
|
+
callback();
|
|
1095
|
+
},
|
|
1096
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
1097
|
+
});
|
|
1098
|
+
break;
|
|
1099
|
+
}
|
|
1100
|
+
case "digits": {
|
|
1101
|
+
if (!firstParam) {
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
const expectedLength = Number(firstParam);
|
|
1105
|
+
fieldRules.push({
|
|
1106
|
+
validator: (_rule, value, callback) => {
|
|
1107
|
+
if (isEmptyValue(value)) {
|
|
1108
|
+
return callback();
|
|
1109
|
+
}
|
|
1110
|
+
const text = String(value);
|
|
1111
|
+
if (!/^\d+$/.test(text) || text.length !== expectedLength) {
|
|
1112
|
+
return callback(new Error(getMessage(fieldName, "digits", `${attributeName} must be ${expectedLength} digits`)));
|
|
1113
|
+
}
|
|
1114
|
+
callback();
|
|
1115
|
+
},
|
|
1116
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
1117
|
+
});
|
|
1118
|
+
break;
|
|
1119
|
+
}
|
|
1120
|
+
case "digits_between": {
|
|
1121
|
+
if (paramValues.length < 2) {
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
const minLength = Number(paramValues[0]);
|
|
1125
|
+
const maxLength = Number(paramValues[1]);
|
|
1126
|
+
fieldRules.push({
|
|
1127
|
+
validator: (_rule, value, callback) => {
|
|
1128
|
+
if (isEmptyValue(value)) {
|
|
1129
|
+
return callback();
|
|
1130
|
+
}
|
|
1131
|
+
const text = String(value);
|
|
1132
|
+
if (!/^\d+$/.test(text) || text.length < minLength || text.length > maxLength) {
|
|
1133
|
+
return callback(new Error(getMessage(fieldName, "digits_between", `${attributeName} must be between ${minLength} and ${maxLength} digits`)));
|
|
1134
|
+
}
|
|
1135
|
+
callback();
|
|
1136
|
+
},
|
|
1137
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
1138
|
+
});
|
|
1139
|
+
break;
|
|
1140
|
+
}
|
|
394
1141
|
case "gt": {
|
|
395
1142
|
if (!firstParam) {
|
|
396
1143
|
return;
|