diff --git a/README.md b/README.md index 493d1ef..4f1efdd 100644 --- a/README.md +++ b/README.md @@ -1547,7 +1547,7 @@ Name | Default text `dateMin` | The '{field}' field must be greater than or equal to {expected}. `dateMax` | The '{field}' field must be less than or equal to {expected}. `forbidden` | The '{field}' field is forbidden. -‍‍`email` | The '{field}' field must be a valid e-mail. +`email` | The '{field}' field must be a valid e-mail. `emailEmpty` | The '{field}' field must not be empty. `emailMin` | The '{field}' field length must be greater than or equal to {expected} characters long. `emailMax` | The '{field}' field length must be less than or equal to {expected} characters long. @@ -1571,6 +1571,20 @@ Name | Description `expected` | The expected value `actual` | The actual value +# Pass custom metas +In some case, you will need to do something with the validation schema . +Like reusing the validator to pass custom settings, you can use properties starting with `$$` + +````typescript +const check = v.compile({ + $$name: 'Person', + $$description: 'write a description about this schema', + firstName: { type: "string" }, + lastName: { type: "string" }, + birthDate: { type: "date" } +}); +```` + # Development ``` npm run dev diff --git a/index.d.ts b/index.d.ts index a05de47..a2db52f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -877,9 +877,9 @@ export type ValidationRule = | ValidationRuleName; /** - * Definition for validation schema based on validation rules + * */ -export type ValidationSchema = { +export interface ValidationSchemaMetaKeys { /** * Object properties which are not specified on the schema are ignored by default. * If you set the $$strict option to true any additional properties will result in an strictObject error. @@ -899,12 +899,18 @@ export type ValidationSchema = { * @default false */ $$root?: boolean; -} & { - /** - * List of validation rules for each defined field - */ - [key in keyof T]: ValidationRule | undefined | any; - }; +} + +/** + * Definition for validation schema based on validation rules + */ +export type ValidationSchema = ValidationSchemaMetaKeys & { + /** + * List of validation rules for each defined field + */ + [key in keyof T]: ValidationRule | undefined | any; +} + /** * Structure with description of validation error message diff --git a/lib/rules/object.js b/lib/rules/object.js index 0b87b0c..263453b 100644 --- a/lib/rules/object.js +++ b/lib/rules/object.js @@ -46,7 +46,7 @@ module.exports = function ({ schema, messages }, path, context) { sourceCode.push("var parentObj = value;"); sourceCode.push("var parentField = field;"); - const keys = Object.keys(subSchema); + const keys = Object.keys(subSchema).filter(key => !this.isMetaKey(key)); for (let i = 0; i < keys.length; i++) { const property = keys[i]; @@ -58,7 +58,7 @@ module.exports = function ({ schema, messages }, path, context) { 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};`); @@ -70,7 +70,7 @@ module.exports = function ({ schema, messages }, path, context) { sourceCode.push(this.compileRule(rule, context, newPath, innerSource, safePropName)); if (this.opts.haltOnFirstError === true) { sourceCode.push("if (errors.length) return parentObj;"); - } + } } // Strict handler diff --git a/lib/validator.js b/lib/validator.js index 034f925..1295326 100644 --- a/lib/validator.js +++ b/lib/validator.js @@ -121,8 +121,8 @@ class Validator { rule.schema.nullable !== false || rule.schema.type === "forbidden" : rule.schema.optional === true || rule.schema.nullable === true || rule.schema.type === "forbidden"; - const ruleHasDefault = considerNullAsAValue ? - rule.schema.default != undefined && rule.schema.default != null : + const ruleHasDefault = considerNullAsAValue ? + rule.schema.default != undefined && rule.schema.default != null : rule.schema.default != undefined; if (ruleHasDefault) { @@ -161,6 +161,30 @@ class Validator { return src.join("\n"); } + /** + * check if the key is a meta key + * + * @param key + * @return {boolean} + */ + isMetaKey(key) { + return key.startsWith("$$"); + } + /** + * will remove all "metas" keys (keys starting with $$) + * + * @param obj + */ + removeMetasKeys(obj) { + Object.keys(obj).forEach(key => { + if(!this.isMetaKey(key)) { + return; + } + + delete obj[key]; + }); + } + /** * Compile a schema * @@ -204,7 +228,7 @@ class Validator { properties: prevSchema }; - delete prevSchema.$$strict; + this.removeMetasKeys(prevSchema); } } diff --git a/test/integration.spec.js b/test/integration.spec.js index d7e310e..f3a4daa 100644 --- a/test/integration.spec.js +++ b/test/integration.spec.js @@ -1,6 +1,7 @@ "use strict"; const Validator = require("../lib/validator"); +const {RuleEmail} = require("../index"); describe("Test flat schema", () => { const v = new Validator(); @@ -1445,3 +1446,105 @@ describe("edge cases", () => { ]); }); }); + +describe("allow metas starting with $$", () => { + const v = new Validator({ useNewCustomCheckerFunction: true }); + describe("test on schema", () => { + it("should not remove keys from source object", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + name: { type: "string" } }; + const clonedSchema = {...schema}; + v.compile(schema); + + expect(schema).toStrictEqual(clonedSchema); + }); + + it("should works with $$root", () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "email", + empty: true + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(check("john.doe@company.net")).toEqual(true); + expect(check("")).toEqual(true); + expect(schema).toStrictEqual(clonedSchema); + }); + + it("should works with $$async", async () => { + const custom1 = jest.fn().mockResolvedValue("NAME"); + const schema = { + $$foo: { + foo: "bar" + }, + $$async: true, + name: { type: "string", custom: custom1 }, + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + //check schema meta was not changed + expect(schema.$$foo).toStrictEqual(clonedSchema.$$foo); + + expect(check.async).toBe(true); + + let obj = { + id: 3, + name: "John", + username: " john.doe ", + age: 30 + }; + + const res = await check(obj); + expect(res).toBe(true); + + expect(custom1).toBeCalledTimes(1); + expect(custom1).toBeCalledWith("John", [], schema.name, "name", null, expect.anything()); + }); + }); + + describe("test on rule", () => { + it("should not remove keys from source object", async () => { + const schema = { + name: { + $$foo: { + foo: "bar" + }, + type: "string" + } + }; + const clonedSchema = {...schema}; + v.compile(schema); + + expect(schema).toStrictEqual(clonedSchema); + }); + it("should works with $$type", async () => { + const schema = { + dot: { + $$foo: { + foo: "bar" + }, + $$type: "object", + x: "number", // object props here + y: "number", // object props here + } + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(schema).toStrictEqual(clonedSchema); + expect(check({ + x: 1, + y: 1, + })).toBeTruthy(); + }); + }); +}); diff --git a/test/rules/any.spec.js b/test/rules/any.spec.js index b8124e6..7536961 100644 --- a/test/rules/any.spec.js +++ b/test/rules/any.spec.js @@ -39,14 +39,39 @@ describe("Test rule: any", () => { expect(check([])).toEqual(true); expect(check({})).toEqual(true); }); + + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "any", + optional: true + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(schema).toStrictEqual(clonedSchema); + + expect(check(null)).toEqual(true); + expect(check(undefined)).toEqual(true); + expect(check(0)).toEqual(true); + expect(check(1)).toEqual(true); + expect(check("")).toEqual(true); + expect(check("true")).toEqual(true); + expect(check("false")).toEqual(true); + expect(check([])).toEqual(true); + expect(check({})).toEqual(true); + }); }); describe("new case (with considerNullAsAValue flag set to true)", () => { const v = new Validator({considerNullAsAValue: true}); - + it("should give back true anyway", () => { const check = v.compile({ $$root: true, type: "any" }); - + expect(check(null)).toEqual(true); expect(check(undefined)).toEqual([{ type: "required", actual: undefined, message: "The '' field is required." }]); expect(check(0)).toEqual(true); @@ -57,10 +82,35 @@ describe("Test rule: any", () => { expect(check([])).toEqual(true); expect(check({})).toEqual(true); }); - + it("should give back true anyway as optional", () => { const check = v.compile({ $$root: true, type: "any", optional: true }); - + + expect(check(null)).toEqual(true); + expect(check(undefined)).toEqual(true); + expect(check(0)).toEqual(true); + expect(check(1)).toEqual(true); + expect(check("")).toEqual(true); + expect(check("true")).toEqual(true); + expect(check("false")).toEqual(true); + expect(check([])).toEqual(true); + expect(check({})).toEqual(true); + }); + + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "any", + optional: true + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(schema).toStrictEqual(clonedSchema); + expect(check(null)).toEqual(true); expect(check(undefined)).toEqual(true); expect(check(0)).toEqual(true); @@ -72,4 +122,4 @@ describe("Test rule: any", () => { expect(check({})).toEqual(true); }); }); -}); \ No newline at end of file +}); diff --git a/test/rules/array.spec.js b/test/rules/array.spec.js index 38c33b9..c0c25b1 100644 --- a/test/rules/array.spec.js +++ b/test/rules/array.spec.js @@ -251,4 +251,34 @@ describe("Test rule: array", () => { }); }); }); + + it("should allow custom metas", async () => { + const itemSchema = { + $$foo: { + foo: "bar" + }, + type: "string", + }; + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "array", + items: itemSchema + }; + const clonedSchema = {...schema}; + const clonedItemSchema = {...itemSchema}; + const check = v.compile(schema); + + expect(schema).toStrictEqual(clonedSchema); + expect(itemSchema).toStrictEqual(clonedItemSchema); + + expect(check([])).toEqual(true); + expect(check(["human"])).toEqual(true); + expect(check(["male", 3, "female", true])).toEqual([ + { type: "string", field: "[1]", actual: 3, message: "The '[1]' field must be a string." }, + { type: "string", field: "[3]", actual: true, message: "The '[3]' field must be a string." } + ]); + }); }); diff --git a/test/rules/boolean.spec.js b/test/rules/boolean.spec.js index adc2431..5a49ff9 100644 --- a/test/rules/boolean.spec.js +++ b/test/rules/boolean.spec.js @@ -67,4 +67,30 @@ describe("Test rule: boolean", () => { expect(obj).toEqual({ status: true }); }); + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "boolean", + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(schema).toStrictEqual(clonedSchema); + + const message = "The '' field must be a boolean."; + + expect(check(0)).toEqual([{ type: "boolean", actual: 0, message }]); + expect(check(1)).toEqual([{ type: "boolean", actual: 1, message }]); + expect(check("")).toEqual([{ type: "boolean", actual: "", message }]); + expect(check("true")).toEqual([{ type: "boolean", actual: "true", message }]); + expect(check("false")).toEqual([{ type: "boolean", actual: "false", message }]); + expect(check([])).toEqual([{ type: "boolean", actual: [], message }]); + expect(check({})).toEqual([{ type: "boolean", actual: {}, message }]); + + expect(check(false)).toEqual(true); + expect(check(true)).toEqual(true); + }); }); diff --git a/test/rules/class.spec.js b/test/rules/class.spec.js index 844e861..913b726 100644 --- a/test/rules/class.spec.js +++ b/test/rules/class.spec.js @@ -22,4 +22,26 @@ describe("Test rule: class", () => { expect(check({ rawData: Buffer.from([1, 2, 3]) })).toEqual(true); // expect(checker).toBeCalledTimes(1); }); + + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "class", + instanceOf: Buffer + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(schema).toStrictEqual(clonedSchema); + + const message = "The '' field must be an instance of the 'Buffer' class."; + + expect(check("1234")).toEqual([{ type: "classInstanceOf", field: undefined, actual: "1234", expected: "Buffer", message }]); + expect(check([1, 2, 3])).toEqual([{ type: "classInstanceOf", field: undefined, actual: [1, 2, 3], expected: "Buffer", message }]); + expect(check(Buffer.from([1, 2, 3]) )).toEqual(true); + expect(check(Buffer.alloc(3) )).toEqual(true); + }); }); diff --git a/test/rules/currency.spec.js b/test/rules/currency.spec.js index c403094..7f63571 100644 --- a/test/rules/currency.spec.js +++ b/test/rules/currency.spec.js @@ -62,4 +62,21 @@ describe("Test rule: currency", () => { expect(check("123")).toEqual(true); expect(check("134")).toEqual([{"actual": "134", "field": undefined, "message": "The '' must be a valid currency format", "type": "currency"}]); }); + + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "currency" + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(schema).toStrictEqual(clonedSchema); + + expect(check("12.2")).toEqual(true); + expect(check("$12.2")).toEqual( [{"actual": "$12.2", "field": undefined, "message": "The '' must be a valid currency format", "type": "currency"}]); + }); }); diff --git a/test/rules/custom.spec.js b/test/rules/custom.spec.js index 772b247..efb90f6 100644 --- a/test/rules/custom.spec.js +++ b/test/rules/custom.spec.js @@ -44,6 +44,28 @@ describe("Test rule: custom v1", () => { expect(checker).toHaveBeenCalledWith(10, schema.weight, "weight", { weight: 10 }, expect.any(Object)); }); + it("should allow custom metas", async () => { + const checker = jest.fn(() => true); + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "custom", + a: 5, + check: checker + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(schema).toStrictEqual(clonedSchema); + + + expect(check(10)).toEqual(true); + expect(checker).toHaveBeenCalledTimes(1); + //checkFunction should receive the unmodified schema + expect(checker).toHaveBeenCalledWith(10, schema, "null", null, expect.any(Object)); + }); }); @@ -108,4 +130,28 @@ describe("Test rule: custom v2", () => { expect(checker).toHaveBeenCalledWith({ name: "John" }, [], schema, "$$root", null, expect.any(Object)); }); + it("should allow custom metas", async () => { + const checker = jest.fn(v => v); + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "object", + properties: { + name: "string" + }, + custom: checker + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(schema).toStrictEqual(clonedSchema); + + expect(check({ name: "John" })).toEqual(true); + expect(checker).toHaveBeenCalledTimes(1); + //checkFunction should receive the unmodified schema + expect(checker).toHaveBeenCalledWith({ name: "John" }, [], schema, "$$root", null, expect.any(Object)); + }); + }); diff --git a/test/rules/date.spec.js b/test/rules/date.spec.js index a0a7a91..46711ae 100644 --- a/test/rules/date.spec.js +++ b/test/rules/date.spec.js @@ -63,4 +63,33 @@ describe("Test rule: date", () => { expect(check(obj)).toEqual(true); expect(obj).toEqual({ timestamp: new Date("Wed Mar 25 2015 09:56:24 GMT+0100 (W. Europe Standard Time)") }); }); + + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "date" + }; + const clonedSchema = {...schema}; + expect(schema).toStrictEqual(clonedSchema); + const check = v.compile(schema); + const message = "The '' field must be a Date."; + + expect(check(0)).toEqual([{ type: "date", actual: 0, message }]); + expect(check(1)).toEqual([{ type: "date", actual: 1, message }]); + expect(check("")).toEqual([{ type: "date", actual: "", message }]); + expect(check("true")).toEqual([{ type: "date", actual: "true", message }]); + expect(check("false")).toEqual([{ type: "date", actual: "false", message }]); + expect(check([])).toEqual([{ type: "date", actual: [], message }]); + expect(check({})).toEqual([{ type: "date", actual: {}, message }]); + + const now = Date.now(); + expect(check(now)).toEqual([{ type: "date", actual: now, message }]); + + expect(check(new Date())).toEqual(true); + expect(check(new Date(1488876927958))).toEqual(true); + + }); }); diff --git a/test/rules/email.spec.js b/test/rules/email.spec.js index 522b076..42516ba 100644 --- a/test/rules/email.spec.js +++ b/test/rules/email.spec.js @@ -91,4 +91,21 @@ describe("Test rule: email", () => { expect(check("veryLongEmailAddress@veryLongProviderName.com")).toEqual([{ type: "emailMax", expected: 20, actual: 45, message: "The '' field length must be less than or equal to 20 characters long." }]); }); + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "email", + empty: true + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(schema).toStrictEqual(clonedSchema); + expect(check("john.doe@company.net")).toEqual(true); + expect(check("")).toEqual(true); + }); + }); diff --git a/test/rules/enum.spec.js b/test/rules/enum.spec.js index 0374610..8459f2a 100644 --- a/test/rules/enum.spec.js +++ b/test/rules/enum.spec.js @@ -23,4 +23,23 @@ describe("Test rule: enum", () => { expect(check(false)).toEqual(true); }); + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "enum", + values: ["male", "female"] + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(clonedSchema).toEqual(schema); + + expect(check("")).toEqual([{ type: "enumValue", expected: "male, female", actual: "", message: "The '' field value 'male, female' does not match any of the allowed values." }]); + expect(check("human")).toEqual([{ type: "enumValue", expected: "male, female", actual: "human", message: "The '' field value 'male, female' does not match any of the allowed values." }]); + expect(check("male")).toEqual(true); + expect(check("female")).toEqual(true); + }); }); diff --git a/test/rules/equal.spec.js b/test/rules/equal.spec.js index 4fe17dd..4a6dd29 100644 --- a/test/rules/equal.spec.js +++ b/test/rules/equal.spec.js @@ -48,4 +48,26 @@ describe("Test rule: equal", () => { expect(check({ accept: 1 })).toEqual([{ type: "equalValue", field: "accept", actual: 1, expected: true, message }]); expect(check({ accept: true })).toEqual(true); }); + + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + confirm: { + type: "equal", + field: "pass" + } + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + const message = "The 'confirm' field value must be equal to 'pass' field value."; + + expect(clonedSchema).toEqual(schema); + + expect(check({ confirm: "abcd"})).toEqual([{ type: "equalField", field: "confirm", actual: "abcd", expected: "pass", message }]); + expect(check({ pass: "1234", confirm: "abcd"})).toEqual([{ type: "equalField", field: "confirm", actual: "abcd", expected: "pass", message }]); + expect(check({ pass: "1234", confirm: 1234 })).toEqual(true); + expect(check({ pass: "1234", confirm: "1234" })).toEqual(true); + }); }); diff --git a/test/rules/forbidden.spec.js b/test/rules/forbidden.spec.js index 12b170d..6adaa3a 100644 --- a/test/rules/forbidden.spec.js +++ b/test/rules/forbidden.spec.js @@ -45,4 +45,30 @@ describe("Test rule: forbidden", () => { }); + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "forbidden" + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(clonedSchema).toEqual(schema); + + const message = "The '' field is forbidden."; + expect(check(null)).toEqual(true); + expect(check(undefined)).toEqual(true); + expect(check(0)).toEqual([{ type: "forbidden", actual: 0, message }]); + expect(check(1)).toEqual([{ type: "forbidden", actual: 1, message }]); + expect(check("")).toEqual([{ type: "forbidden", actual: "", message }]); + expect(check("null")).toEqual([{ type: "forbidden", actual: "null", message }]); + expect(check([])).toEqual([{ type: "forbidden", actual: [], message }]); + expect(check({})).toEqual([{ type: "forbidden", actual: {}, message }]); + expect(check(false)).toEqual([{ type: "forbidden", actual: false, message }]); + expect(check(true)).toEqual([{ type: "forbidden", actual: true, message }]); + }); + }); diff --git a/test/rules/function.spec.js b/test/rules/function.spec.js index 83e4e18..ef84564 100644 --- a/test/rules/function.spec.js +++ b/test/rules/function.spec.js @@ -22,4 +22,33 @@ describe("Test rule: function", () => { expect(check(() => {})).toEqual(true); expect(check(new Function())).toEqual(true); }); + + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "function" + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(clonedSchema).toEqual(schema); + + const message = "The '' field must be a function."; + + expect(check(0)).toEqual([{ type: "function", actual: 0, message }]); + expect(check(1)).toEqual([{ type: "function", actual: 1, message }]); + expect(check("")).toEqual([{ type: "function", actual: "", message }]); + expect(check("true")).toEqual([{ type: "function", actual: "true", message }]); + expect(check([])).toEqual([{ type: "function", actual: [], message }]); + expect(check({})).toEqual([{ type: "function", actual: {}, message }]); + expect(check(false)).toEqual([{ type: "function", actual: false, message }]); + expect(check(true)).toEqual([{ type: "function", actual: true, message }]); + + expect(check(function() {})).toEqual(true); + expect(check(() => {})).toEqual(true); + expect(check(new Function())).toEqual(true); + }); }); diff --git a/test/rules/luhn.spec.js b/test/rules/luhn.spec.js index 2f65da4..129d1c0 100644 --- a/test/rules/luhn.spec.js +++ b/test/rules/luhn.spec.js @@ -25,4 +25,34 @@ describe("Test rule: luhn", () => { expect(check("4523-739-8990-1198")).toEqual(true); }); + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "luhn" + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(clonedSchema).toEqual(schema); + + let message = "The '' field must be a string."; + + expect(check(0)).toEqual([{ type: "string", actual: 0, message }]); + expect(check(1)).toEqual([{ type: "string", actual: 1, message }]); + expect(check([])).toEqual([{ type: "string", actual: [], message }]); + expect(check({})).toEqual([{ type: "string", actual: {}, message }]); + expect(check(false)).toEqual([{ type: "string", actual: false, message }]); + expect(check(true)).toEqual([{ type: "string", actual: true, message }]); + + message = "The '' field must be a valid checksum luhn."; + expect(check("")).toEqual([{ type: "luhn", actual: "", message }]); + expect(check("true")).toEqual([{ type: "luhn", actual: "true", message }]); + expect(check("452373989911198")).toEqual([{ type: "luhn", actual: "452373989911198", message }]); + expect(check("452373989901199")).toEqual([{ type: "luhn", actual: "452373989901199", message }]); + expect(check("452373989901198")).toEqual(true); + expect(check("4523-739-8990-1198")).toEqual(true); + }); }); diff --git a/test/rules/mac.spec.js b/test/rules/mac.spec.js index 583496f..4f2d9b2 100644 --- a/test/rules/mac.spec.js +++ b/test/rules/mac.spec.js @@ -37,4 +37,46 @@ describe("Test rule: mac", () => { expect(check("01-c8-95-4b-65-fe")).toEqual(true); }); + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "mac" + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(clonedSchema).toEqual(schema); + + let message = "The '' field must be a string."; + + expect(check(0)).toEqual([{ type: "string", actual: 0, message }]); + expect(check(1)).toEqual([{ type: "string", actual: 1, message }]); + expect(check([])).toEqual([{ type: "string", actual: [], message }]); + expect(check({})).toEqual([{ type: "string", actual: {}, message }]); + expect(check(false)).toEqual([{ type: "string", actual: false, message }]); + expect(check(true)).toEqual([{ type: "string", actual: true, message }]); + + message = "The '' field must be a valid MAC address."; + expect(check("")).toEqual([{ type: "mac", actual: "", message }]); + expect(check("true")).toEqual([{ type: "mac", actual: "true", message }]); + expect(check("018.954B.65FE")).toEqual([{ type: "mac", actual: "018.954B.65FE", message }]); + expect(check("01C8.95B.65FE")).toEqual([{ type: "mac", actual: "01C8.95B.65FE", message }]); + expect(check("01C8.954B.6FE")).toEqual([{ type: "mac", actual: "01C8.954B.6FE", message }]); + expect(check("1-C8-95-4B-65-FE")).toEqual([{ type: "mac", actual: "1-C8-95-4B-65-FE", message }]); + expect(check("01-C8-95-4B-65-F")).toEqual([{ type: "mac", actual: "01-C8-95-4B-65-F", message }]); + expect(check("01-C8-95-4B-65-FE-A0")).toEqual([{ type: "mac", actual: "01-C8-95-4B-65-FE-A0", message }]); + expect(check("1:C8:95:4B:65:FE")).toEqual([{ type: "mac", actual: "1:C8:95:4B:65:FE", message }]); + expect(check("01:8:95:4B:65:FE")).toEqual([{ type: "mac", actual: "01:8:95:4B:65:FE", message }]); + expect(check("01:C8:95:4B:65:F")).toEqual([{ type: "mac", actual: "01:C8:95:4B:65:F", message }]); + expect(check("01:C8:95:4B:65:FE:AF")).toEqual([{ type: "mac", actual: "01:C8:95:4B:65:FE:AF", message }]); + expect(check("01:c8:95:4b:65:fe")).toEqual(true); + expect(check("01:C8:95:4B:65:FE")).toEqual(true); + expect(check("01c8.954b.65fe")).toEqual(true); + expect(check("01C8.954B.65FE")).toEqual(true); + expect(check("01-C8-95-4B-65-FE")).toEqual(true); + expect(check("01-c8-95-4b-65-fe")).toEqual(true); + }); }); diff --git a/test/rules/multi.spec.js b/test/rules/multi.spec.js index 7c32f92..0d40d70 100644 --- a/test/rules/multi.spec.js +++ b/test/rules/multi.spec.js @@ -185,4 +185,34 @@ describe("Test rule: multi", () => { } }); }); + + + it("should allow custom metas", async () => { + const fn = jest.fn((v) => v); + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "multi", + rules: [ + { + type: "string", + custom: fn + }, + { + type: "number", + custom: fn + } + ] + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(clonedSchema).toEqual(schema); + + check("s"); + expect(fn).toBeCalledTimes(1); + expect(fn).toBeCalledWith("s", [], schema.rules[0], "$$root", null, expect.any(Object)); + }); }); diff --git a/test/rules/number.spec.js b/test/rules/number.spec.js index 8cb9c12..c46a388 100644 --- a/test/rules/number.spec.js +++ b/test/rules/number.spec.js @@ -155,4 +155,35 @@ describe("Test rule: number", () => { expect(obj).toEqual({ age: -45 }); }); + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "number" + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(clonedSchema).toEqual(schema); + + const message = "The '' field must be a number."; + + expect(check("")).toEqual([{ type: "number", actual: "", message }]); + expect(check("test")).toEqual([{ type: "number", actual: "test", message }]); + expect(check("1")).toEqual([{ type: "number", actual: "1", message }]); + expect(check([])).toEqual([{ type: "number", actual: [], message }]); + expect(check({})).toEqual([{ type: "number", actual: {}, message }]); + expect(check(false)).toEqual([{ type: "number", actual: false, message }]); + expect(check(true)).toEqual([{ type: "number", actual: true, message }]); + expect(check(NaN)).toEqual([{ type: "number", actual: NaN, message }]); + expect(check(Number.POSITIVE_INFINITY)).toEqual([{ type: "number", actual: Number.POSITIVE_INFINITY, message }]); + expect(check(Number.NEGATIVE_INFINITY)).toEqual([{ type: "number", actual: Number.NEGATIVE_INFINITY, message }]); + + expect(check(0)).toEqual(true); + expect(check(5)).toEqual(true); + expect(check(-24)).toEqual(true); + expect(check(5.45)).toEqual(true); + }); }); diff --git a/test/rules/object.spec.js b/test/rules/object.spec.js index a6fde96..90f9f9a 100644 --- a/test/rules/object.spec.js +++ b/test/rules/object.spec.js @@ -167,4 +167,29 @@ describe("Test rule: object", () => { }); }); + + + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "object" + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(clonedSchema).toEqual(schema); + const message = "The '' must be an Object."; + + expect(check(0)).toEqual([{ type: "object", actual: 0, message }]); + expect(check(1)).toEqual([{ type: "object", actual: 1, message }]); + expect(check("")).toEqual([{ type: "object", actual: "", message }]); + expect(check(false)).toEqual([{ type: "object", actual: false, message }]); + expect(check(true)).toEqual([{ type: "object", actual: true, message }]); + expect(check([])).toEqual([{ type: "object", actual: [], message }]); + expect(check({})).toEqual(true); + expect(check({ a: "John" })).toEqual(true); + }); }); diff --git a/test/rules/objectID.spec.js b/test/rules/objectID.spec.js index b4736e4..4d5542d 100644 --- a/test/rules/objectID.spec.js +++ b/test/rules/objectID.spec.js @@ -2,16 +2,16 @@ const Validator = require("../../lib/validator"); const v = new Validator(); -const { ObjectID } = require("mongodb"); +const { ObjectId } = require("mongodb"); describe("Test rule: objectID", () => { it("should validate ObjectID", () => { - const check = v.compile({ id: { type: "objectID", ObjectID } }); + const check = v.compile({ id: { type: "objectID", 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); + expect(check({ id: new ObjectId() })).toEqual(true); const o = { id: "5f082780b00cc7401fb8e8fc" }; expect(check(o)).toEqual(true); @@ -19,18 +19,18 @@ describe("Test rule: objectID", () => { }); it("should convert hexString-objectID to ObjectID", () => { - const check = v.compile({ id: { type: "objectID", ObjectID, convert: true } }); - const oid = new ObjectID(); + const check = v.compile({ id: { type: "objectID", 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).toBeInstanceOf(ObjectId); expect(o.id).toEqual(oid); }); it("should convert hexString-objectID to hexString", () => { - const check = v.compile({ id: { type: "objectID", ObjectID, convert: "hexString" } }); - const oid = new ObjectID(); + const check = v.compile({ id: { type: "objectID", ObjectID: ObjectId, convert: "hexString" } }); + const oid = new ObjectId(); const oidStr = oid.toHexString(); const o = { id: oid }; @@ -41,10 +41,34 @@ describe("Test rule: objectID", () => { it("should catch hexString problems when convert: true", () => { const message = "The 'id' field must be an valid ObjectID"; - const check = v.compile({ id: { type: "objectID", ObjectID, convert: true } }); + const check = v.compile({ id: { type: "objectID", ObjectID: ObjectId, convert: true } }); const badID = "5f082780b00cc7401fb8"; const o = { id: badID }; expect(check(o)).toEqual([{ type: "objectID", field: "id", actual: badID, message }]); }); + + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + id: { + type: "objectID", + ObjectID: ObjectId + } + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(clonedSchema).toEqual(schema); + 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"); + }); }); diff --git a/test/rules/record.spec.js b/test/rules/record.spec.js index 259bb1d..c2701d8 100644 --- a/test/rules/record.spec.js +++ b/test/rules/record.spec.js @@ -107,4 +107,29 @@ describe("Test rule: record", () => { expect(check({ John: value })).toEqual(true); }); + + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "record" + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(clonedSchema).toEqual(schema); + + const message = "The '' must be an Object."; + + expect(check(0)).toEqual([{ type: "record", actual: 0, message }]); + expect(check(1)).toEqual([{ type: "record", actual: 1, message }]); + expect(check("")).toEqual([{ type: "record", actual: "", message }]); + expect(check(false)).toEqual([{ type: "record", actual: false, message }]); + expect(check(true)).toEqual([{ type: "record", actual: true, message }]); + expect(check([])).toEqual([{ type: "record", actual: [], message }]); + expect(check({})).toEqual(true); + expect(check({ a: "John" })).toEqual(true); + }); }); diff --git a/test/rules/string.spec.js b/test/rules/string.spec.js index ee3fc65..4b17e00 100644 --- a/test/rules/string.spec.js +++ b/test/rules/string.spec.js @@ -350,4 +350,29 @@ describe("Test rule: string", () => { }); }); + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "string" + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(clonedSchema).toEqual(schema); + + const message = "The '' field must be a string."; + + expect(check(0)).toEqual([{ type: "string", actual: 0, message }]); + expect(check(1)).toEqual([{ type: "string", actual: 1, message }]); + expect(check([])).toEqual([{ type: "string", actual: [], message }]); + expect(check({})).toEqual([{ type: "string", actual: {}, message }]); + expect(check(false)).toEqual([{ type: "string", actual: false, message }]); + expect(check(true)).toEqual([{ type: "string", actual: true, message }]); + + expect(check("")).toEqual(true); + expect(check("test")).toEqual(true); + }); }); diff --git a/test/rules/tuple.spec.js b/test/rules/tuple.spec.js index f1a99f8..fab1ce7 100644 --- a/test/rules/tuple.spec.js +++ b/test/rules/tuple.spec.js @@ -242,4 +242,34 @@ describe("Test rule: tuple", () => { expect(o.a).toEqual([2, 4]); }); }); + + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "tuple" + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(clonedSchema).toEqual(schema); + + const message = "The '' field must be an array."; + + expect(check(0)).toEqual([{ type: "tuple", actual: 0, message }]); + expect(check(1)).toEqual([{ type: "tuple", actual: 1, message }]); + expect(check({})).toEqual([{ type: "tuple", actual: {}, message }]); + expect(check(false)).toEqual([ + { type: "tuple", actual: false, message } + ]); + expect(check(true)).toEqual([{ type: "tuple", actual: true, message }]); + expect(check("")).toEqual([{ type: "tuple", actual: "", message }]); + expect(check("test")).toEqual([ + { type: "tuple", actual: "test", message } + ]); + + expect(check([])).toEqual(true); + }); }); diff --git a/test/rules/url.spec.js b/test/rules/url.spec.js index 51648e8..c30abb0 100644 --- a/test/rules/url.spec.js +++ b/test/rules/url.spec.js @@ -38,8 +38,45 @@ describe("Test rule: url", () => { expect(check("http://github.com/icebob/fastest-validator")).toEqual(true); expect(check("http://clipboard.space")).toEqual(true); expect(check("https://localhost:3000/?id=5&name=Test#result")).toEqual(true); + }); + + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "url" + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + expect(clonedSchema).toEqual(schema); + + let message = "The '' field must be a string."; + expect(check(0)).toEqual([{ type: "string", actual: 0, message }]); + expect(check(1)).toEqual([{ type: "string", actual: 1, message }]); + expect(check([])).toEqual([{ type: "string", actual: [], message }]); + expect(check({})).toEqual([{ type: "string", actual: {}, message }]); + expect(check(false)).toEqual([{ type: "string", actual: false, message }]); + expect(check(true)).toEqual([{ type: "string", actual: true, message }]); + + message = "The '' field must be a valid URL."; + expect(check("")).toEqual([{ type: "urlEmpty", actual: "", message: "The '' field must not be empty." }]); + expect(check("true")).toEqual([{ type: "url", actual: "true", message }]); + expect(check("abcdefg")).toEqual([{ type: "url", actual: "abcdefg", message }]); + expect(check("1234.c")).toEqual([{ type: "url", actual: "1234.c", message }]); + expect(check("gmail.company1234")).toEqual([{ type: "url", actual: "gmail.company1234", message }]); + expect(check("@gmail.com")).toEqual([{ type: "url", actual: "@gmail.com", message }]); + expect(check("https://")).toEqual([{ type: "url", actual: "https://", message }]); + expect(check("http://www.google.com")).toEqual(true); + expect(check("https://google.com")).toEqual(true); + expect(check("http://nasa.gov")).toEqual(true); + expect(check("https://github.com")).toEqual(true); + expect(check("http://github.com/icebob/fastest-validator")).toEqual(true); + expect(check("http://clipboard.space")).toEqual(true); + expect(check("https://localhost:3000/?id=5&name=Test#result")).toEqual(true); }); }); diff --git a/test/rules/uuid.spec.js b/test/rules/uuid.spec.js index c6ae616..dfd094a 100644 --- a/test/rules/uuid.spec.js +++ b/test/rules/uuid.spec.js @@ -89,4 +89,34 @@ describe("Test rule: uuid", () => { expect(check5("FDDA765F-FC57-5604-A269-52A7DF8164EC")).toEqual(true); expect(check6("A9030619-8514-6970-E0F9-81B9CEB08A5F")).toEqual(true); }); + + it("should allow custom metas", async () => { + const schema = { + $$foo: { + foo: "bar" + }, + $$root: true, + type: "uuid" + }; + const clonedSchema = {...schema}; + const check = v.compile(schema); + + expect(clonedSchema).toEqual(schema); + + let message = "The '' field must be a string."; + + expect(check(0)).toEqual([{ type: "string", actual: 0, message }]); + expect(check(1)).toEqual([{ type: "string", actual: 1, message }]); + expect(check([])).toEqual([{ type: "string", actual: [], message }]); + expect(check({})).toEqual([{ type: "string", actual: {}, message }]); + expect(check(false)).toEqual([{ type: "string", actual: false, message }]); + expect(check(true)).toEqual([{ type: "string", actual: true, message }]); + + message = "The '' field must be a valid UUID."; + expect(check("")).toEqual([{ type: "uuid", actual: "", message }]); + expect(check("true")).toEqual([{ type: "uuid", actual: "true", message }]); + expect(check("10000000-0000-0000-0000-000000000000")).toEqual([{ type: "uuid", actual: "10000000-0000-0000-0000-000000000000", message }]); + expect(check("1234567-1234-1234-1234-1234567890ab")).toEqual([{ type: "uuid", actual: "1234567-1234-1234-1234-1234567890ab", message }]); + expect(check("12345678-1234-1234-1234-1234567890ab")).toEqual(true); + }); });