Repository: icebob/fastest-validator
Pull request #177 (2020-07-10 09:35:01)

ObjectID rule
For validating mongodb/bson ObjectID. I'll update README.md after approvement


Patch:
diff --git a/README.md b/README.md
index a7da48f..aa98221 100644
--- a/README.md
+++ b/README.md
@@ -96,6 +96,8 @@ $ npm run bench
     - [Properties](#properties-12)
   - [`uuid`](#uuid)
     - [Properties](#properties-13)
+  - [`objectID`](#objectID)
+    - [Properties](#properties-14)
 - [Custom validator](#custom-validator)
   - [Custom validation for built-in rules](#custom-validation-for-built-in-rules)
 - [Custom error messages (l10n)](#custom-error-messages-l10n)
@@ -1007,6 +1009,47 @@ Property | Default  | Description
 -------- | -------- | -----------
 `version`  | `4`   | UUID version in range 1-5.
 
+## `objectID`
+You can validate BSON/MongoDB ObjectID's
+```js
+const  { ObjectID } = require("mongodb") // or anywhere else 
+
+const schema = {
+    id: {
+        type: "objectID",
+        ObjectID // passing the ObjectID class
+    }  
+}
+
+const check = v.compile(schema);
+check({ id: "5f082780b00cc7401fb8e8fc" }) // ok
+check({ id: new ObjectID() }) // ok
+check({ id: "5f082780b00cc7401fb8e8" }) // Error
+```
+
+**Pro tip:**  By using defaults props for objectID rule, No longer needed to pass `ObjectID` class in validation schema:
+
+```js
+const  { ObjectID } = require("mongodb") // or anywhere else 
+
+const v = new Validator({
+    defaults: {
+        objectID: {
+            ObjectID
+        }
+    }
+})
+
+const schema = {
+    id: "objectID" 
+}
+```
+
+### Properties
+Property | Default  | Description
+-------- | -------- | -----------
+`convert`  | `false`   | If `true`, the validator converts ObjectID HexString representation to ObjectID `instance`
+
 
 # Custom validator
 You can also create your custom validator.
diff --git a/index.d.ts b/index.d.ts
index 788c52d..a028935 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -328,6 +328,24 @@ declare module "fastest-validator" {
 		maxProps?: number;
 	}
 
+	export interface RuleObjectID extends RuleCustom {
+		/**
+		 * Name of built-in validator
+		 */
+		type: "objectID";
+		/**
+		 * To inject ObjectID dependency
+		 */
+		ObjectID?: {
+			new (id?: string | number | ObjectIdAbstract): ObjectIdAbstract;
+			isValid(o: any): boolean;
+		};
+		/**
+		 * Convert HexStringObjectID to ObjectID
+		 */
+		convert?: boolean;
+	}
+
 	/**
 	 * Validation schema definition for "string" built-in validator
 	 * @see https://github.com/icebob/fastest-validator#string
@@ -727,6 +745,7 @@ declare module "fastest-validator" {
 		| RuleMulti
 		| RuleNumber
 		| RuleObject
+		| RuleObjectID
 		| RuleString
 		| RuleTuple
 		| RuleURL
@@ -815,23 +834,23 @@ declare module "fastest-validator" {
 		};
 
 		/**
-	 	 * For set aliases
-	 	 */
+		 * For set aliases
+		 */
 		aliases?: {
-			[key: string]: ValidationRuleObject
-		}
+			[key: string]: ValidationRuleObject;
+		};
 
 		/**
-	 	 * For set custom rules.
-	 	 */
+		 * For set custom rules.
+		 */
 		customRules?: {
-			[key: string]: CompilationFunction
-		}
+			[key: string]: CompilationFunction;
+		};
 
 		/**
-	 	 * For set plugins.
-	 	 */
-		plugins?: PluginFn<any>[]
+		 * For set plugins.
+		 */
+		plugins?: PluginFn<any>[];
 	}
 
 	export interface CompilationRule {
@@ -883,7 +902,13 @@ declare module "fastest-validator" {
 		context: Context
 	) => { sanitized?: boolean; source: string };
 
-	export type PluginFn<T = void> = (validator: Validator) => T
+	export type PluginFn<T = void> = (validator: Validator) => T;
+
+	export class ObjectIdAbstract {
+		static isValid(o: any): boolean;
+		constructor(id?: string | number | ObjectIdAbstract);
+		toHexString(): string;
+	}
 
 	export default class Validator {
 		/**
@@ -915,12 +940,12 @@ declare module "fastest-validator" {
 		add(type: string, fn: CompilationFunction): void;
 
 		/**
-	 	 * Add a message
-	 	 *
-	 	 * @param {String} name
-	 	 * @param {String} message
-	 	 */
-		addMessage(name: string, message: string): void
+		 * Add a message
+		 *
+		 * @param {String} name
+		 * @param {String} message
+		 */
+		addMessage(name: string, message: string): void;
 
 		/**
 		 * Register an alias in validation object
@@ -930,11 +955,11 @@ declare module "fastest-validator" {
 		alias(name: string, validationRule: ValidationRuleObject): void;
 
 		/**
-	 	 * Add a plugin
-	 	 *
+		 * Add a plugin
+		 *
 		 * @param {Function} fn
-	 	*/
-		plugin<T = void>(fn: PluginFn<T>): T
+		 */
+		plugin<T = void>(fn: PluginFn<T>): T;
 
 		/**
 		 * Build error message
@@ -960,7 +985,7 @@ declare module "fastest-validator" {
 		 * @return {(value: any) => (true | ValidationError[])} function that can be used next for validation of current schema
 		 */
 		compile<T = any>(
-		  schema: ValidationSchema<T> | ValidationSchema<T>[],
+			schema: ValidationSchema<T> | ValidationSchema<T>[]
 		): (value: any) => true | ValidationError[];
 
 		/**
diff --git a/lib/messages.js b/lib/messages.js
index 11897ee..be46b6d 100644
--- a/lib/messages.js
+++ b/lib/messages.js
@@ -72,5 +72,7 @@ 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."
+	classInstanceOf: "The '{field}' field must be an instance of the '{expected}' class.",
+
+	objectID: "The '{field}' field must be an valid ObjectID",
 };
diff --git a/lib/rules/objectID.js b/lib/rules/objectID.js
new file mode 100644
index 0000000..1d463a3
--- /dev/null
+++ b/lib/rules/objectID.js
@@ -0,0 +1,23 @@
+"use strict";
+
+/**	Signature: function(value, field, parent, errors, context)
+ */
+module.exports = function({ schema, messages, index }, path, context) {
+	const src = [];
+
+	if (!context.customs[index]) context.customs[index] = { schema };
+	else context.customs[index].schema = schema;
+
+	src.push(`
+		const ObjectID = context.customs[${index}].schema.ObjectID;
+		if (!ObjectID.isValid(value)) {
+			${this.makeError({ type: "objectID", actual: "value", messages })}
+		}
+
+		${schema.convert ? "return new ObjectID(value)" : "return value;" } ;
+	`);
+
+	return {
+		source: src.join("\n")
+	};
+};
diff --git a/lib/validator.js b/lib/validator.js
index 53e2522..a746550 100644
--- a/lib/validator.js
+++ b/lib/validator.js
@@ -23,6 +23,7 @@ function loadRules() {
 		multi: require("./rules/multi"),
 		number: require("./rules/number"),
 		object: require("./rules/object"),
+		objectID: require("./rules/objectID"),
 		string: require("./rules/string"),
 		tuple: require("./rules/tuple"),
 		url: require("./rules/url"),
diff --git a/package-lock.json b/package-lock.json
index 67789fa..87f8ace 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1788,6 +1788,15 @@
         "@babel/types": "^7.3.0"
       }
     },
+    "@types/bson": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.2.tgz",
+      "integrity": "sha512-+uWmsejEHfmSjyyM/LkrP0orfE2m5Mx9Xel4tXNeqi1ldK5XMQcDsFkBmLDtuyKUbxj2jGDo0H240fbCRJZo7Q==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*"
+      }
+    },
     "@types/color-name": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
@@ -1876,6 +1885,16 @@
       "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
       "dev": true
     },
+    "@types/mongodb": {
+      "version": "3.5.25",
+      "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.25.tgz",
+      "integrity": "sha512-2H/Owt+pHCl9YmBOYnXc3VdnxejJEjVdH+QCWL5ZAfPehEn3evygKBX3/vKRv7aTwfNbUd0E5vjJdQklH/9a6w==",
+      "dev": true,
+      "requires": {
+        "@types/bson": "*",
+        "@types/node": "*"
+      }
+    },
     "@types/node": {
       "version": "13.13.5",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.5.tgz",
@@ -2416,6 +2435,16 @@
       "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
       "dev": true
     },
+    "bl": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz",
+      "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==",
+      "dev": true,
+      "requires": {
+        "readable-stream": "^2.3.5",
+        "safe-buffer": "^5.1.1"
+      }
+    },
     "bluebird": {
       "version": "3.7.2",
       "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@@ -3290,6 +3319,12 @@
       "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
       "dev": true
     },
+    "denque": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
+      "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==",
+      "dev": true
+    },
     "depcheck": {
       "version": "0.8.3",
       "resolved": "https://registry.npmjs.org/depcheck/-/depcheck-0.8.3.tgz",
@@ -6948,6 +6983,13 @@
         "object-visit": "^1.0.0"
       }
     },
