Repository: icebob/fastest-validator (js)

Issue:
Possible bug: Optional enum string fails validation on empty input
## Description

I do not know if this behaviour is by design or not but when I declare some prop like this one:

```javascript
const schema = {
      Status: {
        type: 'string',
        uppercase: true,
        enum: ['a', 'b', 'c'],
        optional: true,
        nullable: true
      }
}
validate({Status: ''}, schema)
```

the validation process fails inferring that the empty value `''` is not in the `stringEnum`, even if I've specified the `optional` and the `nullable` features. Is this by design? Or shouldn't be the null and empty string, in this case, considered as part of the enumeration or checked before validating the enum? If so it should be documented or at least using `optional` or `nullable` with `enum` should throw an exception...


Pull request description:
check `empty: true` in string rule. Fixes #283
```js
const schema = {
	status: {
		type: "string",
		uppercase: true,
		enum: ["a", "b", "c"],
		empty: true
	}
};

const check = v.compile(schema);
const obj = { status: "" };

console.log(check(obj), obj);
```

Patch:
diff --git a/examples/issue-283.js b/examples/issue-283.js
new file mode 100644
index 0000000..9afc42a
--- /dev/null
+++ b/examples/issue-283.js
@@ -0,0 +1,22 @@
+const Validator = require("../index");
+
+const v = new Validator({
+	debug: true,
+	useNewCustomCheckerFunction: true,
+});
+
+const schema = {
+	status: {
+		type: "string",
+		uppercase: true,
+		enum: ["a", "b", "c"],
+		//optional: true, // checks "status === undefined"
+		//nullable: true, // checks "status === null"
+		empty: true
+	}
+};
+
+const check = v.compile(schema);
+const obj = { status: "" };
+
+console.log(check(obj), obj);
diff --git a/lib/rules/string.js b/lib/rules/string.js
index 164083f..c7df637 100644
--- a/lib/rules/string.js
+++ b/lib/rules/string.js
@@ -106,6 +106,12 @@ module.exports = function checkString({ schema, messages }, path, context) {
 				${this.makeError({ type: "stringEmpty",  actual: "value", messages })}
 			}
 		`);
+	} else if (schema.empty === true) {
+		src.push(`
+			if (len === 0) {
+				return value;
+			}
+		`);
 	}
 
 	if (schema.min != null) {
@@ -137,16 +143,9 @@ module.exports = function checkString({ schema, messages }, path, context) {
 		if (typeof schema.pattern == "string")
 			pattern = new RegExp(schema.pattern, schema.patternFlags);
 
-		const patternValidator = `
-			if (!${pattern.toString()}.test(value))
-				${this.makeError({ type: "stringPattern", expected: `"${pattern.toString().replace(/"/g, "\\$&")}"`, actual: "origValue", messages })}
-		`;
-
 		src.push(`
-			if (${schema.empty} === true && len === 0) {
-				// Do nothing
-			} else {
-				${patternValidator}
+			if (!${pattern.toString()}.test(value)) {
+				${this.makeError({ type: "stringPattern", expected: `"${pattern.toString().replace(/"/g, "\\$&")}"`, actual: "origValue", messages })}
 			}
 		`);
 	}
diff --git a/package-lock.json b/package-lock.json
index 747b6bf..a152e99 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7399,7 +7399,8 @@
         },
         "dot-prop": {
           "version": "4.2.0",
-          "resolved": "",
+          "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
+          "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
           "dev": true,
           "requires": {
             "is-obj": "^1.0.0"


Test patch:
diff --git a/test/rules/string.spec.js b/test/rules/string.spec.js
index df42405..ee3fc65 100644
--- a/test/rules/string.spec.js
+++ b/test/rules/string.spec.js
@@ -95,6 +95,13 @@ describe("Test rule: string", () => {
 		expect(check("JOHN")).toEqual(true);
 	});
 
+	it("check pattern with empty string", () => {
+		const check = v.compile({ $$root: true, type: "string", pattern: "^[A-Z]+$", patternFlags: "g", empty: true });
+
+		expect(check("")).toEqual(true);
+		expect(check("JOHN")).toEqual(true);
+	});
+
 	it("check escape pattern", () => {
 		const pattern = /^(([^<>()[]\.,;:\s@"]+(.[^<>()[]\.,;:\s@"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))$/;
 		const check = v.compile({ $$root: true, type: "string", pattern });
@@ -119,6 +126,16 @@ describe("Test rule: string", () => {
 		expect(check("female")).toEqual(true);
 	});
 
+	it("check enum with enabled empty", () => {
+		const check = v.compile({ $$root: true, type: "string", enum: ["male", "female"], empty: true });
+		const message = "The '' field does not match any of the allowed values.";
+
+		expect(check("")).toEqual(true);
+		expect(check("human")).toEqual([{ type: "stringEnum", expected: "male, female", actual: "human", message }]);
+		expect(check("male")).toEqual(true);
+		expect(check("female")).toEqual(true);
+	});
+
 	it("check numeric string", () => {
 		const check = v.compile({ $$root: true, type: "string", numeric: true});
 		const message = "The '' field must be a numeric string.";