Repository: icebob/fastest-validator
Pull request #85 (2019-09-29 12:50:33)

add rules mac and luhn
@icebob I want a new version release =)


Patch:
diff --git a/README.md b/README.md
index 86319a9..e3a4cde 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
 
 ## Key features
 * fast! Really!
-* 13 built-in validators
+* 15+ built-in validators
 * custom validators
 * nested objects & array handling
 * strict object validation
@@ -487,6 +487,39 @@ Property | Default  | Description
 -------- | -------- | -----------
 `version`  | `4`   | UUID version in range 1-5.
 
+## `mac`
+This is an MAC addresses validator. 
+
+```js
+let schema = {
+    mac: { type: "mac" }
+}
+
+v.validate({ mac: "01:C8:95:4B:65:FE" }, schema); // Valid
+v.validate({ mac: "01:c8:95:4b:65:fe", schema); // Valid
+v.validate({ mac: "01C8.954B.65FE" }, schema); // Valid
+v.validate({ mac: "01c8.954b.65fe", schema); // Valid
+v.validate({ mac: "01-C8-95-4B-65-FE" }, schema); // Valid
+v.validate({ mac: "01-c8-95-4b-65-fe" }, schema); // Valid
+v.validate({ mac: "01:C8:95:4B:65:FE" }, schema); // Fail
+```
+
+## `luhn`
+This is an Luhn validator.
+[Luhn algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm) checksum
+Credit Card numbers, IMEI numbers, National Provider Identifier numbers and others 
+
+```js
+let schema = {
+    cc: { type: "luhn" }
+}
+
+v.validate({ cc: "452373989901198" }, schema); // Valid
+v.validate({ cc: 452373989901198 }, schema); // Valid
+v.validate({ cc: "4523-739-8990-1198" }, schema); // Valid
+v.validate({ cc: "452373989901198" }, schema); // Fail
+```
+
 # Custom validator
 You can also create your custom validator.
 
@@ -707,6 +740,8 @@ All files        |      100 |      100 |      100 |      100 |
   enum.js        |      100 |      100 |      100 |      100 |                   |
   forbidden.js   |      100 |      100 |      100 |      100 |                   |
   function.js    |      100 |      100 |      100 |      100 |                   |
+  luhn.js        |      100 |      100 |      100 |      100 |                   |
+  mac.js         |      100 |      100 |      100 |      100 |                   |
   number.js      |      100 |      100 |      100 |      100 |                   |
   object.js      |      100 |      100 |      100 |      100 |                   |
   string.js      |      100 |      100 |      100 |      100 |                   |
diff --git a/lib/messages.js b/lib/messages.js
index 6a3f8ab..7f498c1 100644
--- a/lib/messages.js
+++ b/lib/messages.js
@@ -53,4 +53,6 @@ module.exports = {
 	objectStrict: "The object '{field}' contains invalid keys: '{actual}'!",
 	uuid: "The {field} field must be a valid UUID",
 	uuidVersion: "The {field} field must be a valid version provided",
-};
\ No newline at end of file
+	mac: "The {field} field must be a valid MAC address",
+	luhn: "The {field} field must be a valid checksum luhn",
+};
diff --git a/lib/rules/luhn.js b/lib/rules/luhn.js
new file mode 100644
index 0000000..71bc3af
--- /dev/null
+++ b/lib/rules/luhn.js
@@ -0,0 +1,33 @@
+"use strict";
+
+/**
+ * Luhn algorithm checksum https://en.wikipedia.org/wiki/Luhn_algorithm
+ * Credit Card numbers, IMEI numbers, National Provider Identifier numbers and others
+ * @param value
+ * @param schema
+ * @return {boolean|{actual, expected, type}|ValidationError}
+ */
+module.exports = function checkLuhn(value, schema) {
+
+	if(typeof value !== "number" && typeof value !== "string")
+		return this.makeError("string");
+
+	if (typeof value !== "string")
+		value = String(value);
+
+	value = value.replace(/\D+/g, "");
+
+	const check = function (array) {
+		return number => {
+			let len = number ? number.length : 0,
+				bit = 1,
+				sum = 0;
+			while (len--) {
+				sum += !(bit ^= 1) ? parseInt(number[len], 10) : array[number[len]];
+			}
+			return sum % 10 === 0 && sum > 0;
+		};
+	}([0, 2, 4, 6, 8, 1, 3, 5, 7, 9]);
+
+	return check(value) || this.makeError("luhn");
+};
diff --git a/lib/rules/mac.js b/lib/rules/mac.js
new file mode 100644
index 0000000..defa595
--- /dev/null
+++ b/lib/rules/mac.js
@@ -0,0 +1,14 @@
+"use strict";
+
+const PATTERN = /^((([a-f0-9][a-f0-9]+[-]){5}|([a-f0-9][a-f0-9]+[:]){5})([a-f0-9][a-f0-9])$)|(^([a-f0-9][a-f0-9][a-f0-9][a-f0-9]+[.]){2}([a-f0-9][a-f0-9][a-f0-9][a-f0-9]))$/i;
+
+module.exports = function checkMAC(value, schema) {
+	if (typeof value !== "string")
+		return this.makeError("string");
+
+	value = value.toLowerCase();
+	if (!PATTERN.test(value))
+		return this.makeError("mac");
+
+	return true;
+};
diff --git a/lib/validator.js b/lib/validator.js
index 3e3eb7e..0fb9fda 100644
--- a/lib/validator.js
+++ b/lib/validator.js
@@ -19,7 +19,9 @@ function loadRules() {
 		object: require("./rules/object"),
 		string: require("./rules/string"),
 		url: require("./rules/url"),
-		uuid: require("./rules/uuid")
+		uuid: require("./rules/uuid"),
+		mac: require("./rules/mac"),
+		luhn: require("./rules/luhn")
 	};
 }
 