+    "memory-pager": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
+      "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
+      "dev": true,
+      "optional": true
+    },
     "meow": {
       "version": "3.7.0",
       "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
@@ -7112,6 +7154,28 @@
         "minimist": "^1.2.5"
       }
     },
+    "mongodb": {
+      "version": "3.5.9",
+      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.9.tgz",
+      "integrity": "sha512-vXHBY1CsGYcEPoVWhwgxIBeWqP3dSu9RuRDsoLRPTITrcrgm1f0Ubu1xqF9ozMwv53agmEiZm0YGo+7WL3Nbug==",
+      "dev": true,
+      "requires": {
+        "bl": "^2.2.0",
+        "bson": "^1.1.4",
+        "denque": "^1.4.1",
+        "require_optional": "^1.0.1",
+        "safe-buffer": "^5.1.2",
+        "saslprep": "^1.0.0"
+      },
+      "dependencies": {
+        "bson": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.4.tgz",
+          "integrity": "sha512-S/yKGU1syOMzO86+dGpg2qGoDL0zvzcb262G+gqEy6TgP6rt6z6qxSFX/8X6vLC91P7G7C3nLs0+bvDzmvBA3Q==",
+          "dev": true
+        }
+      }
+    },
     "ms": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -8958,6 +9022,30 @@
       "integrity": "sha1-wR6XJ2tluOKSP3Xav1+y7ww4Qbk=",
       "dev": true
     },
