Repository: icebob/fastest-validator
Pull request #104 (2019-12-13 12:34:59)

add unique validation in array rule


Patch:
diff --git a/README.md b/README.md
index af79ea7..cd373ec 100644
--- a/README.md
+++ b/README.md
@@ -355,6 +355,18 @@ v.validate({ roles: ["user", "admin"] }, schema); // Valid
 v.validate({ roles: ["guest"] }, schema); // Fail
 ```
 
+**Example for `unique`:**
+```js
+const schema = {
+    roles: { type: "array", unique: true }
+}
+
+v.validate({ roles: ["user"] }, schema); // Valid
+v.validate({ roles: [{role:"user"},{role:"admin"},{role:"user"}] }, schema); // Valid
+v.validate({ roles: ["user", "admin", "user"] }, schema); // Fail
+v.validate({ roles: [1, 2, 1] }, schema); // Fail
+```
+
 ### Properties
 Property | Default  | Description
 -------- | -------- | -----------
@@ -363,6 +375,7 @@ Property | Default  | Description
 `max`  	 | `null`   | Maximum count of elements.
 `length` | `null`   | Fix count of elements.
 `contains` | `null` | The array must contain this element too.
+`unique` | `null` | The array must be unique (array of objects is always unique).
 `enum`	 | `null`   | Every element must be an element of the `enum` array.
 `items`	 | `null`   | Schema for array items.
 
@@ -969,6 +982,7 @@ Name                | Default text
 `arrayMax`	| The '{field}' field must contain less than or equal to {expected} items.
 `arrayLength`	| The '{field}' field must contain {expected} items.
 `arrayContains`	| The '{field}' field must contain the '{expected}' item.
+`arrayUnique` | The '{actual}' value in '{field}' field does not unique the '{expected}' values.
 `arrayEnum`	| The '{actual}' value in '{field}' field does not match any of the '{expected}' values.
 `boolean`	| The '{field}' field must be a boolean.
 `function`	| The '{field}' field must be a function.
diff --git a/lib/messages.js b/lib/messages.js
index 3b32ab4..5f04d08 100644
--- a/lib/messages.js
+++ b/lib/messages.js
@@ -31,6 +31,7 @@ module.exports = {
 	arrayMax: "The '{field}' field must contain less than or equal to {expected} items.",
 	arrayLength: "The '{field}' field must contain {expected} items.",
 	arrayContains: "The '{field}' field must contain the '{expected}' item.",
+	arrayUnique: "The '{actual}' value in '{field}' field does not unique the '{expected}' values.",
 	arrayEnum: "The '{actual}' value in '{field}' field does not match any of the '{expected}' values.",
 
 	boolean: "The '{field}' field must be a boolean.",
diff --git a/lib/rules/array.js b/lib/rules/array.js
index afa1514..04da100 100644
--- a/lib/rules/array.js
+++ b/lib/rules/array.js
@@ -54,6 +54,14 @@ module.exports = function({ schema, messages }, path, context) {
 		`);
 	}
 
+	if (schema.unique === true) {
+		src.push(`
+			if(len > (new Set(value)).size) {
+				${this.makeError({ type: "arrayUnique", expected: "Array.from(new Set(value.filter((item, index) => value.indexOf(item) !== index)))", actual: "value", messages })}
+			}
+		`);
+	}
+
 	if (schema.enum != null) {
 		const enumStr = JSON.stringify(schema.enum);
 		src.push(`


Test patch:
diff --git a/test/messages.spec.js b/test/messages.spec.js
index b194167..42abc71 100644
--- a/test/messages.spec.js
+++ b/test/messages.spec.js
@@ -30,6 +30,7 @@ describe("Test Messages", () => {
 		expect(msg.arrayMax).toBeDefined();
 		expect(msg.arrayLength).toBeDefined();
 		expect(msg.arrayContains).toBeDefined();
+		expect(msg.arrayUnique).toBeDefined();
 		expect(msg.arrayEnum).toBeDefined();
 		expect(msg.boolean).toBeDefined();
 		expect(msg.function).toBeDefined();
diff --git a/test/rules/array.spec.js b/test/rules/array.spec.js
index c42c32c..5403530 100644
--- a/test/rules/array.spec.js
+++ b/test/rules/array.spec.js
@@ -71,6 +71,16 @@ describe("Test rule: array", () => {
 		expect(check([8, 5, 2])).toEqual(true);
 	});
 
+	it("check unique", () => {
+		const check = v.compile({ $$root: true, type: "array", unique: true });
+
+		expect(check(["bob","john","bob"])).toEqual([{ type: "arrayUnique", expected: ["bob"], actual: ["bob", "john", "bob"], message: "The 'bob,john,bob' value in '' field does not unique the 'bob' values." }]);
+		expect(check(["bob","john","bob","bob","john"])).toEqual([{ type: "arrayUnique", expected: ["bob","john"], actual: ["bob","john","bob","bob","john"], message: "The 'bob,john,bob,bob,john' value in '' field does not unique the 'bob,john' values." }]);
+		expect(check([1,2,1,false,true,false])).toEqual([{ type: "arrayUnique", expected: [1,false], actual: [1,2,1,false,true,false], message: "The '1,2,1,false,true,false' value in '' field does not unique the '1,false' values." }]);
+		expect(check([{name:"bob"},{name:"john"},{name:"bob"}])).toEqual(true);
+		expect(check(["john", "bob"])).toEqual(true);
+	});
+
 	it("check enum", () => {
 		const check = v.compile({ $$root: true, type: "array", enum: ["male", "female"] });