Repository: icebob/fastest-validator
Pull request #306 (2022-08-28 09:57:04)

Using labels in error messages
Hello, 
I implemented the feature mentioned in issue #209 .
Using label in messages instead of property name.

```js
const schema = {
	email: { type: "email", label: "Email Address" },
};
const check = v.compile(schema);
check({ email: "notAnEmail" });
/* Returns
[
  {
    type: 'email',
    message: "The 'Email Address' field must be a valid e-mail.",
    field: 'email',
    actual: 'notAnEmail',
    label: 'Email Address'
  }
]
*/
```


Patch:
diff --git a/README.md b/README.md
index 186fd90..de49a6c 100644
--- a/README.md
+++ b/README.md
@@ -369,7 +369,28 @@ const v = new FastestValidator({
     }
 });
 ```
+# Label Option
+You can use label names in error messages instead of property names.
+```js
+const schema = {
+	email: { type: "email", label: "Email Address" },
+};
+const check = v.compile(schema);
+
+console.log(check({ email: "notAnEmail" }));
 
+/* Returns
+[
+  {
+    type: 'email',
+    message: "The 'Email Address' field must be a valid e-mail.",
+    field: 'email',
+    actual: 'notAnEmail',
+    label: 'Email Address'
+  }
+]
+*/
+```
 # Built-in validators
 
 ## `any`
diff --git a/lib/rules/object.js b/lib/rules/object.js
index fe93f37..0b87b0c 100644
--- a/lib/rules/object.js
+++ b/lib/rules/object.js
@@ -56,13 +56,16 @@ module.exports = function ({ schema, messages }, path, context) {
 			const safePropName = `parentObj${safeSubName}`;
 			const newPath = (path ? path + "." : "") + property;
 
+			const labelName = subSchema[property].label;
+			const label = labelName ? `'${escapeEvalString(labelName)}'` : undefined;
+			
 			sourceCode.push(`\n// Field: ${escapeEvalString(newPath)}`);
 			sourceCode.push(`field = parentField ? parentField + "${safeSubName}" : "${name}";`);
 			sourceCode.push(`value = ${safePropName};`);