+    "require_optional": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
+      "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
+      "dev": true,
+      "requires": {
+        "resolve-from": "^2.0.0",
+        "semver": "^5.1.0"
+      },
+      "dependencies": {
+        "resolve-from": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
+          "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=",
+          "dev": true
+        },
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
     "resolve": {
       "version": "1.17.0",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
@@ -9351,6 +9439,16 @@
         }
       }
     },
+    "saslprep": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
+      "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "sparse-bitfield": "^3.0.3"
+      }
+    },
     "saxes": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
@@ -9672,6 +9770,16 @@
       "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
       "dev": true
     },
+    "sparse-bitfield": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+      "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "memory-pager": "^1.0.2"
+      }
+    },
     "spdx-correct": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
diff --git a/package.json b/package.json
index 5507e27..b1e8dc3 100644
--- a/package.json
+++ b/package.json
@@ -45,12 +45,14 @@
   "devDependencies": {
     "@ampproject/rollup-plugin-closure-compiler": "^0.26.0",
     "@types/jest": "^25.2.3",
+    "@types/mongodb": "^3.5.25",
     "benchmarkify": "^2.1.2",
     "cli-highlight": "^2.1.4",
     "coveralls": "^3.0.9",
     "eslint": "^7.1.0",
     "jest": "^26.0.1",
     "jest-cli": "^26.0.1",
+    "mongodb": "^3.5.9",
     "nodemon": "^2.0.4",
     "npm-check": "^5.9.0",
     "prettier": "^2.0.5",


Test patch:
diff --git a/test/rules/objectID.spec.js b/test/rules/objectID.spec.js
new file mode 100644
index 0000000..e575e72
--- /dev/null
+++ b/test/rules/objectID.spec.js
@@ -0,0 +1,30 @@
+"use strict";
+
+const Validator = require("../../lib/validator");
+const v = new Validator();
+const { ObjectID } = require("mongodb");
+
+describe("Test rule: objectID", () => {
+
+	it("should validate ObjectID", () => {
+		const check = v.compile({ id: { type: "objectID", ObjectID } });
+		const message = "The 'id' field must be an valid ObjectID";
+
+		expect(check({ id: "5f082780b00cc7401fb8"})).toEqual([{ type: "objectID", field: "id", actual: "5f082780b00cc7401fb8", message }]);
+		expect(check({ id: new ObjectID() })).toEqual(true);
+      
+		const o = { id: "5f082780b00cc7401fb8e8fc" };
+		expect(check(o)).toEqual(true);
+		expect(o.id).toBe("5f082780b00cc7401fb8e8fc");
+	});
+   
+	it("should convert hexString-objectID to ObjectID", () => {
+		const check = v.compile({ id: { type: "objectID", ObjectID, convert: true } });
+		const  oid = new ObjectID();
+		const o = { id: oid.toHexString() };
+
+		expect(check(o)).toEqual(true);
+		expect(o.id).toBeInstanceOf(ObjectID);
+		expect(o.id).toEqual(oid);
+	});
+});
diff --git a/test/typescript/rules/objectID.spec.ts b/test/typescript/rules/objectID.spec.ts
new file mode 100644
index 0000000..e191d33
--- /dev/null
+++ b/test/typescript/rules/objectID.spec.ts
@@ -0,0 +1,32 @@
+/// <reference path="../../../index.d.ts" /> // here we make a reference to exists module definition
+import ValidatorType, { RuleObjectID } from 'fastest-validator'; // here we importing type definition of default export
+import { ObjectID } from 'mongodb';
+
+const Validator: typeof ValidatorType = require('../../../index'); // here we importing real Validator Constructor
+const v: ValidatorType = new Validator();
+
+describe("Test rule: objectID", () => {
+
+	it("should validate ObjectID", () => {
+      const rule: RuleObjectID = { type: "objectID", ObjectID };
+		const check = v.compile({ id: { type: "objectID", ObjectID } });
+		const message = "The 'id' field must be an valid ObjectID";
+
+		expect(check({ id: "5f082780b00cc7401fb8"})).toEqual([{ type: "objectID", field: "id", actual: "5f082780b00cc7401fb8", message }]);
+		expect(check({ id: new ObjectID() })).toEqual(true);
+      
+		const o = { id: "5f082780b00cc7401fb8e8fc" };
+		expect(check(o)).toEqual(true);
+		expect(o.id).toBe("5f082780b00cc7401fb8e8fc");
+	});
+   
+	it("should convert hexString-objectID to ObjectID", () => {
+		const check = v.compile({ id: { type: "objectID", ObjectID, convert: true } });
+		const  oid = new ObjectID();
+		const o = { id: oid.toHexString() };
+
+		expect(check(o)).toEqual(true);
+		expect(o.id).toBeInstanceOf(ObjectID);
+		expect(o.id).toEqual(oid);
+	});
+});
diff --git a/test/typescript/validator.spec.ts b/test/typescript/validator.spec.ts
index be563b5..4a1a0b0 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', 20);
+            expect(Object.keys(v.rules)).toHaveProperty('length', 21);
         });
 
         it('should create instance with custom messages', () => {
diff --git a/test/validator.spec.js b/test/validator.spec.js
index 4b2a128..5348199 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(20);
+		expect(Object.keys(v.rules).length).toBe(21);
 	});
 
 	it("should create instance with custom messages", () => {