Repository: icebob/fastest-validator
Pull request #126 (2020-04-10 17:31:51)

New rule 'class'


Patch:
diff --git a/README.md b/README.md
index 695c0ee..641875f 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,9 @@ $ npm run bench
 ```
 
 # Table of contents
-- [Key features](#key-features)
+
+- [fastest-validator ![NPM version](https://www.npmjs.com/package/fastest-validator) [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=The%20fastest%20JS%20validator%20library%20for%20NodeJS&url=https://github.com/icebob/fastest-validator&via=Icebobcsi&hashtags=nodejs,javascript)](#fastest-validator-img-src%22httpswwwnpmjscompackagefastest-validator%22-alt%22npm-version%22-img-src%22httpsimgshieldsiotwitterurlhttpshieldsiosvgstylesocial%22-alt%22tweet%22)
+  - [Key features](#key-features)
 - [How fast?](#how-fast)
 - [Table of contents](#table-of-contents)
   - [Installation](#installation)
@@ -70,29 +72,31 @@ $ npm run bench
     - [Properties](#properties)
   - [`boolean`](#boolean)
     - [Properties](#properties-1)
-  - [`date`](#date)
+  - [`class`](#class)
     - [Properties](#properties-2)
-  - [`email`](#email)
+  - [`date`](#date)
     - [Properties](#properties-3)
-  - [`enum`](#enum)
+  - [`email`](#email)
     - [Properties](#properties-4)
-  - [`equal`](#equal)
+  - [`enum`](#enum)
     - [Properties](#properties-5)
-  - [`forbidden`](#forbidden)
+  - [`equal`](#equal)
     - [Properties](#properties-6)
+  - [`forbidden`](#forbidden)
+    - [Properties](#properties-7)
   - [`function`](#function)
   - [`luhn`](#luhn)
   - [`mac`](#mac)
   - [`multi`](#multi)
   - [`number`](#number)
-    - [Properties](#properties-7)
-  - [`object`](#object)
     - [Properties](#properties-8)
-  - [`string`](#string)
+  - [`object`](#object)
     - [Properties](#properties-9)
+  - [`string`](#string)
+    - [Properties](#properties-10)
   - [`url`](#url)
   - [`uuid`](#uuid)
-    - [Properties](#properties-10)
+    - [Properties](#properties-11)
 - [Custom validator](#custom-validator)
   - [Custom validation for built-in rules](#custom-validation-for-built-in-rules)
 - [Custom error messages (l10n)](#custom-error-messages-l10n)
@@ -460,6 +464,24 @@ v.validate({ status: "true" }, {
 }); // Valid
 ```
 
+## `class`
+This is a `Class` validator to check the value is an instance of a Class.
+
+```js
+const schema = {
+    rawData: { type: "class", instanceOf: Buffer }
+}
+
+v.validate({ rawData: Buffer.from([1, 2, 3]) }, schema); // Valid
+v.validate({ rawData: 100 }, schema); // Fail
+```
+
+### Properties
+Property | Default  | Description
+-------- | -------- | -----------
+`instanceOf` | `null` | Checked Class.
+
+
 ## `date`
 This is a `Date` validator.
 
diff --git a/examples/full.js b/examples/full.js
index 25d0838..feb891d 100644
--- a/examples/full.js
+++ b/examples/full.js
@@ -41,6 +41,7 @@ const schema = {
 	phone: { type: "string", length: 15, custom: v => v.startsWith("+") ? true : [{ type: "phoneNumber" }] },
 	action: "function",
 	created: "date",
+	raw: { type: "class", instanceOf: Buffer },
 	now: { type: "date", convert: true }
 };
 
@@ -91,7 +92,8 @@ const obj = {
 	phone: "+36-70-123-4567",
 	action: () => {},
 	created: new Date(),
-	now: Date.now()
+	now: Date.now(),
+	raw: Buffer.from([1,2,3])
 };
 
 const res = v.validate(obj, schema);
