Skip to content

Commit 4434a47

Browse files
authored
Merge pull request #304 from avanelli/feat/haltOnFirstErrorConf
Add: haltOnFirstError option
2 parents d3b6b98 + 0baa59e commit 4434a47

File tree

4 files changed

+204
-26
lines changed

4 files changed

+204
-26
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ console.log("Second:", check({ id: 2, name: "Adam" }));
9292
```
9393
[Try it on Repl.it](https://repl.it/@icebob/fastest-validator-fast)
9494

95+
If you want to halt immediately after the first error use: `haltOnFirstError`
96+
```js
97+
const v = new Validator({haltOnFirstError: true});
98+
```
99+
95100
## Browser usage
96101
```html
97102
<script src="https://unpkg.com/fastest-validator"></script>

examples/halt-on-first-error.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"use strict";
2+
const Validator = require("../index");
3+
const v = new Validator({
4+
haltOnFirstError: true,
5+
debug: false,
6+
useNewCustomCheckerFunction: true
7+
});
8+
9+
const schema = {
10+
id: { type: "number", min: 8, max: 101 },
11+
name: { type: "string", optional: false, min: 5, max: 128 },
12+
settings: { type: "object", props: {
13+
notify: [
14+
{ type: "boolean" },
15+
{ type: "object" }
16+
]
17+
}},
18+
multi: [
19+
{ type: "string", min: 3, max: 255 },
20+
{ type: "boolean" }
21+
],
22+
sex: { type: "string", enum: ["male", "female"] },
23+
sex2: { type: "enum", values: ["male", "female"] },
24+
roles: { type: "array", items: { type: "string" }, enum: ["admin", "user"] },
25+
friends: { type: "array", items: { type: "number", positive: true }},
26+
comments: { type: "array", items: { type: "object", props: {
27+
user: { type: "number", positive: true, integer: true },
28+
content: { type: "string" },
29+
voters: { type: "array", optional: true, items: { type: "number" }}
30+
} } },
31+
multiarray: { type: "array", empty: false, items: {
32+
type: "array", empty: true, items: {
33+
type: "number"
34+
}
35+
}},
36+
email: { type: "email", optional: true },
37+
homepage: { type: "url", optional: true },
38+
status: "boolean",
39+
age: { type: "number", min: 18, max: 100, convert: true },
40+
apikey: "forbidden",
41+
uuidv4: { type: "uuid", version: 4 },
42+
uuid: "uuid",
43+
phone: { type: "string", length: 15, custom: (v, errors) => {
44+
if (!v.startsWith("+"))
45+
errors.push({ type: "phoneNumber", actual: v });
46+
return v.replace(/[^\d+]/g, "");
47+
} },
48+
action: "function",
49+
created: "date",
50+
raw: { type: "class", instanceOf: Buffer, custom: v => v.toString("base64") },
51+
now: { type: "date", convert: true },
52+
state: {
53+
type: "enum",
54+
values: ["active", "inactive"],
55+
defVal: "active",
56+
default: (schema, field, parent, context) => {
57+
return schema.defVal;
58+
}
59+
}
60+
};
61+
62+
const obj = {
63+
id: 5, // expect error min 8
64+
name: "John", // expect error min len 5
65+
sex: "N/A", // expect error
66+
sex2: "invalid", // expect error
67+
settings: {
68+
//notify: true,
69+
notify: {
70+
corner: "top"
71+
}
72+
},
73+
multi: "AA", // expect error
74+
roles: [
75+
"reader" // expect error
76+
],
77+
78+
friends: [
79+
5,
80+
10,
81+
2
82+
],
83+
84+
comments: [
85+
{ user: 1, content: "Cool!" },
86+
{ user: 2, content: "Very fast!" },
87+
{ user: 1, content: "", voters: [1] }
88+
],
89+
90+
multiarray: [
91+
[
92+
],
93+
[
94+
5,
95+
10,
96+
//"a"
97+
]
98+
],
99+
100+
101+
homepage: "http://google.com",
102+
status: true,
103+
age: "28",
104+
apikey: null,
105+
uuidv4: "10ba038e-48da-487b-96e8-8d3b99b6d18a",
106+
uuid: "10ba038e-48da-487b-96e8-8d3b99b6d18a",
107+
phone: "+36-70-123-4567",
108+
action: () => {},
109+
created: new Date(),
110+
now: Date.now(),
111+
raw: Buffer.from([1,2,3])
112+
};
113+
114+
const res = v.validate(obj, schema);
115+
116+
console.log(res, obj);

