check-rule-mate 0.5.2 → 0.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 +151 -2
- package/bin/generate-docs.js +2 -2
- package/bin/verify-templates.js +226 -0
- package/dist/main.cjs.js +1 -1
- package/package.json +3 -2
- package/src/index.d.ts +101 -1
- package/src/js/dataValidate.js +36 -7
- package/src/types.js +72 -1
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ It is designed for scenarios where traditional schema-based validators start to
|
|
|
13
13
|
- Async checks
|
|
14
14
|
- Reusable validation logic across multiple forms
|
|
15
15
|
- Full control over execution flow and error handling
|
|
16
|
+
- Lifecycle Hooks
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
**Github repository:** [check-rule-mate repository](https://github.com/johnrock16/check-rule-mate)
|
|
@@ -57,6 +58,7 @@ This separation makes the system flexible, scalable, and easy to maintain.
|
|
|
57
58
|
- i18n-ready error messages
|
|
58
59
|
- Framework-agnostic (frontend or backend)
|
|
59
60
|
- Auto documentation (automatic generated by a CLI command)
|
|
61
|
+
- Template checker (automatic checked by a CLI command)
|
|
60
62
|
|
|
61
63
|
## Table of Contents
|
|
62
64
|
|
|
@@ -66,7 +68,9 @@ This separation makes the system flexible, scalable, and easy to maintain.
|
|
|
66
68
|
- [Running Tests](#Repository---Running-Tests)
|
|
67
69
|
- [How It Works](#How-It-Works)
|
|
68
70
|
- [Basic Usage](#Basic-Usage)
|
|
71
|
+
- [Lifecycle Hooks](#Lifecycle-Hooks)
|
|
69
72
|
- [Auto documentation](#Auto-Documentation)
|
|
73
|
+
- [Auto Checker for Templates](#Auto-Checker-for-Templates)
|
|
70
74
|
- [Defining Validation](#Defining-Validation)
|
|
71
75
|
- [1. Schema](#Defining-a-Schema-What-to-validate)
|
|
72
76
|
- [2. Rules](#Defining-Rules-How-to-validate)
|
|
@@ -167,18 +171,163 @@ When is **invalid** and **has errors**:
|
|
|
167
171
|
}
|
|
168
172
|
```
|
|
169
173
|
|
|
174
|
+
### Lifecycle Hooks
|
|
175
|
+
The **check-rule-mate** lifecycle hooks allow you to observe, extend and react to the validation process without coupling logic to rules or helpers.
|
|
176
|
+
|
|
177
|
+
Hooks are especially useful for:
|
|
178
|
+
|
|
179
|
+
- Logging and debugging
|
|
180
|
+
- Analytics and metrics
|
|
181
|
+
- Custom side-effects
|
|
182
|
+
- Integrating validation with UI or external systems
|
|
183
|
+
- Advanced error handling and reporting
|
|
184
|
+
- They provide fine-grained control over the validation lifecycle.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
hooks?: {
|
|
188
|
+
onValidateStart?: (payload) => void;
|
|
189
|
+
onValidateFieldStart?: (payload) => void;
|
|
190
|
+
onValidateFieldError?: (payload) => void;
|
|
191
|
+
onValidateFieldSuccess?: (payload) => void;
|
|
192
|
+
onValidateEnd?: (payload) => void;
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### Hook Payloads
|
|
197
|
+
|
|
198
|
+
##### `onValidateStart`
|
|
199
|
+
Triggered once before validation begins.
|
|
200
|
+
```typescript
|
|
201
|
+
onValidateStart: ({ data }) => void
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Payload:**
|
|
205
|
+
- `data`: Full form data object being validated
|
|
206
|
+
|
|
207
|
+
##### `onValidateFieldStart`
|
|
208
|
+
Triggered before validating each field.
|
|
209
|
+
```typescript
|
|
210
|
+
onValidateFieldStart: ({ field, value, schemaField }) => void
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Payload:**
|
|
214
|
+
- `field`: Field name
|
|
215
|
+
- `value`: Field value
|
|
216
|
+
- `schemaField`: Schema configuration for the field (or `null` if missing)
|
|
217
|
+
|
|
218
|
+
##### `onValidateFieldError`
|
|
219
|
+
Triggered when a field fails validation.
|
|
220
|
+
```typescript
|
|
221
|
+
onValidateFieldError: ({ field, value, schemaField, error }) => void
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Payload:**
|
|
225
|
+
- `field`: Field name
|
|
226
|
+
- `value`: Field value
|
|
227
|
+
- `schemaField`: Schema configuration for the field (or `null` if missing)
|
|
228
|
+
- `error`: Validation error object
|
|
229
|
+
|
|
230
|
+
##### `onValidateFieldSuccess`
|
|
231
|
+
Triggered when a field is successfully validated.
|
|
232
|
+
```typescript
|
|
233
|
+
onValidateFieldSuccess: ({ field, value, schemaField }) => void
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Payload:**
|
|
237
|
+
- `field`: Field name
|
|
238
|
+
- `value`: Field value
|
|
239
|
+
- `schemaField`: Schema configuration for the field (or `null` if missing)
|
|
240
|
+
|
|
241
|
+
##### `onValidateEnd`
|
|
242
|
+
Triggered once after validation finishes.
|
|
243
|
+
```typescript
|
|
244
|
+
onValidateEnd: ({ data, errors }) => void
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Payload:**
|
|
248
|
+
- `data`: Full form data object being validated
|
|
249
|
+
- `errors`: Validation errors (if any)
|
|
250
|
+
If no errors occurred, `errors` may be `undefined`.
|
|
251
|
+
|
|
252
|
+
#### Hooks - Example Usage
|
|
253
|
+
```javascript
|
|
254
|
+
const validator = createValidator(fields, {
|
|
255
|
+
validationHelpers: myValidator,
|
|
256
|
+
rules: MY_RULES,
|
|
257
|
+
schema: CONTACT_US,
|
|
258
|
+
errorMessages: ERROR_MESSAGES,
|
|
259
|
+
hooks: {
|
|
260
|
+
onValidateStart: ({ data }) => {
|
|
261
|
+
console.log('Validation started', data);
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
onValidateFieldStart: ({ field, value }) => {
|
|
265
|
+
console.log(`Validating ${field}`, value);
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
onValidateFieldError: ({ field, error }) => {
|
|
269
|
+
console.error(`Error on ${field}`, error);
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
onValidateFieldSuccess: ({ field }) => {
|
|
273
|
+
console.log(`${field} validated successfully`);
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
onValidateEnd: ({ data, errors }) => {
|
|
277
|
+
if (errors) {
|
|
278
|
+
console.log('Validation finished with errors', errors);
|
|
279
|
+
} else {
|
|
280
|
+
console.log('Validation finished successfully');
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
await validator.validate();
|
|
287
|
+
```
|
|
288
|
+
|
|
170
289
|
### Auto Documentation
|
|
171
290
|
|
|
172
|
-
check-rule-mate contains a script to generate documentation based in your rules, schemas and error messages.
|
|
291
|
+
**check-rule-mate** contains a script to generate **automatic documentation** based in your rules, schemas and error messages.
|
|
173
292
|
|
|
174
293
|
To use that it is simple, you only need to run this command:
|
|
175
294
|
|
|
176
295
|
```bash
|
|
177
|
-
npx
|
|
296
|
+
npx check-rule-mate-auto-docs --rules {rules path} --schemas {schemas path} --errors {errors path} --out file.html
|
|
178
297
|
```
|
|
179
298
|
|
|
180
299
|
This will generate a HTML file containing the rules, schemas and errors.
|
|
181
300
|
|
|
301
|
+
### Auto Checker for Templates
|
|
302
|
+
You can **auto check** if your template it is working properly with all necessary properties or have something missing.
|
|
303
|
+
|
|
304
|
+
It is works with: `Schemas`, `Rules` and `Errors`
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
npx check-rule-mate-verify-templates --rules {rules path} --schemas {schemas path} --errors {errors path}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
If everything it is **working properly** this should be your message:
|
|
311
|
+
```bash
|
|
312
|
+
✔ Schemas loaded: 4
|
|
313
|
+
✔ Rules loaded: 6
|
|
314
|
+
✔ Errors loaded: 6
|
|
315
|
+
|
|
316
|
+
✔ No issues found
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Else if some error was found could be something like that:
|
|
320
|
+
```bash
|
|
321
|
+
✔ Schemas loaded: 4
|
|
322
|
+
✔ Rules loaded: 6
|
|
323
|
+
✔ Errors loaded: 6
|
|
324
|
+
|
|
325
|
+
❌ Validation failed
|
|
326
|
+
|
|
327
|
+
❌ Schema "contactUs.json" → field "phone" references rule "cellphone" which does not exist
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
|
|
182
331
|
## Defining Validation
|
|
183
332
|
|
|
184
333
|
### Defining a Schema (What to validate)
|
package/bin/generate-docs.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
|
-
const process = require('process')
|
|
12
|
+
const process = require('process');
|
|
13
13
|
|
|
14
14
|
/* ---------------------------------------
|
|
15
15
|
* CLI ARGS
|
|
@@ -28,7 +28,7 @@ const OUTPUT = getArg('--out') || "check-rule-mate-docs.html";
|
|
|
28
28
|
if (!RULES_DIR || !SCHEMAS_DIR) {
|
|
29
29
|
console.error(`
|
|
30
30
|
Usage:
|
|
31
|
-
|
|
31
|
+
npx check-rule-mate-auto-docs --rules ./rules --schemas ./schemas --errors ./errors --out docs.html
|
|
32
32
|
`);
|
|
33
33
|
process.exit(1);
|
|
34
34
|
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// import fs from 'fs';
|
|
4
|
+
// import path from 'path';
|
|
5
|
+
// import process from 'process';
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const process = require('process');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ---------------------------
|
|
13
|
+
* Utils
|
|
14
|
+
* ---------------------------
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
function readJSONFilesFromDir(dirPath) {
|
|
18
|
+
if (!fs.existsSync(dirPath)) {
|
|
19
|
+
throw new Error(`Directory not found: ${dirPath}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const files = fs.readdirSync(dirPath).filter(f => f.endsWith('.json'));
|
|
23
|
+
|
|
24
|
+
const data = {};
|
|
25
|
+
for (const file of files) {
|
|
26
|
+
const fullPath = path.join(dirPath, file);
|
|
27
|
+
const content = JSON.parse(fs.readFileSync(fullPath, 'utf-8'));
|
|
28
|
+
data[file] = content;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseRuleName(ruleName) {
|
|
35
|
+
const [base, modifier] = ruleName.split('--');
|
|
36
|
+
return { base, modifier };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function error(msg) {
|
|
40
|
+
return { type: 'error', message: msg };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function warning(msg) {
|
|
44
|
+
return { type: 'warning', message: msg };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* ---------------------------
|
|
49
|
+
* Core Validators
|
|
50
|
+
* ---------------------------
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
function validateSchemas({ schemas, rules }) {
|
|
54
|
+
const issues = [];
|
|
55
|
+
|
|
56
|
+
for (const [schemaFile, schema] of Object.entries(schemas)) {
|
|
57
|
+
for (const [field, config] of Object.entries(schema)) {
|
|
58
|
+
if (!config.rule) continue;
|
|
59
|
+
|
|
60
|
+
const { base, modifier } = parseRuleName(config.rule);
|
|
61
|
+
|
|
62
|
+
if (!rules[base]) {
|
|
63
|
+
issues.push(
|
|
64
|
+
error(
|
|
65
|
+
`Schema "${schemaFile}" → field "${field}" references rule "${base}" which does not exist`
|
|
66
|
+
)
|
|
67
|
+
);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (modifier) {
|
|
72
|
+
if (!rules[base].modifier || !rules[base].modifier[modifier]) {
|
|
73
|
+
issues.push(
|
|
74
|
+
error(
|
|
75
|
+
`Schema "${schemaFile}" → field "${field}" references modifier "${modifier}" on rule "${base}" which does not exist`
|
|
76
|
+
)
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return issues;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function validateRuleErrors({ rules, errors }) {
|
|
87
|
+
const issues = [];
|
|
88
|
+
|
|
89
|
+
for (const [ruleName, rule] of Object.entries(rules)) {
|
|
90
|
+
const checkErrorMap = (errorMap, context) => {
|
|
91
|
+
if (!errorMap) return;
|
|
92
|
+
|
|
93
|
+
for (const errorKey of Object.values(errorMap)) {
|
|
94
|
+
const [namespace, key] = errorKey.split('.');
|
|
95
|
+
if (!errors[namespace] || !errors[namespace][key]) {
|
|
96
|
+
issues.push(
|
|
97
|
+
error(
|
|
98
|
+
`Rule "${ruleName}"${context} references error key "${errorKey}" which does not exist`
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
checkErrorMap(rule.error, '');
|
|
106
|
+
|
|
107
|
+
if (rule.modifier) {
|
|
108
|
+
for (const [modifierName, modifier] of Object.entries(rule.modifier)) {
|
|
109
|
+
checkErrorMap(modifier.error, ` (modifier "${modifierName}")`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return issues;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function findUnusedErrors({ rules, errors }) {
|
|
118
|
+
const issues = [];
|
|
119
|
+
const usedErrors = new Set();
|
|
120
|
+
|
|
121
|
+
const collectErrors = errorMap => {
|
|
122
|
+
if (!errorMap) return;
|
|
123
|
+
Object.values(errorMap).forEach(e => usedErrors.add(e));
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
for (const rule of Object.values(rules)) {
|
|
127
|
+
collectErrors(rule.error);
|
|
128
|
+
if (rule.modifier) {
|
|
129
|
+
Object.values(rule.modifier).forEach(m => collectErrors(m.error));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const [namespace, keys] of Object.entries(errors)) {
|
|
134
|
+
for (const key of Object.keys(keys)) {
|
|
135
|
+
const fullKey = `${namespace}.${key}`;
|
|
136
|
+
if (!usedErrors.has(fullKey)) {
|
|
137
|
+
issues.push(
|
|
138
|
+
warning(`Unused error message detected: "${fullKey}"`)
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return issues;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* ---------------------------
|
|
149
|
+
* CLI Argument Parsing
|
|
150
|
+
* ---------------------------
|
|
151
|
+
*/
|
|
152
|
+
|
|
153
|
+
function parseArgs() {
|
|
154
|
+
const args = process.argv.slice(2);
|
|
155
|
+
const options = {};
|
|
156
|
+
|
|
157
|
+
for (let i = 0; i < args.length; i++) {
|
|
158
|
+
if (args[i].startsWith('--')) {
|
|
159
|
+
const key = args[i].replace('--', '');
|
|
160
|
+
options[key] = args[i + 1];
|
|
161
|
+
i++;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return options;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* ---------------------------
|
|
170
|
+
* Runner
|
|
171
|
+
* ---------------------------
|
|
172
|
+
*/
|
|
173
|
+
|
|
174
|
+
async function runVerify() {
|
|
175
|
+
try {
|
|
176
|
+
const args = parseArgs();
|
|
177
|
+
|
|
178
|
+
if (!args.schemas || !args.rules || !args.errors) {
|
|
179
|
+
console.error(
|
|
180
|
+
'Usage: check-rule-mate-verify-templates --schemas <path> --rules <path> --errors <path>'
|
|
181
|
+
);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const schemas = readJSONFilesFromDir(args.schemas);
|
|
186
|
+
const rulesRaw = readJSONFilesFromDir(args.rules);
|
|
187
|
+
const errorsRaw = readJSONFilesFromDir(args.errors);
|
|
188
|
+
|
|
189
|
+
// Merge all rule files into one object
|
|
190
|
+
const rules = Object.assign({}, ...Object.values(rulesRaw));
|
|
191
|
+
const errors = Object.assign({}, ...Object.values(errorsRaw));
|
|
192
|
+
|
|
193
|
+
let issues = [];
|
|
194
|
+
|
|
195
|
+
issues.push(...validateSchemas({ schemas, rules }));
|
|
196
|
+
issues.push(...validateRuleErrors({ rules, errors }));
|
|
197
|
+
issues.push(...findUnusedErrors({ rules, errors }));
|
|
198
|
+
|
|
199
|
+
const hasErrors = issues.some(i => i.type === 'error');
|
|
200
|
+
|
|
201
|
+
console.log('✔ Schemas loaded:', Object.keys(schemas).length);
|
|
202
|
+
console.log('✔ Rules loaded:', Object.keys(rules).length);
|
|
203
|
+
console.log('✔ Errors loaded:', Object.keys(errors).length);
|
|
204
|
+
console.log('');
|
|
205
|
+
|
|
206
|
+
if (issues.length === 0) {
|
|
207
|
+
console.log('✔ No issues found');
|
|
208
|
+
process.exit(0);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log(hasErrors ? '❌ Validation failed' : '⚠️ Validation warnings');
|
|
212
|
+
console.log('');
|
|
213
|
+
|
|
214
|
+
for (const issue of issues) {
|
|
215
|
+
const prefix = issue.type === 'error' ? '❌' : '⚠️';
|
|
216
|
+
console.log(`${prefix} ${issue.message}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
process.exit(hasErrors ? 1 : 0);
|
|
220
|
+
} catch (err) {
|
|
221
|
+
console.error('Fatal error:', err.message);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
runVerify();
|
package/dist/main.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var M=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var L=(r,a)=>{for(var u in a)M(r,u,{get:a[u],enumerable:!0})},_=(r,a,u,l)=>{if(a&&typeof a=="object"||typeof a=="function")for(let d of D(a))!U.call(r,d)&&d!==u&&M(r,d,{get:()=>a[d],enumerable:!(l=j(a,d))||l.enumerable});return r};var x=r=>_(M({},"__esModule",{value:!0}),r);var K={};L(K,{createValidator:()=>q});module.exports=x(K);var O={propertiesMustMatch:!0,abortEarly:!1,cache:!0};function q(r,{validationHelpers:a={},rules:u,schema:l,errorMessages:d={},hooks:g={},options:p=O}){p={...O,...p},r=r;let i={},y={},v={all:async e=>Promise.all([...e].map(async n=>await m(n,r))),first:async e=>{let n=[];for(let V of e){let t=await m(V,r);if(n.push(t),!t)return n}return n}};function w(e,n){if(!e||typeof n!="string")return;let V=n.split("."),t=e;for(let f of V){if(t[f]===void 0)return;t=t[f]}return t}async function m(e,n=null){var V,t;if(o("onValidateFieldStart",{field:e.key,value:e.value,schemaField:l[e.key]||null}),l[e.key]){let{rule:f,required:s}=l[e.key];if((((V=l[e.key])==null?void 0:V.cache)!==void 0?l[e.key].cache:p.cache)&&n[e.key]===((t=y[e.key])==null?void 0:t.value))return y[e.key].isValid;if((f&&s||!s&&e.value!="")&&f){let P=f.split("--")[0],S=f.split("--").length>1?f.split("--")[1]:"",I=B(e.value,u[P],S,a,n),{isValid:E,errorMessage:F,errorType:T}=await I.validate();if(E)o("onValidateFieldSuccess",{field:e.key,value:e.value,schemaField:l[e.key]});else{let R={name:e.key,field:e.key,code:F,type:T,message:w(d,F)||""};i[e.key]=R,o("onValidateFieldError",{field:e.key,value:e.value,schemaField:l[e.key],error:R})}return y[e.key]={isValid:E,value:n[e.key]},E}}else if(p.propertiesMustMatch){let f={name:e.key,field:e.key,message:"Invalid property",internal:!0};return i[e.key]=f,o("onValidateFieldError",{field:e.key,value:e.value,error:f}),!1}return o("onValidateFieldSuccess",{field:e.key,value:e.value}),!0}async function c(e){return await m({key:e,value:r[e]},r)?{ok:!0}:{error:!0,errors:i[e]}}async function k(){o("onValidateStart",{data:r}),i={};let e=Object.keys(r).map(n=>({key:n,value:r[n]}));if(e&&e.length>0){if(!Object.keys(l).every(s=>r.hasOwnProperty(s)))return o("onValidateEnd",{data:r,errors:[{name:"internal: schema - missing properties",message:"Missing properties",internal:!0}]}),{error:!0,errorMessage:"Missing properties"};if((p!=null&&p.abortEarly?await v.first(e):await v.all(e)).some(s=>!s))return o("onValidateEnd",{data:r,errors:i}),{error:!0,errors:i};let V=Object.keys(l).map(s=>({key:s,required:l[s].required})),t=e.map(s=>s.key);if(!V.filter(s=>s.required).map(s=>s.key).every(s=>t.includes(s))){let s={error:!0};return o("onValidateEnd",{data:r,errors:[{name:"internal: fields - required",message:"",internal:!0}]}),s}}else if(!e||e.length===0){let n={error:!0,errorMessage:"Missing fields for schema"};return o("onValidateEnd",{data:r,errors:[{name:"internal: schema - missing fields",message:n.errorMessage,internal:!0}]}),n}return o("onValidateEnd",{data:r}),{ok:!0}}function o(e,n){g!=null&&g[e]&&typeof g[e]=="function"&&g[e]({...n})}function h(e){r=e}return{validate:k,validateField:c,setData:h}}function B(r,a,u=null,l=null,d=null){async function g(i){let y,v;return{isValid:!(await Promise.all(i.validate.map(async c=>{let k=!0;if(i.params&&i.params[c]&&i.params[c].length>0){let o=i.params[c].map(e=>typeof e=="string"&&e[0]==="$"?e.substring(1,e.length):e);k=await this[c](...o)}else k=await this[c]();return!k&&!y&&(i!=null&&i.error[c])&&(y=i.error[c],v=c),k}))).some(c=>!c),errorMessage:y,errorType:v}}async function p(){if(l&&typeof l=="function"){let y=l(r,a,u,d);Object.keys(y).forEach(v=>{this[v]=y[v]})}return u?await g.call(this,a.modifier[u]):await g.call(this,a)}return{validate:p}}0&&(module.exports={createValidator});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "check-rule-mate",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./dist/main.cjs.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"check rule mate"
|
|
19
19
|
],
|
|
20
20
|
"bin": {
|
|
21
|
-
"
|
|
21
|
+
"check-rule-mate-auto-docs": "./bin/generate-docs.js",
|
|
22
|
+
"check-rule-mate-verify-templates": "./bin/verify-templates.js"
|
|
22
23
|
},
|
|
23
24
|
"scripts": {
|
|
24
25
|
"start": "node ./examples/vanilla/src/index.js",
|
package/src/index.d.ts
CHANGED
|
@@ -86,6 +86,9 @@ export interface DataValidatorConfigs {
|
|
|
86
86
|
|
|
87
87
|
/** Validator options */
|
|
88
88
|
options: ValidatorOptions
|
|
89
|
+
|
|
90
|
+
/** Validator hooks */
|
|
91
|
+
hooks: ValidatorHooks
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
/**
|
|
@@ -100,9 +103,12 @@ export interface DataValidatorSuccessResponse {
|
|
|
100
103
|
* Error object.
|
|
101
104
|
*/
|
|
102
105
|
export interface CheckError {
|
|
103
|
-
/**
|
|
106
|
+
/** Error name */
|
|
104
107
|
name: string
|
|
105
108
|
|
|
109
|
+
/** Field name */
|
|
110
|
+
field?: string
|
|
111
|
+
|
|
106
112
|
/** Error path */
|
|
107
113
|
code?: string
|
|
108
114
|
|
|
@@ -111,6 +117,9 @@ export interface CheckError {
|
|
|
111
117
|
|
|
112
118
|
/** Error message */
|
|
113
119
|
message?: string
|
|
120
|
+
|
|
121
|
+
/** Internal error flag */
|
|
122
|
+
internal?: boolean
|
|
114
123
|
}
|
|
115
124
|
|
|
116
125
|
/**
|
|
@@ -126,3 +135,94 @@ export interface DataValidatorErrorResponse {
|
|
|
126
135
|
/** Additional error details */
|
|
127
136
|
errors?: Record<string, CheckError>
|
|
128
137
|
}
|
|
138
|
+
|
|
139
|
+
export interface ValidatorHooks {
|
|
140
|
+
/** Executed before validation runs */
|
|
141
|
+
onValidateStart?: onValidateStart
|
|
142
|
+
|
|
143
|
+
/** Executed before validation runs for each field */
|
|
144
|
+
onValidateFieldStart?: onValidateFieldStart
|
|
145
|
+
|
|
146
|
+
/** Executed when validation fails for some field */
|
|
147
|
+
onValidateFieldError?: onValidateFieldError
|
|
148
|
+
|
|
149
|
+
/** Executed when validation has success for some field */
|
|
150
|
+
onValidateFieldSuccess?: onValidateFieldSuccess
|
|
151
|
+
|
|
152
|
+
/** Executed after validation runs */
|
|
153
|
+
onValidateEnd?: onValidateEnd
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface onValidateStart {
|
|
157
|
+
/** Returns the payload of hook */
|
|
158
|
+
payload: onValidateStartPayload
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface onValidateFieldStart {
|
|
162
|
+
/** Returns the payload of hook */
|
|
163
|
+
payload: onValidateFieldStartPayload
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface onValidateFieldError {
|
|
167
|
+
/** Returns the payload of hook */
|
|
168
|
+
payload: onValidateFieldErrorPayload
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface onValidateFieldSuccess {
|
|
172
|
+
/** Returns the payload of hook */
|
|
173
|
+
payload: onValidateFieldSuccessPayload
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface onValidateEnd {
|
|
177
|
+
/** Returns the payload of hook */
|
|
178
|
+
payload: onValidateEndPayload
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface onValidateStartPayload {
|
|
182
|
+
/** Form data object */
|
|
183
|
+
data: Object
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export interface onValidateFieldStart {
|
|
187
|
+
/** Field name */
|
|
188
|
+
field: string
|
|
189
|
+
|
|
190
|
+
/** Field value */
|
|
191
|
+
value: any
|
|
192
|
+
|
|
193
|
+
/** Schema field */
|
|
194
|
+
schemaField: SchemaRuleField
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface onValidateFieldError {
|
|
198
|
+
/** Field name */
|
|
199
|
+
field: string
|
|
200
|
+
|
|
201
|
+
/** Field value */
|
|
202
|
+
value: any
|
|
203
|
+
|
|
204
|
+
/** Schema field */
|
|
205
|
+
schemaField: SchemaRuleField
|
|
206
|
+
|
|
207
|
+
/** Field error */
|
|
208
|
+
error: CheckError
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface onValidateFieldSuccess {
|
|
212
|
+
/** Field name */
|
|
213
|
+
field: string
|
|
214
|
+
|
|
215
|
+
/** Field value */
|
|
216
|
+
value: any
|
|
217
|
+
|
|
218
|
+
/** Schema field */
|
|
219
|
+
schemaField: SchemaRuleField
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface onValidateEndPayload {
|
|
223
|
+
/** Form data object */
|
|
224
|
+
data: Object
|
|
225
|
+
|
|
226
|
+
/** Form errors */
|
|
227
|
+
errors?: Record<string, CheckError>
|
|
228
|
+
}
|
package/src/js/dataValidate.js
CHANGED
|
@@ -5,7 +5,7 @@ const DEFAUL_OPTIONS = { propertiesMustMatch: true, abortEarly: false, cache: tr
|
|
|
5
5
|
* @param {[DataField]} data - All the data fields to be validate
|
|
6
6
|
* @param {DataValidatorConfigs} config - The configs which will be followed during validation
|
|
7
7
|
*/
|
|
8
|
-
export function createValidator(data, {validationHelpers = {}, rules, schema, errorMessages = {}, options = DEFAUL_OPTIONS}) {
|
|
8
|
+
export function createValidator(data, {validationHelpers = {}, rules, schema, errorMessages = {}, hooks = {}, options = DEFAUL_OPTIONS}) {
|
|
9
9
|
options = { ...DEFAUL_OPTIONS, ...options};
|
|
10
10
|
data = data;
|
|
11
11
|
let errors = {};
|
|
@@ -41,6 +41,7 @@ export function createValidator(data, {validationHelpers = {}, rules, schema, er
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
async function inputValidation(dataAttribute, data = null) {
|
|
44
|
+
hookTrigger('onValidateFieldStart', { field: dataAttribute.key, value: dataAttribute.value, schemaField: schema[dataAttribute.key] || null });
|
|
44
45
|
if (schema[dataAttribute.key]) {
|
|
45
46
|
const { rule, required } = schema[dataAttribute.key];
|
|
46
47
|
const cacheEnabled = schema[dataAttribute.key]?.cache !== undefined ? schema[dataAttribute.key].cache : options.cache;
|
|
@@ -56,24 +57,36 @@ export function createValidator(data, {validationHelpers = {}, rules, schema, er
|
|
|
56
57
|
const dataAttributeValidation = dataAttributeValidator(dataAttribute.value, rules[INPUT_RULE], RULE_MODIFIER, validationHelpers, data);
|
|
57
58
|
const { isValid, errorMessage, errorType } = await dataAttributeValidation.validate();
|
|
58
59
|
if (!isValid) {
|
|
59
|
-
|
|
60
|
+
const error = {
|
|
60
61
|
name: dataAttribute.key,
|
|
62
|
+
field: dataAttribute.key,
|
|
61
63
|
code: errorMessage,
|
|
62
64
|
type: errorType,
|
|
63
65
|
message: getObjectValueByPath(errorMessages, errorMessage) || ''
|
|
64
|
-
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
errors[dataAttribute.key] = error;
|
|
69
|
+
hookTrigger('onValidateFieldError', { field: dataAttribute.key, value: dataAttribute.value, schemaField: schema[dataAttribute.key] , error: error });
|
|
70
|
+
} else {
|
|
71
|
+
hookTrigger('onValidateFieldSuccess', { field: dataAttribute.key, value: dataAttribute.value, schemaField: schema[dataAttribute.key] });
|
|
65
72
|
}
|
|
66
73
|
oldData[dataAttribute.key] = {isValid: isValid, value: data[dataAttribute.key]};
|
|
67
74
|
return isValid;
|
|
68
75
|
}
|
|
69
76
|
}
|
|
70
77
|
} else if (options.propertiesMustMatch) {
|
|
71
|
-
|
|
78
|
+
const error = {
|
|
72
79
|
name: dataAttribute.key,
|
|
73
|
-
|
|
80
|
+
field: dataAttribute.key,
|
|
81
|
+
message: "Invalid property",
|
|
82
|
+
internal: true
|
|
74
83
|
}
|
|
84
|
+
|
|
85
|
+
errors[dataAttribute.key] = error;
|
|
86
|
+
hookTrigger('onValidateFieldError', { field: dataAttribute.key, value: dataAttribute.value, error: error });
|
|
75
87
|
return false;
|
|
76
88
|
}
|
|
89
|
+
hookTrigger('onValidateFieldSuccess', { field: dataAttribute.key, value: dataAttribute.value });
|
|
77
90
|
return true;
|
|
78
91
|
}
|
|
79
92
|
|
|
@@ -95,15 +108,18 @@ export function createValidator(data, {validationHelpers = {}, rules, schema, er
|
|
|
95
108
|
* @returns {DataValidatorSuccessResponse | DataValidatorErrorResponse} - The response of your validation
|
|
96
109
|
*/
|
|
97
110
|
async function validate() {
|
|
111
|
+
hookTrigger('onValidateStart', { data });
|
|
98
112
|
errors = {};
|
|
99
113
|
let dataArr = Object.keys(data).map((key) => ({ key, value: data[key] }));
|
|
100
114
|
if (dataArr && dataArr.length > 0) {
|
|
101
115
|
if(!Object.keys(schema).every((key) => data.hasOwnProperty(key))) {
|
|
116
|
+
hookTrigger('onValidateEnd', { data, errors: [{name: 'internal: schema - missing properties' , message: 'Missing properties', internal: true}] });
|
|
102
117
|
return { error: true, errorMessage: "Missing properties"}
|
|
103
118
|
}
|
|
104
119
|
const dataValidators = options?.abortEarly ? await validateByStrategies.first(dataArr) : await validateByStrategies.all(dataArr);
|
|
105
120
|
|
|
106
121
|
if (dataValidators.some((element) => !element)) {
|
|
122
|
+
hookTrigger('onValidateEnd', { data, errors: errors });
|
|
107
123
|
return { error: true, errors: errors };
|
|
108
124
|
}
|
|
109
125
|
|
|
@@ -112,14 +128,27 @@ export function createValidator(data, {validationHelpers = {}, rules, schema, er
|
|
|
112
128
|
const dataAttributesRequired = dataRuleArr.filter((rule) => rule.required).map((rule) => rule.key);
|
|
113
129
|
|
|
114
130
|
if (!dataAttributesRequired.every((fieldRequired) => dataAttributesKey.includes(fieldRequired))) {
|
|
115
|
-
|
|
131
|
+
const error = { error: true };
|
|
132
|
+
|
|
133
|
+
hookTrigger('onValidateEnd', { data, errors: [{name: 'internal: fields - required' , message: '', internal: true}] });
|
|
134
|
+
return error;
|
|
116
135
|
}
|
|
117
136
|
} else if (!dataArr || dataArr.length === 0) {
|
|
118
|
-
|
|
137
|
+
const error = { error: true, errorMessage: "Missing fields for schema"};
|
|
138
|
+
|
|
139
|
+
hookTrigger('onValidateEnd', { data, errors: [{name: 'internal: schema - missing fields' , message: error.errorMessage, internal: true}] });
|
|
140
|
+
return error;
|
|
119
141
|
}
|
|
142
|
+
hookTrigger('onValidateEnd', { data });
|
|
120
143
|
return { ok: true };
|
|
121
144
|
}
|
|
122
145
|
|
|
146
|
+
function hookTrigger(hookName, parameters) {
|
|
147
|
+
if (hooks?.[hookName] && typeof hooks[hookName] === 'function') {
|
|
148
|
+
hooks[hookName]({...parameters});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
123
152
|
function setData(newData) {
|
|
124
153
|
data = newData;
|
|
125
154
|
}
|
package/src/types.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* @property {SchemaRule} schema - The rules you want to use per field
|
|
17
17
|
* @property {Object} errorMessages - The error messages you want to show during errors
|
|
18
18
|
* @property {ValidatorOptions} options - Options
|
|
19
|
+
* @property {ValidatorHooks} hooks - The hooks you want to execute in some specific phase of validation
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -74,8 +75,78 @@
|
|
|
74
75
|
* Error Object
|
|
75
76
|
*
|
|
76
77
|
* @typedef {Object} CheckError
|
|
77
|
-
* @property {string} name -
|
|
78
|
+
* @property {string} name - Error name
|
|
79
|
+
* @property {string} field - Field name (optional)
|
|
78
80
|
* @property {string} code - Error path (optional)
|
|
79
81
|
* @property {string} type - Error type (optional)
|
|
80
82
|
* @property {string} message - Error message (optional)
|
|
83
|
+
* @property {boolean} internal - Flag to know if it is a internal error (optional)
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @typedef {Object} ValidatorHooks
|
|
89
|
+
* @property {onValidateStart} onValidateStart - Executed before validation runs
|
|
90
|
+
* @property {onValidateFieldStart} onValidateFieldStart - Executed before validation runs for each field
|
|
91
|
+
* @property {onValidateFieldError} onValidateFieldError - Executed when validation fails for some field
|
|
92
|
+
* @property {onValidateFieldSuccess} onValidateFieldSuccess - Executed when validation has success for some field
|
|
93
|
+
* @property {onValidateEnd} onValidateEnd - Executed after validation runs
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @typedef {Function} onValidateStart
|
|
98
|
+
* @property {onValidateStartPayload} payload - Returns the payload of validate form hook
|
|
99
|
+
*/
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @typedef {Object} onValidateStartPayload
|
|
103
|
+
* @property {Object} data - Returns the form data
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @typedef {Function} onValidateFieldStart
|
|
108
|
+
* @property {onValidateFieldStartPayload} payload - Returns the payload of validate field hook
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @typedef {Object} onValidateFieldStartPayload
|
|
113
|
+
* @property {string} field - Returns the field name
|
|
114
|
+
* @property {any} value - Returns the value of field
|
|
115
|
+
* @property {SchemaRuleField} schemaField - Returns the schema field
|
|
116
|
+
*/
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @typedef {Function} onValidateFieldError
|
|
120
|
+
* @property {onValidateFieldErrorPayload} payload - Returns the payload of validate field error hook
|
|
121
|
+
*/
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @typedef {Object} onValidateFieldErrorPayload
|
|
125
|
+
* @property {string} field - Returns the field name
|
|
126
|
+
* @property {string} value - Returns the value of field
|
|
127
|
+
* @property {SchemaRuleField} schemaField - Returns the schema field
|
|
128
|
+
* @property {CheckError} error - Returns the error data
|
|
129
|
+
*/
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @typedef {Function} onValidateFieldSuccess
|
|
133
|
+
* @property {onValidateFieldSuccessPayload} payload - Returns the payload of validate field success hook
|
|
134
|
+
*/
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @typedef {Object} onValidateFieldSuccessPayload
|
|
138
|
+
* @property {string} field - Returns the field name
|
|
139
|
+
* @property {string} value - Returns the value of field
|
|
140
|
+
* @property {SchemaRuleField} schemaField - Returns the schema field
|
|
141
|
+
*/
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @typedef {Function} onValidateEnd
|
|
145
|
+
* @property {onValidateEndPayload} payload - Returns the payload of validate form hook
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @typedef {Object} onValidateEndPayload
|
|
150
|
+
* @property {Object} data - Returns the form data
|
|
151
|
+
* @property {DataValidatorErrorResponse} errors - Returns the errors of validation if had
|
|
81
152
|
*/
|