diff --git a/index.d.ts b/index.d.ts
index 8362979..b432aab 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -6,6 +6,7 @@ declare module 'fastest-validator' {
 		'any'
 		| 'array'
 		| 'boolean'
+		| 'class'
 		| 'custom'
 		| 'date'
 		| 'email'
@@ -90,6 +91,21 @@ declare module 'fastest-validator' {
 		convert?: boolean;
 	}
 
+	/**
+	 * Validation schema definition for "class" built-in validator
+	 * @see https://github.com/icebob/fastest-validator#class
+	 */
+	interface RuleClass<T = any> extends RuleCustom {
+		/**
+		 * Name of built-in validator
+		 */
+		type: 'class';
+		/**
+		 * Checked Class
+		 */
+		instanceOf?: T;
+	}
+
 	/**
 	 * Validation schema definition for "date" built-in validator
 	 * @see https://github.com/icebob/fastest-validator#date
@@ -662,6 +678,7 @@ declare module 'fastest-validator' {
 		RuleAny
 		| RuleArray
 		| RuleBoolean
+		| RuleClass
 		| RuleDate
 		| RuleEmail
 		| RuleEqual
@@ -745,7 +762,7 @@ declare module 'fastest-validator' {
 		messages?: MessagesType,
 
 		/**
-		 * Default settings for rules 
+		 * Default settings for rules
 		 */
 		defaults?: {
 			[key in ValidationRuleName]: ValidationSchema
@@ -856,6 +873,7 @@ declare module 'fastest-validator' {
 		RuleAny,
 		RuleArray,
 		RuleBoolean,
+		RuleClass,
 		RuleDate,
 		RuleEmail,
 		RuleEnum,
diff --git a/lib/messages.js b/lib/messages.js
index 5f04d08..d463b00 100644
--- a/lib/messages.js
+++ b/lib/messages.js
@@ -62,4 +62,6 @@ module.exports = {
 
 	uuid: "The '{field}' field must be a valid UUID.",
 	uuidVersion: "The '{field}' field must be a valid UUID version provided.",
+
+	classInstanceOf: "The '{field}' field must be an instance of the '{expected}' class."
 };
diff --git a/lib/rules/class.js b/lib/rules/class.js
new file mode 100644
index 0000000..5455855
--- /dev/null
+++ b/lib/rules/class.js
@@ -0,0 +1,29 @@
+"use strict";
+
+/**	Signature: function(value, field, parent, errors, context)
+ */
+module.exports = function({ schema, messages, customValidation }, path, context) {
+	const src = [];
+
+	if (context.customs[path]) {
+		context.customs[path].instanceOf = schema.instanceOf;
+	} else {
+		context.customs[path] = { instanceOf: schema.instanceOf };
+	}
+
+	const className = schema.instanceOf.name ? schema.instanceOf.name : "<UnknowClass>";
+
+	src.push(`
+		if (!(value instanceof context.customs["${path}"].instanceOf))
+			${this.makeError({ type: "classInstanceOf",  actual: "value", expected: "'" + className + "'", messages })}
+	`);
+
+	src.push(`
+		${customValidation("value")}
+		return value;
+	`);
+
+	return {
+		source: src.join("\n")
+	};
+};
diff --git a/lib/validator.js b/lib/validator.js
index 529325d..28089e6 100644
--- a/lib/validator.js
+++ b/lib/validator.js
@@ -9,6 +9,7 @@ function loadRules() {
 		any: require("./rules/any"),
 		array: require("./rules/array"),
 		boolean: require("./rules/boolean"),
+		class: require("./rules/class"),
 		custom: require("./rules/custom"),
 		date: require("./rules/date"),
 		email: require("./rules/email"),


Test patch:
diff --git a/test/rules/class.spec.js b/test/rules/class.spec.js
new file mode 100644
index 0000000..9f4d862
--- /dev/null
+++ b/test/rules/class.spec.js
@@ -0,0 +1,17 @@
+"use strict";
+
+const Validator = require("../../lib/validator");
+const v = new Validator();
+
+describe("Test rule: class", () => {
+
+	it("should value instanceOf Buffer", () => {
+		const check = v.compile({ rawData: { type: "class", instanceOf: Buffer } });
+		const message = "The 'rawData' field must be an instance of the 'Buffer' class.";
+
+		expect(check({ rawData: "1234"})).toEqual([{ type: "classInstanceOf", field: "rawData", actual: "1234", expected: "Buffer", message }]);
+		expect(check({ rawData: [1, 2, 3]})).toEqual([{ type: "classInstanceOf", field: "rawData", actual: [1, 2, 3], expected: "Buffer", message }]);
+		expect(check({ rawData: Buffer.from([1, 2, 3]) })).toEqual(true);
+		expect(check({ rawData: Buffer.alloc(3) })).toEqual(true);
+	});
+});
diff --git a/test/typescript/rules/class.spec.ts b/test/typescript/rules/class.spec.ts
new file mode 100644
index 0000000..e99b48d
--- /dev/null
+++ b/test/typescript/rules/class.spec.ts
@@ -0,0 +1,18 @@
+/// <reference path="../../../index.d.ts" /> // here we make a reference to exists module definition
+import ValidatorType from 'fastest-validator'; // here we importing type definition of default export
+
+const Validator: typeof ValidatorType = require('../../../index'); // here we importing real Validator Constructor
+const v: ValidatorType = new Validator();
+
+describe("Test rule: class", () => {
+
+	it("should value instanceOf Buffer", () => {
+		const check = v.compile({ rawData: { type: "class", instanceOf: Buffer } });
+		const message = "The 'rawData' field must be an instance of the 'Buffer' class.";
+
+		expect(check({ rawData: "1234"})).toEqual([{ type: "classInstanceOf", field: "rawData", actual: "1234", expected: "Buffer", message }]);
+		expect(check({ rawData: [1, 2, 3]})).toEqual([{ type: "classInstanceOf", field: "rawData", actual: [1, 2, 3], expected: "Buffer", message }]);
+		expect(check({ rawData: Buffer.from([1, 2, 3]) })).toEqual(true);
+		expect(check({ rawData: Buffer.alloc(3) })).toEqual(true);
+	});
+});
diff --git a/test/typescript/validator.spec.ts b/test/typescript/validator.spec.ts
index 9cdf32b..e51fb79 100644
--- a/test/typescript/validator.spec.ts
+++ b/test/typescript/validator.spec.ts
@@ -14,7 +14,7 @@ describe('TypeScript Definitions', () => {
             expect(v.validate).toBeInstanceOf(Function);
             expect(v.add).toBeInstanceOf(Function);
 
-            expect(Object.keys(v.rules)).toHaveProperty('length', 18);
+            expect(Object.keys(v.rules)).toHaveProperty('length', 19);
         });
 
         it('should create instance with custom messages', () => {
diff --git a/test/validator.spec.js b/test/validator.spec.js
index e748cf4..a4b3b3b 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(18);
+		expect(Object.keys(v.rules).length).toBe(19);
 	});
 
 	it("should create instance with custom messages", () => {
@@ -299,7 +299,7 @@ describe("Test compile (integration test)", () => {
 			expect(res).toBe(true);
 			expect(customValidator.mock.calls[0][2]).toBe("customValue");
 		});
-	}); */	
+	}); */
 });
 
 describe("Test aliases", () => {
@@ -364,7 +364,7 @@ describe("Test custom validation for built-in rules", () => {
 
 
 	it("should compile without error", () => {
-		
+
 		check = v.compile({
 			num: {
 				type: "number",
@@ -399,7 +399,7 @@ describe("Test default settings", () => {
 				strict: "remove"
 			}
 		}
-	});	
+	});
 
 	it("should consider default settings", () => {
 		const check = v.compile({
@@ -452,4 +452,4 @@ describe("Test default settings", () => {
 			}
 		});
 	});
-});
\ No newline at end of file
+});