Repository: icebob/fastest-validator (js)

Issue:
bug: TypeError: Cannot convert object to primitive value
**What?**

The string validator is in someway evaluating the objects coming as params and it breaks if it has native prototype references (e.g. toString, toJSON)

**How to reproduce it?**

```js
const Validator = require("fastest-validator");
const v = new Validator();
const schema = {    name: { type: "string" },};
const check = v.compile(schema);

console.log(check({ name: "test"})); // it works, and passes
console.log(check({ name: { "abc": 1 }})); // it works, and rejects
console.log(check({ name: { "toString": 1 }})); // it fails - TypeError: Cannot convert object to primitive value
```

**Environment:**

$ node --version
v12.16.1
macOS Catalina 10.15.16
fastest-validator version: 1.11.0

**Comments:**
I didn't inspect the code deeply, but if you have any suggestions about how to solve it, I will be glad to help!

Pull request:
fix issue #235
fixes #235

Patch:
diff --git a/lib/helpers/replace.js b/lib/helpers/replace.js
new file mode 100644
index 0000000..d4b2096
--- /dev/null
+++ b/lib/helpers/replace.js
@@ -0,0 +1,8 @@
+function convertible(value) {
+	if (value === undefined) return "";
+	if (value === null) return "";
+	if (typeof value.toString === "function") return value;
+	return typeof value;
+}
+
+module.exports = (string, searchValue, newValue) => string.replace(searchValue, convertible(newValue));
diff --git a/lib/validator.js b/lib/validator.js
index cd93da9..47896f9 100644
--- a/lib/validator.js
+++ b/lib/validator.js
@@ -5,8 +5,8 @@ try {
 	AsyncFunction = (new Function("return Object.getPrototypeOf(async function(){}).constructor"))();
 } catch(err) { /* async is not supported */}
 
-//const flatten = require("./helpers/flatten");
 const deepExtend = require("./helpers/deep-extend");
+const replace = require("./helpers/replace");
 
 function loadMessages() {
 	return Object.assign({} , require("./messages"));
@@ -167,7 +167,10 @@ class Validator {
 			async: schema.$$async === true,
 			rules: [],
 			fn: [],
-			customs: {}
+			customs: {},
+			utils: {
+				replace,
+			},
 		};
 		this.cache.clear();
 		delete schema.$$async;
@@ -205,11 +208,11 @@ class Validator {
 		sourceCode.push("if (errors.length) {");
 		sourceCode.push(`
 			return errors.map(err => {
-				if (err.message)
-					err.message = err.message
-						.replace(/\\{field\\}/g, err.field || "")
-						.replace(/\\{expected\\}/g, err.expected != null ? err.expected : "")
-						.replace(/\\{actual\\}/g, err.actual != null ? err.actual : "");
+				if (err.message) {
+					err.message = context.utils.replace(err.message, /\\{field\\}/g, err.field);
+					err.message = context.utils.replace(err.message, /\\{expected\\}/g, err.expected);
+					err.message = context.utils.replace(err.message, /\\{actual\\}/g, err.actual);
+				}
 
 				return err;
 			});


Test patch:
diff --git a/test/helpers/replace.spec.js b/test/helpers/replace.spec.js
new file mode 100644
index 0000000..dac5935
--- /dev/null
+++ b/test/helpers/replace.spec.js
@@ -0,0 +1,21 @@
+const replace = require("../../lib/helpers/replace");
+
+describe("replace", () => {
+	it("should replace string", () => {
+		expect(replace("foo bar", "foo", "zoo")).toBe("zoo bar");
+	});
+
+	it("should replace if newValue is null or undefined", () => {
+		expect(replace("foo bar", "foo", undefined)).toBe(" bar");
+		expect(replace("foo bar", "foo", null)).toBe(" bar");
+	});
+
+	it("should replace if newValue has valid toString prototype", () => {
+		expect(replace("foo bar", "foo", { a: "b" })).toBe("[object Object] bar");
+		expect(replace("foo bar", "foo", ["a", "b"])).toBe("a,b bar");
+	});
+
+	it("should replace if newValue has invalid toString prototype", () => {
+		expect(replace("foo bar", "foo", { toString: 1 })).toBe("object bar");
+	});
+});
diff --git a/test/integration.spec.js b/test/integration.spec.js
index adfa637..b10f7b4 100644
--- a/test/integration.spec.js
+++ b/test/integration.spec.js
@@ -1285,3 +1285,20 @@ describe("Test context meta", () => {
 		expect(obj).toEqual({ name: "from-meta" });
 	});
 });
+
+describe("edge cases", () => {
+	const v = new Validator({ useNewCustomCheckerFunction: true });
+
+	it("issue #235 bug", () => {
+		const schema = { name: { type: "string" } };
+		const check = v.compile(schema);
+		expect(check({ name: { toString: 1 } })).toEqual([
+			{
+				actual: { toString: 1 },
+				field: "name",
+				message: "The 'name' field must be a string.",
+				type: "string",
+			},
+		]);
+	});
+});
diff --git a/test/typescript/validator.spec.ts b/test/typescript/validator.spec.ts
index 3ef59fc..e17cc0b 100644
--- a/test/typescript/validator.spec.ts
+++ b/test/typescript/validator.spec.ts
@@ -116,13 +116,13 @@ describe('TypeScript Definitions', () => {
 
             check = v.compile(schema);
 
-            const context = {
+            const context = expect.objectContaining({
                 customs: expect.any(Object),
                 rules: expect.any(Array),
                 fn: expect.any(Array),
                 index: 2,
 				async: false
-            };
+            });
 
             expect(validFn).toHaveBeenCalledTimes(1);
             expect(validFn).toHaveBeenCalledWith(expect.any(Object), 'a', context);
diff --git a/test/validator.spec.js b/test/validator.spec.js
index af8396a..74044c4 100644
--- a/test/validator.spec.js
+++ b/test/validator.spec.js
@@ -154,13 +154,13 @@ describe("Test add", () => {
 
 		check = v.compile(schema);
 
-		const context = {
+		const context = expect.objectContaining({
 			customs: expect.any(Object),
 			rules: expect.any(Array),
 			fn: expect.any(Array),
 			index: 2,
 			async: false
-		};
+		});
 
 
 		expect(validFn).toHaveBeenCalledTimes(1);