diff --git a/package-lock.json b/package-lock.json
index a323f44..e15b514 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6433,7 +6433,8 @@
             "ansi-regex": {
               "version": "2.1.1",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             },
             "aproba": {
               "version": "1.2.0",
@@ -6476,7 +6477,8 @@
             "code-point-at": {
               "version": "1.1.0",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             },
             "concat-map": {
               "version": "0.0.1",
@@ -6487,7 +6489,8 @@
             "console-control-strings": {
               "version": "1.1.0",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             },
             "core-util-is": {
               "version": "1.0.2",
@@ -6604,7 +6607,8 @@
             "inherits": {
               "version": "2.0.3",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             },
             "ini": {
               "version": "1.3.5",
@@ -6616,6 +6620,7 @@
               "version": "1.0.0",
               "bundled": true,
               "dev": true,
+              "optional": true,
               "requires": {
                 "number-is-nan": "^1.0.0"
               }
@@ -6638,12 +6643,14 @@
             "minimist": {
               "version": "0.0.8",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             },
             "minipass": {
               "version": "2.2.4",
               "bundled": true,
               "dev": true,
+              "optional": true,
               "requires": {
                 "safe-buffer": "^5.1.1",
                 "yallist": "^3.0.0"
@@ -6662,6 +6669,7 @@
               "version": "0.5.1",
               "bundled": true,
               "dev": true,
+              "optional": true,
               "requires": {
                 "minimist": "0.0.8"
               }
@@ -6742,7 +6750,8 @@
             "number-is-nan": {
               "version": "1.0.1",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             },
             "object-assign": {
               "version": "4.1.1",
@@ -6754,6 +6763,7 @@
               "version": "1.4.0",
               "bundled": true,
               "dev": true,
+              "optional": true,
               "requires": {
                 "wrappy": "1"
               }
@@ -6839,7 +6849,8 @@
             "safe-buffer": {
               "version": "5.1.1",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             },
             "safer-buffer": {
               "version": "2.1.2",
@@ -6875,6 +6886,7 @@
               "version": "1.0.2",
               "bundled": true,
               "dev": true,
+              "optional": true,
               "requires": {
                 "code-point-at": "^1.0.0",
                 "is-fullwidth-code-point": "^1.0.0",
@@ -6894,6 +6906,7 @@
               "version": "3.0.1",
               "bundled": true,
               "dev": true,
+              "optional": true,
               "requires": {
                 "ansi-regex": "^2.0.0"
               }
@@ -6937,12 +6950,14 @@
             "wrappy": {
               "version": "1.0.2",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             },
             "yallist": {
               "version": "3.0.2",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             }
           }
         },


Test patch:
diff --git a/test/messages.spec.js b/test/messages.spec.js
index 251c6b0..844b08b 100644
--- a/test/messages.spec.js
+++ b/test/messages.spec.js
@@ -43,7 +43,9 @@ describe("Test Messages", () => {
 		expect(msg.objectStrict).toBeDefined();
 		expect(msg.uuid).toBeDefined();
 		expect(msg.uuidVersion).toBeDefined();
+		expect(msg.mac).toBeDefined();
+		expect(msg.luhn).toBeDefined();
 
 	});
 
-});
\ No newline at end of file
+});
diff --git a/test/rules/luhn.spec.js b/test/rules/luhn.spec.js
new file mode 100644
index 0000000..e28259d
--- /dev/null
+++ b/test/rules/luhn.spec.js
@@ -0,0 +1,32 @@
+"use strict";
+
+const Validator = require("../../lib/validator");
+const fn = require("../../lib/rules/luhn");
+
+const v = new Validator();
+const check = fn.bind(v);
+
+describe("Test checkLuhn", () => {
+
+	it("should check type of value", () => {
+		const luhn = {type: "luhn"};
+		const err = {type: "luhn"};
+		const errString = {type: "string"};
+
+		expect(check(null, luhn)).toEqual(errString);
+		expect(check(undefined, luhn)).toEqual(errString);
+		expect(check(0, luhn)).toEqual(err);
+		expect(check(1, luhn)).toEqual(err);
+		expect(check("", luhn)).toEqual(err);
+		expect(check("true", luhn)).toEqual(err);
+		expect(check([], luhn)).toEqual(errString);
+		expect(check({}, luhn)).toEqual(errString);
+		expect(check(false, luhn)).toEqual(errString);
+		expect(check(true, luhn)).toEqual(errString);
+		expect(check("452373989911198", luhn)).toEqual(err);
+		expect(check("452373989901199", luhn)).toEqual(err);
+		expect(check("452373989901198", luhn)).toEqual(true);
+		expect(check("4523-739-8990-1198", luhn)).toEqual(true);
+	});
+
+});
diff --git a/test/rules/mac.spec.js b/test/rules/mac.spec.js
new file mode 100644
index 0000000..9430c63
--- /dev/null
+++ b/test/rules/mac.spec.js
@@ -0,0 +1,44 @@
+"use strict";
+
+const Validator = require("../../lib/validator");
+const fn = require("../../lib/rules/mac");
+
+const v = new Validator();
+const check = fn.bind(v);
+
+describe("Test checkMAC", () => {
+
+	it("should check type of value", () => {
+		const mac = {type: "mac"};
+		const err = {type: "mac"};
+		const errString = {type: "string"};
+
+		expect(check(null, mac)).toEqual(errString);
+		expect(check(undefined, mac)).toEqual(errString);
+		expect(check(0, mac)).toEqual(errString);
+		expect(check(1, mac)).toEqual(errString);
+		expect(check("", mac)).toEqual(err);
+		expect(check("true", mac)).toEqual(err);
+		expect(check([], mac)).toEqual(errString);
+		expect(check({}, mac)).toEqual(errString);
+		expect(check(false, mac)).toEqual(errString);
+		expect(check(true, mac)).toEqual(errString);
+		expect(check("018.954B.65FE", mac)).toEqual(err);
+		expect(check("01C8.95B.65FE", mac)).toEqual(err);
+		expect(check("01C8.954B.6FE", mac)).toEqual(err);
+		expect(check("1-C8-95-4B-65-FE", mac)).toEqual(err);
+		expect(check("01-C8-95-4B-65-F", mac)).toEqual(err);
+		expect(check("01-C8-95-4B-65-FE-A0", mac)).toEqual(err);
+		expect(check("1:C8:95:4B:65:FE", mac)).toEqual(err);
+		expect(check("01:8:95:4B:65:FE", mac)).toEqual(err);
+		expect(check("01:C8:95:4B:65:F", mac)).toEqual(err);
+		expect(check("01:C8:95:4B:65:FE:AF", mac)).toEqual(err);
+		expect(check("01:c8:95:4b:65:fe", mac)).toEqual(true);
+		expect(check("01:C8:95:4B:65:FE", mac)).toEqual(true);
+		expect(check("01c8.954b.65fe", mac)).toEqual(true);
+		expect(check("01C8.954B.65FE", mac)).toEqual(true);
+		expect(check("01-C8-95-4B-65-FE", mac)).toEqual(true);
+		expect(check("01-c8-95-4b-65-fe", mac)).toEqual(true);
+	});
+
+});
diff --git a/test/validator.spec.js b/test/validator.spec.js
index 2c38e22..63969ae 100644
--- a/test/validator.spec.js
+++ b/test/validator.spec.js
@@ -12,7 +12,7 @@ describe("Test constructor", () => {
 		expect(v.validate).toBeInstanceOf(Function);
 		expect(v.add).toBeInstanceOf(Function);
 
-		expect(Object.keys(v.rules).length).toBe(14);
+		expect(Object.keys(v.rules).length).toBe(16);
 	});
 
 	it("should create instance with custom messages", () => {