lib/rules/object.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ module.exports = function ({ schema, messages }, path, context) {
6565
${safePropName} = ${context.async ? "await " : ""}context.fn[%%INDEX%%](value, field, parentObj, errors, context);
6666
`;
6767
sourceCode.push(this.compileRule(rule, context, newPath, innerSource, safePropName));
68+
if (this.opts.haltOnFirstError === true) {
69+
sourceCode.push("if (errors.length) return parentObj;");
70+
}
6871
}
6972

7073
// Strict handler

test/validator.spec.js

Lines changed: 80 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,11 @@ describe("Test add", () => {
128128
}
129129
});
130130

131-
const validFn = jest.fn(function({ schema, messages }, path, context) {
131+
const validFn = jest.fn(function ({ schema, messages }, path, context) {
132132
return {
133133
source: `
134134
if (value % 2 != 0)
135-
${this.makeError({ type: "evenNumber", actual: "value", messages })}
135+
${this.makeError({ type: "evenNumber", actual: "value", messages })}
136136
return value;
137137
`
138138
};
@@ -168,7 +168,7 @@ describe("Test add", () => {
168168
});
169169

170170
it("should check the new rule", () => {
171-
expect(check({ a: 5 })).toEqual([{"type": "evenNumber", "field": "a", "actual": 5, "message": "The 'a' field must be an even number! Actual: 5"}]);
171+
expect(check({ a: 5 })).toEqual([{ "type": "evenNumber", "field": "a", "actual": 5, "message": "The 'a' field must be an even number! Actual: 5" }]);
172172
expect(check({ a: 6 })).toEqual(true);
173173
});
174174

@@ -262,7 +262,7 @@ describe("Test getRuleFromSchema method", () => {
262262
});
263263

264264
expect(res.schema).toEqual({
265-
type: "object" ,
265+
type: "object",
266266
props: {
267267
name: { type: "string" },
268268
age: { type: "number" }
@@ -278,7 +278,7 @@ describe("Test getRuleFromSchema method", () => {
278278
});
279279

280280
expect(res.schema).toEqual({
281-
type: "object" ,
281+
type: "object",
282282
optional: true,
283283
props: {
284284
name: { type: "string" },
@@ -316,7 +316,7 @@ describe("Test compile (integration test)", () => {
316316
let check = v.compile(schema);
317317

318318
it("should give back one errors", () => {
319-
let res = check({id: 5, name: "John" });
319+
let res = check({ id: 5, name: "John" });
320320
expect(res).toBeInstanceOf(Array);
321321

322322
expect(res.length).toBe(1);
@@ -340,6 +340,60 @@ describe("Test compile (integration test)", () => {
340340

341341
});
342342

343+
describe("Test check generator with wrong obj and haltOnFirstError", () => {
344+
const v = new Validator({ haltOnFirstError: true });
345+
346+
it("should give back one errors", () => {
347+
const schema = {
348+
id: { type: "number" },
349+
name: { type: "string", min: 5, uppercase: true },
350+
password: { type: "forbidden" }
351+
};
352+
353+
let check = v.compile(schema);
354+
let obj = { id: "string", name: "John", password: "123456" };
355+
356+
let res = check(obj);
357+
expect(res).toBeInstanceOf(Array);
358+
expect(res.length).toBe(1);
359+
expect(res[0]).toEqual({
360+
type: "number",
361+
field: "id",
362+
message: "The 'id' field must be a number.",
363+
actual: "string",
364+
});
365+
expect(obj).toEqual({ id: "string", name: "John", password: "123456" });
366+
});
367+
368+
it("should return true if no errors", () => {
369+
const schema = {
370+
id: { type: "number" },
371+
name: { type: "string", min: 5, uppercase: true },
372+
password: { type: "forbidden" }
373+
};
374+
375+
let check = v.compile(schema);
376+
let obj = { id: 5, name: "John Doe" };
377+
let res = check(obj);
378+
expect(res).toBe(true);
379+
expect(obj).toEqual({ id: 5, name: "JOHN DOE" });
380+
});
381+
382+
it("should return true if has valid in multi rule", () => {
383+
const schema = {
384+
status: [
385+
{ type: "string", enums: ["active", "inactive"] },
386+
{ type: "number", min: 0 }
387+
]
388+
};
389+
390+
let check = v.compile(schema);
391+
expect(check({ status: "active" })).toBe(true);
392+
expect(check({ status: 1 })).toBe(true);
393+
expect(check({ status: false })).toEqual([{ "actual": false, "field": "status", "message": "The 'status' field must be a string.", "type": "string" }, { "actual": false, "field": "status", "message": "The 'status' field must be a number.", "type": "number" }]);
394+
});
395+
});
396+
343397
/*
344398
describe("Test check generator with custom path & parent", () => {
345399
it("when schema is defined as an array, and custom path & parent are specified, they should be forwarded to validators", () => {
@@ -452,7 +506,7 @@ describe("Test custom validation v1", () => {
452506
min: 10,
453507
max: 15,
454508
integer: true,
455-
custom(value){
509+
custom(value) {
456510
if (value % 2 !== 0) return [{ type: "evenNumber", actual: value }];
457511
}
458512
}
@@ -469,20 +523,20 @@ describe("Test custom validation v1", () => {
469523
min: 10,
470524
max: 15,
471525
integer: true,
472-
custom(value){
526+
custom(value) {
473527
fn(this, value);
474528
if (value % 2 !== 0) return [{ type: "evenNumber", actual: value }];
475529
}
476530
}
477531
});
478532

479-
const res = check({num: 12});
533+
const res = check({ num: 12 });
480534
expect(res).toBe(true);
481535
expect(fn).toBeCalledWith(v, 12);
482536

483-
expect(check({num: 8})[0].type).toEqual("numberMin");
484-
expect(check({num: 18})[0].type).toEqual("numberMax");
485-
expect(check({num: 13})[0].type).toEqual("evenNumber");
537+
expect(check({ num: 8 })[0].type).toEqual("numberMin");
538+
expect(check({ num: 18 })[0].type).toEqual("numberMax");
539+
expect(check({ num: 13 })[0].type).toEqual("evenNumber");
486540
});
487541

488542
it("should work with multiple custom validators", () => {
@@ -491,21 +545,21 @@ describe("Test custom validation v1", () => {
491545
const check = v.compile({
492546
a: {
493547
type: "number",
494-
custom(value){
548+
custom(value) {
495549
fn(value);
496550
if (value % 2 !== 0) return [{ type: "evenNumber", actual: value }];
497551
}
498552
},
499553
b: {
500554
type: "number",
501-
custom(value){
555+
custom(value) {
502556
fn(value);
503557
if (value % 2 !== 0) return [{ type: "evenNumber", actual: value }];
504558
}
505559
}
506560
});
507561

508-
const res = check({a: 12, b:10});
562+
const res = check({ a: 12, b: 10 });
509563
expect(res).toBe(true);
510564
expect(fn).toBeCalledTimes(2);
511565
});
@@ -531,8 +585,8 @@ describe("Test custom validation", () => {
531585
min: 10,
532586
max: 15,
533587
integer: true,
534-
custom(value, errors){
535-
fn(this ,value, errors);
588+
custom(value, errors) {
589+
fn(this, value, errors);
536590
if (value % 2 !== 0) errors.push({ type: "evenNumber", actual: value });
537591
return value;
538592
}
@@ -543,13 +597,13 @@ describe("Test custom validation", () => {
543597
});
544598

545599
it("should work correctly with custom validator", () => {
546-
const res = check({num: 12});
600+
const res = check({ num: 12 });
547601
expect(res).toBe(true);
548602
expect(fn).toBeCalledWith(v, 12, []);
549603

550-
expect(check({num: 8})[0].type).toEqual("numberMin");
551-
expect(check({num: 18})[0].type).toEqual("numberMax");
552-
expect(check({num: 13})[0].type).toEqual("evenNumber");
604+
expect(check({ num: 8 })[0].type).toEqual("numberMin");
605+
expect(check({ num: 18 })[0].type).toEqual("numberMax");
606+
expect(check({ num: 13 })[0].type).toEqual("evenNumber");
553607
});
554608

555609
it("should call checker function after build-in rule", () => {
@@ -599,7 +653,7 @@ describe("Test default values", () => {
599653
arr: {
600654
type: "array",
601655
items: "number",
602-
default: [1,2,3]
656+
default: [1, 2, 3]
603657
},
604658
obj: {
605659
type: "object",
@@ -642,7 +696,7 @@ describe("Test default values", () => {
642696
num: 123,
643697
boolT: true,
644698
boolF: false,
645-
arr: [1,2,3],
699+
arr: [1, 2, 3],
646700
obj: {
647701
id: 1,
648702
name: "abc",
@@ -655,7 +709,7 @@ describe("Test default values", () => {
655709
});
656710

657711
expect(fn).toBeCalledTimes(1);
658-
expect(fn).toBeCalledWith(schema.par.properties.name, "par.name", obj, expect.any(Object) );
712+
expect(fn).toBeCalledWith(schema.par.properties.name, "par.name", obj, expect.any(Object));
659713
});
660714
});
661715

@@ -776,7 +830,7 @@ describe("Test plugins", () => {
776830
});
777831
});
778832

779-
describe("Test addMessage" , () => {
833+
describe("Test addMessage", () => {
780834
const v = new Validator();
781835
v.addMessage("string", "C");
782836
expect(v.messages.string).toBe("C");
@@ -977,7 +1031,7 @@ describe("Test normalize", () => {
9771031
d: {
9781032
type: "multi",
9791033
optional: false,
980-
rules: [{type: "string"}, {type: "boolean"}]
1034+
rules: [{ type: "string" }, { type: "boolean" }]
9811035
},
9821036
e: {
9831037
type: "array",

0 commit comments

Comments
 (0)