Repository: icebob/fastest-validator (js)

Issue:
v0.6.11 broke some specific schemas
I use UUID literals nested keys in validation schema its work on 0.6.10 and was broken in 0.6.11.
Simple example code:
```js
const schema = {
  minPrice: {
    type: 'object',
    props: {
      value: { type: 'number', positive: true, integer: true },
      enabled: 'boolean',
      regions: {
        type: 'object',
        optional: true,
        props: { '12345678-ef85-4c62-b8f3-e00fbbc155d4': { type: 'number', positive: true, integer: true, optional: true } },
      },
    },
  },
};

/**
 * Next line throws
 * SyntaxError: Invalid or unexpected token
 * at new Function (<anonymous>)
 * at Validator.compileSchemaObject (node_modules\fastest-validator\lib\validator.js:114:33)
 * at Validator.compileSchemaRule (node_modules\fastest-validator\lib\validator.js:154:24)
 * at Validator.compileSchemaType (ec-order\node_modules\fastest-validator\dist\index.js:472:15)
 *
 * Generated code looks like
 * propertyPath = (path !== undefined ? path + ".12345678-ef85-4c62-b8f3-e00fbbc155d4" : "12345678-ef85-4c62-b8f3-e00fbbc155d4");
 * res = this.checkSchemaRule(value.12345678-ef85-4c62-b8f3-e00fbbc155d4, compiledObject[0].compiledType, propertyPath, value);
 * if (res !== true) {             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 *  this.handleResult(errors, propertyPath, res);
 * }
 */
const result = v.validate(data, schema);
```

At the moment, I don't have enough time to make a pull request.

Pull request:
Handle irregular object property names
Fixes #35 

Two notes:
- No particular handling of reserved keywords as property names. They are supported since ES5, and the codegen already uses ES6/ES2015 keywords like `const`
- Does not optimize all valid unquoted Unicode property names allowed by the spec. This requires a Unicode library or including some large-ish tables

Patch:
diff --git a/lib/validator.js b/lib/validator.js
index cf1ea4a..0de60a1 100644
--- a/lib/validator.js
+++ b/lib/validator.js
@@ -22,6 +22,34 @@ function loadRules() {
 	};
 }
 
+// Quick regex to match most common unquoted JavaScript property names. Note the spec allows Unicode letters.
+// Unmatched property names will be quoted and validate slighly slower. https://www.ecma-international.org/ecma-262/5.1/#sec-7.6
+const identifierRegex = /^[_$a-zA-Z][_$a-zA-Z0-9]*$/;
+
+// Regex to escape quoted property names for eval/new Function
+const escapeEvalRegex = /["'\\\n\r\u2028\u2029]/g;
+
+function escapeEvalString(str) {
+	// Based on https://github.com/joliss/js-string-escape
+	return str.replace(escapeEvalRegex, function (character) {
+		switch (character) {
+			case "\"":
+			case "'":
+			case "\\":
+				return "\\" + character;
+			// Four possible LineTerminator characters need to be escaped:
+			case "\n":
+				return "\\n";
+			case "\r":
+				return "\\r";
+			case "\u2028":
+				return "\\u2028";
+			case "\u2029":
+				return "\\u2029";
+		}
+	});
+}
+
 /**
  * Validator class constructor
  *
@@ -106,12 +134,14 @@ Validator.prototype.compileSchemaObject = function(schemaObject) {
 	sourceCode.push("const errors = [];");
 	for (let i = 0; i < compiledObject.properties.length; i++) {
 		const property = compiledObject.properties[i];
-		const name = property.name;
+		const name = escapeEvalString(property.name);
+		const propertyValueExpr = identifierRegex.test(name) ? `value.${name}` : `value["${name}"]`;
+
 		sourceCode.push(`propertyPath = (path !== undefined ? path + ".${name}" : "${name}");`);
 		if (Array.isArray(property.compiledType)) {
-			sourceCode.push(`res = this.checkSchemaType(value.${name}, properties[${i}].compiledType, propertyPath, value);`);
+			sourceCode.push(`res = this.checkSchemaType(${propertyValueExpr}, properties[${i}].compiledType, propertyPath, value);`);
 		} else {
-			sourceCode.push(`res = this.checkSchemaRule(value.${name}, properties[${i}].compiledType, propertyPath, value);`);
+			sourceCode.push(`res = this.checkSchemaRule(${propertyValueExpr}, properties[${i}].compiledType, propertyPath, value);`);
 		}
 		sourceCode.push("if (res !== true) {");
 		sourceCode.push("\tthis.handleResult(errors, propertyPath, res);");


Test patch:
diff --git a/test/validator.spec.js b/test/validator.spec.js
index db914ba..5cfddde 100644
--- a/test/validator.spec.js
+++ b/test/validator.spec.js
@@ -1062,3 +1062,71 @@ describe("Test recursive/cyclic schema", () => {
 		expect(res[0].field).toBe("subcategories[1].subcategories[0].name");
 	});
 });
+
+describe("Test irregular object property names", () => {
+	const v = new Validator();
+	it("should compile schema with dash", () => {
+		const schema = {
+			"1-1": { type: "string" },
+		};
+
+		const res = v.validate({
+			"1-1": "test",
+		}, schema);
+		expect(res).toBe(true);
+	});
+
+	it("should compile schema with quotes", () => {
+		const schema = {
+			"a'bc": { type: "string" },
+			"a\"bc": { type: "string" },
+		};
+
+		const res = v.validate({ "a'bc": "test", "a\"bc": "test" }, schema);
+		expect(res).toBe(true);
+	});
+
+	it("should compile schema with linebreak", () => {
+		const schema = {
+			"a\nbc\ndef": { type: "string" },
+			"a\rbc": { type: "string" },
+			"a\u2028bc": { type: "string" },
+			"a\u2029bc": { type: "string" },
+		};
+
+		const res = v.validate({
+			"a\nbc\ndef": "test",
+			"a\rbc": "test",
+			"a\u2028bc": "test",
+			"a\u2029bc": "test",
+		}, schema);
+		expect(res).toBe(true);
+	});
+
+	it("should compile schema with escape characters", () => {
+		const schema = {
+			"\\o/": { type: "string" },
+		};
+
+		const res = v.validate({ "\\o/": "test" }, schema);
+		expect(res).toBe(true);
+	});
+
+	it("should compile schema with reserved keyword", () => {
+		// Reserved keywords are permitted as unquoted property names in ES5+. There is no special support for these
+		const schema = {
+			for: { type: "string" },
+			goto: { type: "string" },
+			var: { type: "string" },
+			try: { type: "string" },
+		};
+
+		const res = v.validate({
+			for: "hello",
+			goto: "hello",
+			var: "test",
+			try: "test",
+		}, schema);
+		expect(res).toBe(true);
+	});
+});