-
+			sourceCode.push(`label = ${label}`);
 			const rule = this.getRuleFromSchema(subSchema[property]);
 			const innerSource = `
-				${safePropName} = ${context.async ? "await " : ""}context.fn[%%INDEX%%](value, field, parentObj, errors, context);
+				${safePropName} = ${context.async ? "await " : ""}context.fn[%%INDEX%%](value, field, parentObj, errors, context, label);
 			`;
 			sourceCode.push(this.compileRule(rule, context, newPath, innerSource, safePropName));
 			if (this.opts.haltOnFirstError === true) {
diff --git a/lib/validator.js b/lib/validator.js
index 380425b..d39b697 100644
--- a/lib/validator.js
+++ b/lib/validator.js
@@ -201,20 +201,21 @@ class Validator {
 			"var errors = [];",
 			"var field;",
 			"var parent = null;",
+			`var label = ${schema.label ? "\"" + schema.label + "\"" : "null"};`
 		];
 
 		const rule = this.getRuleFromSchema(schema);
-		sourceCode.push(this.compileRule(rule, context, null, `${context.async ? "await " : ""}context.fn[%%INDEX%%](value, field, null, errors, context);`, "value"));
+		sourceCode.push(this.compileRule(rule, context, null, `${context.async ? "await " : ""}context.fn[%%INDEX%%](value, field, null, errors, context, label);`, "value"));
 
 		sourceCode.push("if (errors.length) {");
 		sourceCode.push(`
 			return errors.map(err => {
 				if (err.message) {
-					err.message = context.utils.replace(err.message, /\\{field\\}/g, err.field);
+					err.message = context.utils.replace(err.message, /\\{field\\}/g, err.label || err.field);
 					err.message = context.utils.replace(err.message, /\\{expected\\}/g, err.expected);
 					err.message = context.utils.replace(err.message, /\\{actual\\}/g, err.actual);
 				}
-
+				if(!err.label) delete err.label
 				return err;
 			});
 		`);
@@ -282,7 +283,7 @@ class Validator {
 			const res = rule.ruleFunction.call(this, rule, path, context);
 			res.source = res.source.replace(/%%INDEX%%/g, rule.index);
 			const FnClass = context.async ? AsyncFunction : Function;
-			const fn = new FnClass("value", "field", "parent", "errors", "context", res.source);
+			const fn = new FnClass("value", "field", "parent", "errors", "context", "label", res.source);
 			context.fn[rule.index] = fn.bind(this);
 			sourceCode.push(this.wrapRequiredCheckSourceCode(rule, innerSrc.replace(/%%INDEX%%/g, rule.index), context, resVar));
 			sourceCode.push(this.makeCustomValidator({vName: resVar, path: customPath, schema: rule.schema, context, messages: rule.messages, ruleIndex: rule.index}));
@@ -380,6 +381,7 @@ class Validator {
 		else o.field = "field";
 		if (expected != null) o.expected = expected;
 		if (actual != null) o.actual = actual;
+		o.label = "label";
 
 		const s = Object.keys(o)
 			.map(key => `${key}: ${o[key]}`)


Test patch:
diff --git a/test/typescript/validator.spec.ts b/test/typescript/validator.spec.ts
index c056da9..843d78e 100644
--- a/test/typescript/validator.spec.ts
+++ b/test/typescript/validator.spec.ts
@@ -229,10 +229,10 @@ describe('TypeScript Definitions', () => {
 
 		it('should generate an error creation code', () => {
 			expect(v.makeError({ type: 'required', messages: v.messages })).
-				toBe('errors.push({ type: "required", message: "The \'{field}\' field is required.", field: field });');
+				toBe('errors.push({ type: "required", message: "The \'{field}\' field is required.", field: field, label: label });');
 			expect(v.makeError({ type: 'stringMin', field: 'firstName', expected: 6, actual: 3, messages: v.messages })).
 				toBe(
-					'errors.push({ type: "stringMin", message: "The \'{field}\' field length must be greater than or equal to {expected} characters long.", field: "firstName", expected: 6, actual: 3 });');
+					'errors.push({ type: "stringMin", message: "The \'{field}\' field length must be greater than or equal to {expected} characters long.", field: "firstName", expected: 6, actual: 3, label: label });');
 		});
 
 	});
diff --git a/test/validator.spec.js b/test/validator.spec.js
index c635b9e..c6ed2b1 100644
--- a/test/validator.spec.js
+++ b/test/validator.spec.js
@@ -294,8 +294,8 @@ describe("Test makeError", () => {
 	const v = new Validator();
 
 	it("should generate an error creation code", () => {
-		expect(v.makeError({ type: "required", messages: v.messages })).toBe("errors.push({ type: \"required\", message: \"The '{field}' field is required.\", field: field });");
-		expect(v.makeError({ type: "stringMin", field: "firstName", expected: 6, actual: 3, messages: v.messages })).toBe("errors.push({ type: \"stringMin\", message: \"The '{field}' field length must be greater than or equal to {expected} characters long.\", field: \"firstName\", expected: 6, actual: 3 });");
+		expect(v.makeError({ type: "required", messages: v.messages })).toBe("errors.push({ type: \"required\", message: \"The '{field}' field is required.\", field: field, label: label });");
+		expect(v.makeError({ type: "stringMin", field: "firstName", expected: 6, actual: 3, messages: v.messages })).toBe("errors.push({ type: \"stringMin\", message: \"The '{field}' field length must be greater than or equal to {expected} characters long.\", field: \"firstName\", expected: 6, actual: 3, label: label });");
 	});
 
 });
@@ -340,6 +340,22 @@ describe("Test compile (integration test)", () => {
 
 	});
 
+	describe("Test label in error message instead of property names", () => {
+		const v = new Validator();
+		const schema = {
+			email: { type: "email", label: "Email Address" },
+		};
+
+		let check = v.compile(schema);
+
+		it("Should return message with label value", () => {
+			let res = check({});
+
+			expect(res[0].label).toBe(schema.email.label);
+			expect(res[0].message).toBe("The 'Email Address' field is required.");
+		});
+	});
+
 	describe("Test check generator with wrong obj and haltOnFirstError", () => {
 		const v = new Validator({ haltOnFirstError: true });