diff --git a/README.md b/README.md index be9ab80..96935f7 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,14 @@ check({ foo: ["bar"] }) // true ### Nested objects +```js +const schema = { + code: /^[0-9]{6}$/, + phone: /^\+9\d{11}$/, +}; +``` + +### Regex ```js const schema = { dot: { @@ -294,6 +302,7 @@ const schema = { }; ``` + # Alias definition You can define custom aliases. diff --git a/index.d.ts b/index.d.ts index 46c964f..8a10e4a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -785,7 +785,8 @@ declare module "fastest-validator" { export type ValidationRule = | ValidationRuleObject | ValidationRuleObject[] - | ValidationRuleName; + | ValidationRuleName + | RegExp; /** * Definition for validation schema based on validation rules 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..35f7deb 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; }); @@ -315,6 +318,11 @@ class Validator { .every(rule => rule.schema.optional == true); if (isOptional) schema.optional = true; + } else if (schema instanceof RegExp) { + schema = { + type: "string", + pattern: schema + }; } if (schema.$$type) { 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..2f6520e 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); @@ -251,6 +251,12 @@ describe("Test getRuleFromSchema method", () => { expect(res2.schema).toEqual({ type: "array", optional: true, items: "string", min: 1 }); }); + it("should convert RegExp", () => { + const regex = /(foo)/; + const res = v.getRuleFromSchema(regex); + expect(res.schema).toEqual({ type: "string", pattern: regex }); + }); + }); describe("Test objects shorthand rule ($$type)", () => {