diff --git a/lib/validator.js b/lib/validator.js index 265072e..f37c7ad 100644 --- a/lib/validator.js +++ b/lib/validator.js @@ -109,6 +109,15 @@ Validator.prototype._processRule = function(rule, name, iterate) { throw new Error("Invalid '" + rule.type + "' type in validator schema!"); } + + /** + * !IMPORTANT!: For the functioning of multiRule cases it is important that + * pushing of object and array special rules is done in directly after this + * simple rule. + * If you which to push other checks, do it before the simple ones or after + * the array special case. + * See the comments in _checkWrapper for further explanation. + */ checks.push({ fn: this.rules[rule.type], type: rule.type, @@ -125,7 +134,8 @@ Validator.prototype._processRule = function(rule, name, iterate) { type: rule.type, name: name, schema: rule, - iterate: iterate + iterate: iterate, + secondPart: true //first part is the "primitive" typeof check above }); } @@ -137,7 +147,8 @@ Validator.prototype._processRule = function(rule, name, iterate) { type: rule.type, name: name, schema: rule, - iterate: true + iterate: true, + secondPart: true //first part is the "primitive" typeof check above }); } @@ -181,18 +192,37 @@ Validator.prototype._checkWrapper = function(rules, isMultipleRules) { } else { // Call the checker function if (check.iterate) { + let errorInCurrentArray = false; const l = value.length; for (let i = 0; i < l; i++) { let _stack = stack + "[" + i + "]"; let res = check.fn.call(self, value[i], schema, _stack, obj); - if (res !== true) + if (res !== true) { + errorInCurrentArray = true; self.handleResult(errors, _stack, res); + } + } + /** + * If this is second part of a multiRule array check and the array + * is valid, then the rule is valid. + */ + if (!errorInCurrentArray && isMultipleRules && check.secondPart) { + return true; } } else { let res = check.fn.call(self, value, schema, stack, obj); if (isMultipleRules) { if (res === true) { + /** + * Object and array checks are divided into two internal checks. In case of a multiRule + * we have to make sure to check both parts. Thus we we continue to also do the second + * check if their is one. + */ + const nextRule = rules[i+1] + if (nextRule && nextRule.secondPart){ + continue; + } // Jump out after first success and clear previous errors return true; } diff --git a/test/validator.spec.js b/test/validator.spec.js index 1defe67..24aabe4 100644 --- a/test/validator.spec.js +++ b/test/validator.spec.js @@ -631,3 +631,266 @@ describe("Test multiple rules", () => { }); }); + +describe("Test multiple rules with objects", () => { + const v = new Validator(); + + let schema = { + list: [ + { + type: "object", + props: { + name: {type: "string"}, + age: {type: "number"}, + } + }, + { + type: "object", + props: { + country: {type: "string"}, + code: {type: "string"}, + } + } + ] + }; + + let check = v.compile(schema); + + it("should give true if first object is given", () => { + let obj = { list: { + name: "Joe", + age: 34 + } }; + + let res = check(obj); + + expect(res).toBe(true); + }); + + it("should give true if second object is given", () => { + let obj = { list: { + country: "germany", + code: "de" + }}; + + let res = check(obj); + + expect(res).toBe(true); + }); + + it("should give error if the object is broken", () => { + let obj = { list: { + name: "Average", + age: "Joe" + } }; + + let res = check(obj); + + expect(res).toBeInstanceOf(Array); + expect(res.length).toBe(3); + expect(res[0].type).toBe("number"); + expect(res[0].field).toBe("list.age"); + + expect(res[1].type).toBe("required"); + expect(res[1].field).toBe("list.country"); + }); + + it("should give error if the object is only partly given", () => { + let obj = { list: {} }; + let res = check(obj); + + expect(res).toBeInstanceOf(Array); + expect(res.length).toBe(4); + expect(res[0].type).toBe("required"); + expect(res[0].field).toBe("list.name"); + + expect(res[1].type).toBe("required"); + expect(res[1].field).toBe("list.age"); + + }); + +}); + +describe("Test multiple rules with objects within array", () => { + const v = new Validator(); + + let schema = { + list: { + type: "array", + items: [ + { + type: "object", + props: { + name: {type: "string"}, + age: {type: "number"}, + } + }, + { + type: "object", + props: { + country: {type: "string"}, + code: {type: "string"}, + } + } + ] + } + }; + + let check = v.compile(schema); + + it("should give true if one valid object is given", () => { + let obj = { list: [ + { + name: "Joe", + age: 34 + } + ]}; + let res = check(obj); + expect(res).toBe(true); + + let obj2 = { list: [ + { + country: "germany", + code: "de" + } + ]}; + let res2 = check(obj2); + expect(res2).toBe(true); + }); + + it("should give true if three valid objects given", () => { + let obj = { list: [ + { + name: "Joe", + age: 34 + }, + { + country: "germany", + code: "de" + }, + { + country: "hungary", + code: "hu" + } + ]}; + let res = check(obj); + expect(res).toBe(true); + }); + + it("should give error if one object is broken", () => { + let obj = { list: [ + { + name: "Joe", + age: 34 + }, + { + country: "germany", + }, + { + country: "hungary", + code: "hu" + } + ]}; + + let res = check(obj); + + expect(res).toBeInstanceOf(Array); + expect(res.length).toBe(3); + expect(res[0].type).toBe("required"); + expect(res[0].field).toBe("list[1].name"); + + expect(res[1].type).toBe("required"); + expect(res[1].field).toBe("list[1].age"); + }); + + it("should give error if one object is empty", () => { + let obj = { list: [ + { + name: "Joe", + age: 34 + }, + { + country: "hungary", + code: "hu" + }, + { + } + ]}; + + let res = check(obj); + + expect(res).toBeInstanceOf(Array); + expect(res.length).toBe(4); + expect(res[0].type).toBe("required"); + expect(res[0].field).toBe("list[2].name"); + + expect(res[1].type).toBe("required"); + expect(res[1].field).toBe("list[2].age"); + + }); + +}); + +describe("Test multiple rules with arrays", () => { + const v = new Validator(); + + let schema = { + list: [ + { + type: "array", + items: "string" + }, + { + type: "array", + items: "number" + } + ] + }; + + let check = v.compile(schema); + + it("should give true if first array is given", () => { + let obj = { list: ["hello", "there", "this", "is", "a", "test"] }; + + let res = check(obj); + + expect(res).toBe(true); + }); + + it("should give true if second array is given", () => { + let obj = { list: [1, 3, 3, 7] }; + + let res = check(obj); + + expect(res).toBe(true); + }); + + it("should give error if the array is broken", () => { + let obj = { list: ["hello", 3] }; + + let res = check(obj); + + expect(res).toBeInstanceOf(Array); + expect(res.length).toBe(2); + expect(res[0].type).toBe("string"); + expect(res[0].field).toBe("list[1]"); + + expect(res[1].type).toBe("number"); + expect(res[1].field).toBe("list[0]"); + }); + + it("should give error if the array is broken", () => { + let obj = { list: [true, false] }; + let res = check(obj); + + expect(res).toBeInstanceOf(Array); + expect(res.length).toBe(4); + expect(res[0].type).toBe("string"); + expect(res[0].field).toBe("list[0]"); + + expect(res[1].type).toBe("string"); + expect(res[1].field).toBe("list[1]"); + + }); + +}); \ No newline at end of file