diff --git a/.eslintrc.js b/.eslintrc.js index c152a23..66e6ce8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,38 +1,39 @@ module.exports = { - root: true, - "env": { - "node": true, - "commonjs": true, - "es6": true, - "jquery": false, - "jest": true, - "jasmine": true - }, - "extends": "eslint:recommended", - "parserOptions": { - "sourceType": "module" - }, - "rules": { - "indent": [ - "warn", - "tab" - ], - "quotes": [ - "warn", - "double" - ], - "semi": [ - "error", - "always" - ], - "no-var": [ - "error" - ], - "no-console": [ - "error" - ], - "no-unused-vars": [ - "warn" - ] - } -}; \ No newline at end of file + root: true, + "env": { + "node": true, + "commonjs": true, + "es6": true, + "jquery": false, + "jest": true, + "jasmine": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "sourceType": "module", + "ecmaVersion": "2018" + }, + "rules": { + "indent": [ + "warn", + "tab" + ], + "quotes": [ + "warn", + "double" + ], + "semi": [ + "error", + "always" + ], + "no-var": [ + "error" + ], + "no-console": [ + "error" + ], + "no-unused-vars": [ + "warn" + ] + } +}; diff --git a/.github/workflows/deno.yml b/.github/workflows/deno.yml new file mode 100644 index 0000000..ac48d10 --- /dev/null +++ b/.github/workflows/deno.yml @@ -0,0 +1,21 @@ +name: Deno + +on: [push, pull_request] + +jobs: + test: + name: ${{ matrix.os }} + runs-on: ${{ matrix.os }} + timeout-minutes: 5 + strategy: + matrix: + os: [ubuntu-latest] + steps: + - uses: actions/checkout@master + - uses: denolib/setup-deno@master + with: + deno-version: 1.x.x + + - run: deno -V + + - run: deno test ./deno-test/index.test.ts diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 0000000..b3dea66 --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,29 @@ +name: Node CI + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [12.x, 14.x, 16.x, 18.x, 20.x, 22.x] + + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - uses: actions/cache@v4 + with: + path: node_modules + key: ${{ matrix.node-version }}-node-${{ hashFiles('**/package-lock.json') }} + - name: npm install, build, and test + run: | + npm ci + npm test + npm run build + env: + CI: true diff --git a/.gitignore b/.gitignore index 94659bb..2e53a00 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,9 @@ build/Release # Dependency directories node_modules -jspm_packages + +# IDE directories +.idea # Optional npm cache directory .npm diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 30742bb..0000000 --- a/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -language: node_js -cache: - directories: - - node_modules -node_js: - - "10" - - "8" -after_success: - - npm run coverall diff --git a/.vscode/launch.json b/.vscode/launch.json index 52f28dd..ec68aff 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,7 @@ "type": "node", "request": "launch", "name": "Launch dev", - "program": "${workspaceRoot}\\examples\\custom.js" + "program": "${workspaceRoot}\\examples\\issue-303.js" }, { "type": "node", @@ -26,6 +26,18 @@ "cwd": "${workspaceRoot}", "runtimeArgs": [ "--nolazy" + ] + }, + { + "type": "node", + "request": "launch", + "name": "Jest single", + "program": "${workspaceRoot}/node_modules/jest-cli/bin/jest.js", + "args": ["--runInBand", "${fileBasenameNoExtension}"], + "console": "internalConsole", + "cwd": "${workspaceRoot}", + "runtimeArgs": [ + "--nolazy" ] }, { @@ -36,4 +48,4 @@ "port": 5858 } ] -} \ No newline at end of file +} diff --git a/CHANGELOG.md b/CHANGELOG.md index be02b63..f90514f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,581 @@ + + +# 1.19.1 (2025-04-30) + +## Changes +- fix false negative check on UUID v7 and v8 [#351](https://github.com/icebob/fastest-validator/pull/351) + + + +# 1.19.0 (2024-07-28) + +## Changes +- shorthand label hotfix. [#345](https://github.com/icebob/fastest-validator/pull/345) +- fix deprecated substr. [#346](https://github.com/icebob/fastest-validator/pull/346) +- improved custom function with array and global custom functions. [More info](https://github.com/icebob/fastest-validator?tab=readme-ov-file#chaining-custom-functions-and-global-definitions) [#332](https://github.com/icebob/fastest-validator/pull/332) + + + +# 1.18.0 (2024-04-21) + +## Changes +- improve typing. [#339](https://github.com/icebob/fastest-validator/pull/339) +- allow to add metas in the schema. [#341](https://github.com/icebob/fastest-validator/pull/341) + + + +# 1.17.0 (2023-04-23) + +## Changes +- add `considerNullAsAValue` to Validation constructor options. [#317](https://github.com/icebob/fastest-validator/pull/317) +- add type definition for haltOnFirstError on Validator constructor. [#322](https://github.com/icebob/fastest-validator/pull/322) + + + +# 1.16.0 (2022-12-17) + +## Changes +- add `convert` to array rule. [#314](https://github.com/icebob/fastest-validator/pull/314) + + + +# 1.15.0 (2022-08-30) + +## Changes +- fixed bug in record rule. [#307](https://github.com/icebob/fastest-validator/issues/307) +- add label support for error messages. [More info](https://github.com/icebob/fastest-validator#label-option) [#306](https://github.com/icebob/fastest-validator/pull/306) + + + +# 1.14.0 (2022-08-27) + +## Changes +- fix: Multi-schema nullable validators not working as expected. [#303](https://github.com/icebob/fastest-validator/issues/303) +- add new `haltOnFirstError` option (https://github.com/icebob/fastest-validator#halting). [#304](https://github.com/icebob/fastest-validator/pull/304) + + + +# 1.13.0 (2022-08-15) + +## Changes +- update dev dependencies. +- update d.ts +- fixing string enum check in case of optional field. [#284](https://github.com/icebob/fastest-validator/pull/284) +- date rule add convert string to number for timestamp. [#286](https://github.com/icebob/fastest-validator/pull/286) +- fix(multi): item rule has custom checker will throw error if validate. [#290](https://github.com/icebob/fastest-validator/pull/290) +- fix backward compatibility issue. [#298](https://github.com/icebob/fastest-validator/pull/298) +- add [new `Record` rule](https://github.com/icebob/fastest-validator#record). [#300](https://github.com/icebob/fastest-validator/pull/300) + + + +# 1.12.0 (2021-10-17) + +## Changes +- update dev dependencies. +- add parameters to dynamic default value function. E.g: `age: (schema, field, parent, context) => { ... }` +- fix typescript definitions. [#269](https://github.com/icebob/fastest-validator/pull/269), [#270](https://github.com/icebob/fastest-validator/pull/270), [#261](https://github.com/icebob/fastest-validator/pull/261) +- fix multi validate with object strict remove. [#272](https://github.com/icebob/fastest-validator/pull/272) +- add `normalize` method. [#275](https://github.com/icebob/fastest-validator/pull/275) E.g.: `validator.normalize({ a: "string[]|optional" })` + + +# 1.11.1 (2021-07-14) + +## Changes +- fix debug mode. [#237](https://github.com/icebob/fastest-validator/pull/237) +- fix object "toString" issue. [#235](https://github.com/icebob/fastest-validator/pull/235) +- remove Node 10 from CI pipeline. +- refactoring the typescript definitions. [#251](https://github.com/icebob/fastest-validator/pull/251) +- update examples in readme. [#255](https://github.com/icebob/fastest-validator/pull/255) + +-------------------------------------------------- + +# 1.11.0 (2021-05-11) + +## Async custom validator supports + +```js +const schema = { + // Turn on async mode for this schema + $$async: true, + name: { + type: "string", + min: 4, + max: 25, + custom: async (v) => { + await new Promise(resolve => setTimeout(resolve, 1000)); + return v.toUpperCase(); + } + }, + + username: { + type: "custom", + custom: async (v) => { + // E.g. checking in the DB that whether is unique. + await new Promise(resolve => setTimeout(resolve, 1000)); + return v.trim(); + } + }, +} +``` + +The compiled `check` function has an `async` property to detect this mode. If `true` it returns a `Promise`. +```js +const check = v.compile(schema); +console.log("Is async?", check.async); +``` + +## Meta-information for custom validators +You can pass any extra meta information for the custom validators which is available via `context.meta`. +```js +const schema = { + name: { type: "string", custom: (value, errors, schema, name, parent, context) => { + // Access to the meta + return context.meta.a; + } }, +}; +const check = v.compile(schema); + +const res = check(obj, { + // Passes meta information + meta: { a: "from-meta" } +}); +``` + +## Changes +- support default and optional in tuples and arrays [#226](https://github.com/icebob/fastest-validator/pull/226) +- fix that `this` points to the Validator instance in custom functions [#231](https://github.com/icebob/fastest-validator/pull/231) +- +-------------------------------------------------- + +# 1.10.1 (2021-03-22) + +## Changes +- fix issue with regex `pattern` in `string` rule [#221](https://github.com/icebob/fastest-validator/pull/221) +- fix returned value issue in `email` rule in case of `empty: false` [#224](https://github.com/icebob/fastest-validator/pull/224) + +-------------------------------------------------- + +# 1.10.0 (2021-02-03) + +## Changes +- fix issue in multiple custom validator [#203](https://github.com/icebob/fastest-validator/pull/203) +- Add `min`, `max` property to `email` rule [#213](https://github.com/icebob/fastest-validator/pull/213) +- Add `base64` property to `string` rule [#214](https://github.com/icebob/fastest-validator/pull/214) + +-------------------------------------------------- + +# 1.9.0 (2020-11-16) + +## Changes +- `uuid` rule supports version 0 in [#201](https://github.com/icebob/fastest-validator/pull/201) by [@intech](https://github.com/intech) + +-------------------------------------------------- + +# 1.8.0 (2020-10-18) + +## New `nullable` rule attribute in [#185](https://github.com/icebob/fastest-validator/pull/185) + +```js +const schema = { + age: { type: "number", nullable: true } +} +v.validate({ age: 42 }, schema); // Valid +v.validate({ age: null }, schema); // Valid +v.validate({ age: undefined }, schema); // Fail because undefined is disallowed +v.validate({}, schema); // Fail because undefined is disallowed +``` + +## Changes +- Shorthand for array `foo: "string[]" // means array of string` in [#190](https://github.com/icebob/fastest-validator/pull/190) +- allow converting `objectID` to `string` in in [#196](https://github.com/icebob/fastest-validator/pull/196) + +-------------------------------------------------- + +# 1.7.0 (2020-08-30) + +## Changes +- New `currency` rule by [@ishan-srivastava](https://github.com/ishan-srivastava) in [#178](https://github.com/icebob/fastest-validator/pull/178) +- fix issue in `any` rule [#185](https://github.com/icebob/fastest-validator/pull/185) +- update dev deps + +-------------------------------------------------- + +# 1.6.1 (2020-08-11) + +## Changes +- Fix issue with `ObjectID` rule + +-------------------------------------------------- + +# 1.6.0 (2020-08-06) + +## New `objectID` rule +You can validate BSON/MongoDB ObjectID's + +**Example** +```js +const { ObjectID } = require("mongodb") // or anywhere else +const schema = { + id: { + type: "objectID", + ObjectID // passing the ObjectID class + } +} +const check = v.compile(schema); +check({ id: "5f082780b00cc7401fb8e8fc" }) // ok +check({ id: new ObjectID() }) // ok +check({ id: "5f082780b00cc7401fb8e8" }) // Error +``` + +## Dynamic default value +You can use dynamic default value by defining a function that returns a value. + +**Example** +In the following code, if `createdAt` field not defined in object`, the validator sets the current time into the property: +```js +const schema = { + createdAt: { + type: "date", + default: () => new Date() + } +}; +const obj = {} +v.validate(obj, schema); // Valid +console.log(obj); +/* +{ + createdAt: Date(2020-07-25T13:17:41.052Z) +} +*/ +``` + +## Changes +- Add support for uuid v6. [#181](https://github.com/icebob/fastest-validator/issues/181) +- Add `addMessage` method for using in plugins [#166](https://github.com/icebob/fastest-validator/issues/166) +- Fix uppercase uuid issue. [#176](https://github.com/icebob/fastest-validator/issues/176) +- Add `singleLine` property to `string` rule. [#180](https://github.com/icebob/fastest-validator/issues/180) + +## Credits +Many thanks to @intech and @erfanium for contributing. + +-------------------------------------------------- + +# 1.5.1 (2020-06-19) + +## Changes +- Fixing issue with pattern & empty handling in `string` rule [#165](https://github.com/icebob/fastest-validator/issues/165) + +-------------------------------------------------- + +# 1.5.0 (2020-06-18) + +## New `tuple` validation rule +Thanks for [@Gamote](https://github.com/Gamote), in this version there is a new `tuple`. This rule checks if a value is an `Array` with the elements order as described by the schema. + +**Example** +```js +const schema = { + grade: { type: "tuple", items: ["string", "number", "string"] } +}; +``` + +```js +const schema = { + location: { type: "tuple", empty: false, items: [ + { type: "number", min: 35, max: 45 }, + { type: "number", min: -75, max: -65 } + ] } +} +``` + +## Define aliases & custom rules in constructor options [#162](https://github.com/icebob/fastest-validator/issues/162) +You can define aliases & custom rules in constructor options instead of using `v.alias` and `v.add`. + +**Example** + +```js +const v = new Validator({ + aliases: { + username: { + type: 'string', + min: 4, + max: 30 + } + }, + customRules: { + even: function({ schema, messages }, path, context) { + return { + source: ` + if (value % 2 != 0) + ${this.makeError({ type: "evenNumber", actual: "value", messages })} + + return value; + ` + }; + }) + } +}); +``` + +## Support plugins +Thanks for [@erfanium](https://github.com/erfanium), you can create plugin for `fastest-validator`. + +**Example** +```js +// Plugin Side +function myPlugin(validator){ + // you can modify validator here + // e.g.: validator.add(...) + // or : validator.alias(...) +} +// Validator Side +const v = new Validator(); +v.plugin(myPlugin) +``` + +## Changes +- Allow `empty` property in `string` rule with pattern [#149](https://github.com/icebob/fastest-validator/issues/149) +- Add `empty` property to `url` and `email` rule [#150](https://github.com/icebob/fastest-validator/issues/150) +- Fix custom rule issue when multiple rules [#155](https://github.com/icebob/fastest-validator/issues/155) +- Update type definition [#156](https://github.com/icebob/fastest-validator/issues/156) + + -------------------------------------------------- + +# 1.4.2 (2020-06-03) + +## Changes +- added Deno example to readme. +- added `minProps` and `maxProps` by [@alexjab](https://github.com/alexjab) [#142](https://github.com/icebob/fastest-validator/issues/142) +- shorthand for nested objectsby [@erfanium](https://github.com/erfanium) [#143](https://github.com/icebob/fastest-validator/issues/143) +- typescript generics for `compile` method by [@Gamote](https://github.com/Gamote) [#146](https://github.com/icebob/fastest-validator/issues/146) + +-------------------------------------------------- + +# 1.4.1 (2020-05-13) + +## Changes +- Fix `custom` function issue in `array` rule and in root-level [#136](https://github.com/icebob/fastest-validator/issues/136), [#137](https://github.com/icebob/fastest-validator/issues/137) + +-------------------------------------------------- + +# 1.4.0 (2020-05-08) + +## New `custom` function signature +Thanks for [@erfanium](https://github.com/erfanium), in this version there is a new signature of custom check functions. +In this new function you should always return the value. It means you can change the value, thus you can also sanitize the input value. + +**Old custom function:** +```js +const v = new Validator({}); + +const schema = { + weight: { + type: "custom", + minWeight: 10, + check(value, schema) { + return (value < schema.minWeight) + ? [{ type: "weightMin", expected: schema.minWeight, actual: value }] + : true; + } + } +}; +``` + +**New custom function:** +```js +const v = new Validator({ + useNewCustomCheckerFunction: true, // using new version +}); + +const schema = { + name: { type: "string", min: 3, max: 255 }, + weight: { + type: "custom", + minWeight: 10, + check(value, errors, schema) { + if (value < minWeight) errors.push({ type: "weightMin", expected: schema.minWeight, actual: value }); + if (value > 100) value = 100 + return value + } + } +}; +``` + +>Please note: the old version will be removed in the version 2.0.0! + +The signature is used in `custom` function of built-in rules. + +```js +const v = new Validator({ + useNewCustomCheckerFunction: true // using new version +}); + +const schema = { + phone: { type: "string", length: 15, custom(v, errors) => { + if (!v.startWith("+")) errors.push({ type: "phoneNumber" }) + return v.replace(/[^\d+]/g, ""); // Sanitize: remove all special chars except numbers + } } +}; +``` + +-------------------------------------------------- + +# 1.3.0 (2020-04-29) + +## Changes +- Add new `class` rule to check the instance of value [#126](https://github.com/icebob/fastest-validator/issues/126) +- Updated typescript definitions [#127](https://github.com/icebob/fastest-validator/issues/127) [#129](https://github.com/icebob/fastest-validator/issues/129) +- Fix deep-extend function to detect objects better. [#128](https://github.com/icebob/fastest-validator/issues/128) +- Add `hex` check to `string` rule [#132](https://github.com/icebob/fastest-validator/issues/132) +-------------------------------------------------- + +# 1.2.0 (2020-04-05) + +## Changes +- Add default settings for built-in rules [#120](https://github.com/icebob/fastest-validator/issues/120) by [@erfanium](https://github.com/erfanium) +- Updated typescript definitions [#122](https://github.com/icebob/fastest-validator/issues/122) by [@FFKL](https://github.com/FFKL) + +-------------------------------------------------- + +# 1.1.0 (2020-03-22) + +## Changes +- New user-defined 'alias' feature [#118](https://github.com/icebob/fastest-validator/issues/118) by [@erfanium](https://github.com/erfanium) +- Add custom validation function for built-in rules [#119](https://github.com/icebob/fastest-validator/issues/119) by [@erfanium](https://github.com/erfanium) + +-------------------------------------------------- + +# 1.0.2 (2020-02-09) + +## Changes +- Fix string with pattern where regular expression contains a double quote [#111](https://github.com/icebob/fastest-validator/issues/111) by [@FranzZemen](https://github.com/FranzZemen) + +-------------------------------------------------- + +# 1.0.1 (2020-02-01) + +## Changes +- fix missing field property in custom rules [#109](https://github.com/icebob/fastest-validator/issues/109) + +-------------------------------------------------- + +# 1.0.0 (2019-12-18) + +## Changes +- add unique validation in array rule [#104](https://github.com/icebob/fastest-validator/pull/104) + +-------------------------------------------------- + +# 1.0.0-beta4 (2019-11-17) + +## Changes +- fix optional multi rule. +- fix array rule return value issue (again). + +-------------------------------------------------- + +# 1.0.0-beta2 (2019-11-15) + +## Changes +- fix array rule return value issue. + +-------------------------------------------------- + +# 1.0.0-beta1 (2019-11-15) + +The full library has been rewritten. It uses code generators in order to be much faster. + +## Breaking changes +This new version contains several breaking changes. + +### Rule logic changed +The rule codes have been rewritten to code generator functions. Therefore if you use custom validators, you should rewrite them after upgrading. + +### Convert values +The `number`, `boolean` and `date` rules have a `convert: true` property. In the previous version it doesn't modify the value in the checked object, just converted the value to the rules. In the version 1.0 this property converts the values in the checked object, as well. + +## New + +### Sanitizations +The sanitization function is implemented. There are several rules which contains sanitizers. **Please note, the sanitizers change the original checked object values.** + +| Rule | Property | Description | +| ---- | -------- | ----------- | +`boolean` | `convert` | Convert the value to a boolean. +`number` | `convert` | Convert the value to a number. +`date` | `convert` | Convert the value to a date. +`string` | `trim` | Trim the value. +`string` | `trimLeft` | Left trim the value. +`string` | `trimRight` | Right trim the value. +`string` | `lowercase` | Lowercase the value. +`string` | `uppercase` | Uppercase the value. +`string` | `localeLowercase` | Lowercase the value with `String.toLocaleLowerCase`. +`string` | `localeUppercase` | Uppercase the value with `String.toLocaleUpperCase`. +`string` | `padStart` | Left padding the value. +`string` | `padEnd` | Right padding the value. +`string` | `convert` | Convert the value to a string. +`email` | `normalize` | Trim & lowercase the value. +`forbidden` | `remove` | Remove the forbidden field. +`object` | `strict: "remove"` | Remove additional properties in the object. +`*` | `default` | Use this default value if the value is `null` or `undefined`. + +### Root element validation +Basically the validator expects that you want to validate a Javascript object. If you want others, you can define the root level schema, as well. In this case set the `$$root: true` property. + +**Example to validate a `string` variable instead of `object`** +```js +const schema = { + $$root: true, + type: "string", + min: 3, + max: 6 +}; + +v.validate("John", schema); // Valid +v.validate("Al", schema); // Fail, too short. +``` + +### Enhanced shorthand types +You can use string-based shorthand validation definitions in the schema with properties. + +```js +{ + password: "string|min:6", + age: "number|optional|integer|positive|min:0|max:99", + + retry: ["number|integer|min:0", "boolean"] // multiple types +} +``` + +## Other changes + +### New `equal` rule +It checks the value equal (`==`) to a static value or another property. The `strict` property uses `===` to check values. + +**Example with static value**: +```js +const schema = { + agreeTerms: { type: "equal", value: true, strict: true } // strict means `===` +} + +v.validate({ agreeTerms: true }, schema); // Valid +v.validate({ agreeTerms: false }, schema); // Fail +``` + +**Example with other field**: +```js +const schema = { + password: { type: "string", min: 6 }, + confirmPassword: { type: "equal", field: "password" } +} + +v.validate({ password: "123456", confirmPassword: "123456" }, schema); // Valid +v.validate({ password: "123456", confirmPassword: "pass1234" }, schema); // Fail +``` + +### `properties` in object rule +You can use the `properties` property besides the `props` property in the object rule. + -------------------------------------------------- # 0.6.19 (2019-10-25) diff --git a/README.md b/README.md index 9d851a5..7c2a38a 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,19 @@  -[](https://travis-ci.org/icebob/fastest-validator) + [](https://coveralls.io/github/icebob/fastest-validator?branch=master) [](https://www.codacy.com/app/mereg-norbert/fastest-validator?utm_source=github.com&utm_medium=referral&utm_content=icebob/fastest-validator&utm_campaign=Badge_Grade) [](https://snyk.io/test/github/icebob/fastest-validator) [](https://bundlephobia.com/result?p=fastest-validator) # fastest-validator [](https://www.npmjs.com/package/fastest-validator) [](https://twitter.com/intent/tweet?text=The%20fastest%20JS%20validator%20library%20for%20NodeJS&url=https://github.com/icebob/fastest-validator&via=Icebobcsi&hashtags=nodejs,javascript) -:zap: The fastest JS validator library for NodeJS. - -**If you like my work, please [donate](https://www.paypal.me/meregnorbert). Thank you!** +:zap: The fastest JS validator library for NodeJS | Browser | Deno. ## Key features -* fast! Really! -* 15+ built-in validators -* custom validators +* blazing fast! Really! +* 20+ built-in validators +* many sanitizations +* custom validators & aliases * nested objects & array handling * strict object validation * multiple validators @@ -23,16 +22,16 @@ * no dependencies * unit tests & 100% coverage -# How fast? -Very fast! ~5 million validations/sec (on Intel i7-4770K, Node.JS: 8.11.0) +## How fast? +Very fast! 8 million validations/sec (on Intel i7-4770K, Node.JS: 12.14.1) ``` -√ validate with pre-compiled schema 5,460,129 rps +√ validate 8,678,752 rps ``` Compared to other popular libraries: -[](https://github.com/icebob/validator-benchmark#result) -> 100x faster than Joi. +[](https://github.com/icebob/validator-benchmark#result) +> 50x faster than Joi. **Would you like to test it?** @@ -43,27 +42,31 @@ $ npm install $ npm run bench ``` -## Installation -### NPM +## Approach +In order to achieve lowest cost/highest performance redaction fastest-validator creates and compiles functions using the `Function` constructor. It's important to distinguish this from the dangers of a runtime eval, no user input is involved in creating the validation schema that compiles into the function. This is as safe as writing code normally and having it compiled by V8 in the usual way. + +# Installation + +## NPM You can install it via [NPM](http://npmjs.org/). ``` -$ npm install fastest-validator --save +$ npm i fastest-validator --save ``` or ``` $ yarn add fastest-validator ``` -## Usage +# Usage -### Simple method -Call the `validate` method with the `object` and the `schema`. -> If performance is important, you won't use this method. +## Validate +The first step is to compile the schema to a compiled "checker" function. After that, to validate your object, just call this "checker" function. +> This method is the fastest. ```js -let Validator = require("fastest-validator"); +const Validator = require("fastest-validator"); -let v = new Validator(); +const v = new Validator(); const schema = { id: { type: "number", positive: true, integer: true }, @@ -71,45 +74,12 @@ const schema = { status: "boolean" // short-hand def }; -console.log(v.validate({ id: 5, name: "John", status: true }, schema)); -// Returns: true - -console.log(v.validate({ id: 5, name: "Al", status: true }, schema)); -/* Returns an array with errors: - [ - { - type: 'stringMin', - expected: 3, - actual: 2, - field: 'name', - message: 'The \'name\' field length must be greater than or equal to 3 characters long!' - } - ] -*/ -``` -[Try it on Runkit](https://runkit.com/icebob/fastest-validator-usage-simple) - -### Fast method -In this case, the first step is to compile the schema to a compiled "checker" function. After that, to validate your object, just call this "checker" function. -> This method is ~10x faster than the "simple method". - -```js -let Validator = require("fastest-validator"); - -let v = new Validator(); - -var schema = { - id: { type: "number", positive: true, integer: true }, - name: { type: "string", min: 3, max: 255 }, - status: "boolean" // short-hand def -}; - -var check = v.compile(schema); +const check = v.compile(schema); -console.log(check({ id: 5, name: "John", status: true })); +console.log("First:", check({ id: 5, name: "John", status: true })); // Returns: true -console.log(check({ id: 2, name: "Adam" })); +console.log("Second:", check({ id: 2, name: "Adam" })); /* Returns an array with errors: [ { @@ -120,15 +90,22 @@ console.log(check({ id: 2, name: "Adam" })); ] */ ``` -[Try it on Runkit](https://runkit.com/icebob/fastest-validator-usage-quick) +[Try it on Repl.it](https://repl.it/@icebob/fastest-validator-fast) + +### Halting + +If you want to halt immediately after the first error: +```js +const v = new Validator({ haltOnFirstError: true }); +``` -### Browser usage +## Browser usage ```html ``` ```js -var v = new FastestValidator(); +const v = new FastestValidator(); const schema = { id: { type: "number", positive: true, integer: true }, @@ -142,62 +119,319 @@ console.log(check({ id: 5, name: "John", status: true })); // Returns: true ``` -# Optional & required fields +## Deno usage +With `esm.sh`, now Typescript is supported + +```js +import FastestValidator from "https://esm.sh/fastest-validator@1" + +const v = new FastestValidator(); +const check = v.compile({ + name: "string", + age: "number", +}); + +console.log(check({ name: "Erf", age: 18 })); //true +``` + +## Supported frameworks +- *Moleculer*: Natively supported +- *Fastify*: By using [fastify-fv](https://github.com/erfanium/fastify-fv) +- *Express*: By using [fastest-express-validator](https://github.com/muturgan/fastest-express-validator) + + +# Optional, Required & Nullable fields +## Optional Every field in the schema will be required by default. If you'd like to define optional fields, set `optional: true`. ```js -let schema = { +const schema = { name: { type: "string" }, // required age: { type: "number", optional: true } } -v.validate({ name: "John", age: 42 }, schema); // Valid -v.validate({ name: "John" }, schema); // Valid -v.validate({ age: 42 }, schema); // Fail +const check = v.compile(schema); + +check({ name: "John", age: 42 }); // Valid +check({ name: "John" }); // Valid +check({ age: 42 }); // Fail because name is required +``` + +## Nullable +If you want disallow `undefined` value but allow `null` value, use `nullable` instead of `optional`. +```js +const schema = { + age: { type: "number", nullable: true } +} + +const check = v.compile(schema); + +check({ age: 42 }); // Valid +check({ age: null }); // Valid +check({ age: undefined }); // Fail because undefined is disallowed +check({}); // Fail because undefined is disallowed +``` +### Nullable and default values +`null` is a valid input for nullable fields that has default value. + +```js +const schema = { + about: { type: "string", nullable: true, default: "Hi! I'm using javascript" } +} + +const check = v.compile(schema) + +const object1 = { about: undefined } +check(object1) // Valid +object1.about // is "Hi! I'm using javascript" + +const object2 = { about: null } +check(object2) // valid +object2.about // is null + +check({ about: "Custom" }) // Valid ``` +### Considering `null` as a value +In specific case, you may want to consider `null` as a valid input even for a `required` field. + +It's useful in cases you want a field to be: + - `required` and `null` without specifying `nullable: true` in its definition. + - `required` and not `null` by specifying `nullable: false` in its definition. + - `optional` **but specifically not** `null`. + +To be able to achieve this you'll have to set the `considerNullAsAValue` validator option to `true`. +```js +const v = new Validator({considerNullAsAValue: true}); + +const schema = {foo: {type: "number"}, bar: {type: "number", optional: true, nullable: false}, baz: {type: "number", nullable: false}}; +const check = v.compile(schema); + +const object1 = {foo: null, baz: 1}; +check(object1); // valid (foo is required and can be null) + +const object2 = {foo: 3, bar: null, baz: 1}; +check(object2); // not valid (bar is optional but can't be null) + +const object3 = {foo: 3, baz: null}; +check(object3); // not valid (baz is required but can't be null) + +``` +With this option set all fields will be considered _nullable_ by default. # Strict validation -Object properties which are not specified on the schema are ignored by default. If you set the `$$strict` option to `true` any aditional properties will result in an `strictObject` error. +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. ```js -let schema = { +const schema = { name: { type: "string" }, // required $$strict: true // no additional properties allowed } -v.validate({ name: "John" }, schema); // Valid -v.validate({ name: "John", age: 42 }, schema); // Fail +const check = v.compile(schema); + +check({ name: "John" }); // Valid +check({ name: "John", age: 42 }); // Fail ``` +## Remove additional fields +To remove the additional fields in the object, set `$$strict: "remove"`. + + # Multiple validators It is possible to define more validators for a field. In this case, only one validator needs to succeed for the field to be valid. ```js -let schema = { +const schema = { cache: [ { type: "string" }, { type: "boolean" } ] } -v.validate({ cache: true }, schema); // Valid -v.validate({ cache: "redis://" }, schema); // Valid -v.validate({ cache: 150 }, schema); // Fail +const check = v.compile(schema); + +check({ cache: true }); // Valid +check({ cache: "redis://" }); // Valid +check({ cache: 150 }); // Fail ``` +# Root element schema +Basically the validator expects that you want to validate a Javascript object. If you want others, you can define the root level schema, as well. In this case set the `$$root: true` property. + +**Example to validate a `string` variable instead of `object`** +```js +const schema = { + $$root: true, + type: "string", + min: 3, + max: 6 +}; + +const check = v.compile(schema); + +check("John"); // Valid +check("Al"); // Fail, too short. +``` + +# Sanitizations +The library contains several sanitizers. **Please note, the sanitizers change the original checked object.** + +## Default values +The most common sanitizer is the `default` property. With it, you can define a default value for all properties. If the property value is `null`* or `undefined`, the validator set the defined default value into the property. + +**Static Default value example**: +```js +const schema = { + roles: { type: "array", items: "string", default: ["user"] }, + status: { type: "boolean", default: true }, +}; + +const check = v.compile(schema); + +const obj = {} + +check(obj); // Valid +console.log(obj); +/* +{ + roles: ["user"], + status: true +} +*/ +``` +**Dynamic Default value**: +Also you can use dynamic default value by defining a function that returns a value. For example, in the following code, if `createdAt` field not defined in object`, the validator sets the current time into the property: + +```js +const schema = { + createdAt: { + type: "date", + default: (schema, field, parent, context) => new Date() + } +}; + +const check = v.compile(schema); + +const obj = {} + +check(obj); // Valid +console.log(obj); +/* +{ + createdAt: Date(2020-07-25T13:17:41.052Z) +} +*/ +``` + +# Shorthand definitions +You can use string-based shorthand validation definitions in the schema. + +```js +const schema = { + password: "string|min:6", + age: "number|optional|integer|positive|min:0|max:99", // additional properties + state: ["boolean", "number|min:0|max:1"] // multiple types +} +``` + +### Array of X +```js +const schema = { + foo: "string[]" // means array of string +} + +const check = v.compile(schema); + +check({ foo: ["bar"] }) // true +``` + +### Nested objects + +```js +const schema = { + dot: { + $$type: "object", + x: "number", // object props here + y: "number", // object props here + }, + circle: { + $$type: "object|optional", // using other shorthands + o: { + $$type: "object", + x: "number", + y: "number", + }, + r: "number" + } +}; +``` + +# Alias definition +You can define custom aliases. + +```js +v.alias('username', { + type: 'string', + min: 4, + max: 30 + // ... +}); + +const schema = { + username: "username|max:100", // Using the 'username' alias + password: "string|min:6", +} +``` + +# Default options +You can set default rule options. + +```js +const v = new FastestValidator({ + defaults: { + object: { + strict: "remove" + } + } +}); +``` +# 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` This does not do type validation. Accepts any types. ```js -let schema = { +const schema = { prop: { type: "any" } } -v.validate({ prop: true }, schema); // Valid -v.validate({ prop: 100 }, schema); // Valid -v.validate({ prop: "John" }, schema); // Valid +const check = v.compile(schema) + +check({ prop: true }); // Valid +check({ prop: 100 }); // Valid +check({ prop: "John" }); // Valid ``` ## `array` @@ -205,32 +439,34 @@ This is an `Array` validator. **Simple example with strings:** ```js -let schema = { +const schema = { roles: { type: "array", items: "string" } } +const check = v.compile(schema) -v.validate({ roles: ["user"] }, schema); // Valid -v.validate({ roles: [] }, schema); // Valid -v.validate({ roles: "user" }, schema); // Fail +check({ roles: ["user"] }); // Valid +check({ roles: [] }); // Valid +check({ roles: "user" }); // Fail ``` **Example with only positive numbers:** ```js -let schema = { +const schema = { list: { type: "array", min: 2, items: { type: "number", positive: true, integer: true } } } +const check = v.compile(schema) -v.validate({ list: [2, 4] }, schema); // Valid -v.validate({ list: [1, 5, 8] }, schema); // Valid -v.validate({ list: [1] }, schema); // Fail (min 2 elements) -v.validate({ list: [1, -7] }, schema); // Fail (negative number) +check({ list: [2, 4] }); // Valid +check({ list: [1, 5, 8] }); // Valid +check({ list: [1] }); // Fail (min 2 elements) +check({ list: [1, -7] }); // Fail (negative number) ``` **Example with an object list:** ```js -let schema = { +const schema = { users: { type: "array", items: { type: "object", props: { id: { type: "number", positive: true }, @@ -239,104 +475,213 @@ let schema = { } } } } +const check = v.compile(schema) -v.validate({ +check({ users: [ { id: 1, name: "John", status: true }, { id: 2, name: "Jane", status: true }, { id: 3, name: "Bill", status: false } ] -}, schema); // Valid +}); // Valid +``` + +**Example for `enum`:** +```js +const schema = { + roles: { type: "array", items: "string", enum: [ "user", "admin" ] } +} + +const check = v.compile(schema) + +check({ roles: ["user"] }); // Valid +check({ roles: ["user", "admin"] }); // Valid +check({ roles: ["guest"] }); // Fail ``` +**Example for `unique`:** +```js +const schema = { + roles: { type: "array", unique: true } +} +const check = v.compile(schema); + +check({ roles: ["user"] }); // Valid +check({ roles: [{role:"user"},{role:"admin"},{role:"user"}] }); // Valid +check({ roles: ["user", "admin", "user"] }); // Fail +check({ roles: [1, 2, 1] }); // Fail +``` + +**Example for `convert`:** + +```js +const schema = { + roles: { type: "array", items: 'string', convert: true } +} +const check = v.compile(schema); + +check({ roles: ["user"] }); // Valid +check({ roles: "user" }); // Valid +// After both validation: roles = ["user"] +``` ### Properties Property | Default | Description -------- | -------- | ----------- -`empty` | `true` | If true, the validator accepts an empty array `[]`. +`empty` | `true` | If `true`, the validator accepts an empty array `[]`. `min` | `null` | Minimum count of elements. `max` | `null` | Maximum count of elements. `length` | `null` | Fix count of elements. `contains` | `null` | The array must contain this element too. +`unique` | `null` | The array must be unique (array of objects is always unique). `enum` | `null` | Every element must be an element of the `enum` array. +`items` | `null` | Schema for array items. +`convert`| `null` | Wrap value into array if different type provided + +## `boolean` +This is a `Boolean` validator. -**Example for `enum`:** ```js -let schema = { - roles: { type: "array", items: "string", enum: [ "user", "admin" ] } +const schema = { + status: { type: "boolean" } } +const check = v.compile(schema); -v.validate({ roles: ["user"] }, schema); // Valid -v.validate({ roles: ["user", "admin"] }, schema); // Valid -v.validate({ roles: ["guest"] }, schema); // Fail +check({ status: true }); // Valid +check({ status: false }); // Valid +check({ status: 1 }); // Fail +check({ status: "true" }); // Fail ``` +### Properties +Property | Default | Description +-------- | -------- | ----------- +`convert` | `false` | if `true` and the type is not `Boolean`, it will be converted. `1`, `"true"`, `"1"`, `"on"` will be true. `0`, `"false"`, `"0"`, `"off"` will be false. _It's a sanitizer, it will change the value in the original object._ +**Example for `convert`:** +```js +const schema = { + status: { type: "boolean", convert: true} +}; -## `boolean` -This is a `Boolean` validator. +const check = v.compile(schema); + +check({ status: "true" }); // Valid +``` + +## `class` +This is a `Class` validator to check the value is an instance of a Class. ```js -let schema = { - status: { type: "boolean" } +const schema = { + rawData: { type: "class", instanceOf: Buffer } } +const check = v.compile(schema); -v.validate({ status: true }, schema); // Valid -v.validate({ status: false }, schema); // Valid -v.validate({ status: 1 }, schema); // Fail -v.validate({ status: "true" }, schema); // Fail +check({ rawData: Buffer.from([1, 2, 3]) }); // Valid +check({ rawData: 100 }); // Fail ``` + ### Properties Property | Default | Description -------- | -------- | ----------- -`convert` | `false` | if `true` and the type is not `Boolean`, try to convert. `1`, `"true"`, `"1"`, `"on"` will be true. `0`, `"false"`, `"0"`, `"off"` will be false. +`instanceOf` | `null` | Checked Class. + +## `currency` +This is a `Currency` validator to check if the value is a valid currency string. + +```js +const schema = { + money_amount: { type: "currency", currencySymbol: '$' } +} +const check = v.compile(schema); + + +check({ money_amount: '$12.99'}); // Valid +check({ money_amount: '$0.99'}); // Valid +check({ money_amount: '$12,345.99'}); // Valid +check({ money_amount: '$123,456.99'}); // Valid + +check({ money_amount: '$1234,567.99'}); // Fail +check({ money_amount: '$1,23,456.99'}); // Fail +check({ money_amount: '$12,34.5.99' }); // Fail +``` +### Properties +Property | Default | Description +-------- | -------- | ----------- +`currencySymbol` | `null` | The currency symbol expected in string (as prefix). +`symbolOptional` | `false` | Toggle to make the symbol optional in string, although, if present it would only allow the currencySymbol. +`thousandSeparator` | `,` | Thousand place separator character. +`decimalSeparator` | `.` | Decimal place character. +`customRegex` | `null` | Custom regular expression, to validate currency strings (For eg: /[0-9]*/g). ## `date` This is a `Date` validator. ```js -let schema = { +const schema = { dob: { type: "date" } } +const check = v.compile(schema); -v.validate({ dob: new Date() }, schema); // Valid -v.validate({ dob: new Date(1488876927958) }, schema); // Valid -v.validate({ dob: 1488876927958 }, schema); // Fail +check({ dob: new Date() }); // Valid +check({ dob: new Date(1488876927958) }); // Valid +check({ dob: 1488876927958 }); // Fail ``` + ### Properties Property | Default | Description -------- | -------- | ----------- -`convert` | `false`| if `true` and the type is not `Date`, try to convert with `new Date()`. +`convert` | `false`| if `true` and the type is not `Date`, try to convert with `new Date()`. _It's a sanitizer, it will change the value in the original object._ + +**Example for `convert`:** +```js +const schema = { + dob: { type: "date", convert: true} +}; + +const check = v.compile(schema); + +check({ dob: 1488876927958 }, ); // Valid +``` ## `email` This is an e-mail address validator. ```js -let schema = { +const schema = { email: { type: "email" } } +const check = v.compile(schema); + -v.validate({ email: "john.doe@gmail.com" }, schema); // Valid -v.validate({ email: "james.123.45@mail.co.uk" }, schema); // Valid -v.validate({ email: "abc@gmail" }, schema); // Fail +check({ email: "john.doe@gmail.com" }); // Valid +check({ email: "james.123.45@mail.co.uk" }); // Valid +check({ email: "abc@gmail" }); // Fail ``` ### Properties Property | Default | Description -------- | -------- | ----------- +`empty` | `false` | If `true`, the validator accepts an empty array `""`. `mode` | `quick` | Checker method. Can be `quick` or `precise`. +`normalize` | `false` | Normalize the e-mail address (trim & lower-case). _It's a sanitizer, it will change the value in the original object._ +`min` | `null` | Minimum value length. +`max` | `null` | Maximum value length. ## `enum` This is an enum validator. ```js -let schema = { +const schema = { sex: { type: "enum", values: ["male", "female"] } } +const check = v.compile(schema); + -v.validate({ sex: "male" }, schema); // Valid -v.validate({ sex: "female" }, schema); // Valid -v.validate({ sex: "other" }, schema); // Fail +check({ sex: "male" }); // Valid +check({ sex: "female" }); // Valid +check({ sex: "other" }); // Fail ``` ### Properties @@ -344,44 +689,179 @@ Property | Default | Description -------- | -------- | ----------- `values` | `null` | The valid values. +## `equal` +This is an equal value validator. It checks a value with a static value or with another property. + +**Example with static value**: +```js +const schema = { + agreeTerms: { type: "equal", value: true, strict: true } // strict means `===` +} +const check = v.compile(schema); + +check({ agreeTerms: true }); // Valid +check({ agreeTerms: false }); // Fail +``` + +**Example with other field**: +```js +const schema = { + password: { type: "string", min: 6 }, + confirmPassword: { type: "equal", field: "password" } +} +const check = v.compile(schema); + +check({ password: "123456", confirmPassword: "123456" }); // Valid +check({ password: "123456", confirmPassword: "pass1234" }); // Fail +``` + +### Properties +Property | Default | Description +-------- | -------- | ----------- +`value` | `undefined`| The expected value. It can be any primitive types. +`strict` | `false`| if `true`, it uses strict equal `===` for checking. ## `forbidden` This validator returns an error if the property exists in the object. ```js -let schema = { - password: { type: "forbidden" } +const schema = { + password: { type: "forbidden" } +} +const check = v.compile(schema); + + +check({ user: "John" }); // Valid +check({ user: "John", password: "pass1234" }); // Fail +``` + +### Properties +Property | Default | Description +-------- | -------- | ----------- +`remove` | `false` | If `true`, the value will be removed in the original object. _It's a sanitizer, it will change the value in the original object._ + +**Example for `remove`:** +```js +const schema = { + user: { type: "string" }, + token: { type: "forbidden", remove: true } +}; +const check = v.compile(schema); + + +const obj = { + user: "John", + token: "123456" +} + +check(obj); // Valid +console.log(obj); +/* +{ + user: "John", + token: undefined +} +*/ +``` + +## `function` +This a `Function` type validator. + +```js +const schema = { + show: { type: "function" } +} +const check = v.compile(schema); + + +check({ show: function() {} }); // Valid +check({ show: Date.now }); // Valid +check({ show: "function" }); // Fail +``` + +## `luhn` +This is an Luhn validator. +[Luhn algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm) checksum +Credit Card numbers, IMEI numbers, National Provider Identifier numbers and others + +```js +const schema = { + cc: { type: "luhn" } +} +const check = v.compile(schema); + +check({ cc: "452373989901198" }); // Valid +check({ cc: 452373989901198 }); // Valid +check({ cc: "4523-739-8990-1198" }); // Valid +check({ cc: "452373989901199" }); // Fail +``` + +## `mac` +This is an MAC addresses validator. + +```js +const schema = { + mac: { type: "mac" } } +const check = v.compile(schema); -v.validate({ user: "John" }, schema); // Valid -v.validate({ user: "John", password: "pass1234" }, schema); // Fail +check({ mac: "01:C8:95:4B:65:FE" }); // Valid +check({ mac: "01:c8:95:4b:65:fe"); // Valid +check({ mac: "01C8.954B.65FE" }); // Valid +check({ mac: "01c8.954b.65fe"); // Valid +check({ mac: "01-C8-95-4B-65-FE" }); // Valid +check({ mac: "01-c8-95-4b-65-fe" }); // Valid +check({ mac: "01C8954B65FE" }); // Fail ``` -## `function` -This a `Function`validator. +## `multi` +This is a multiple definitions validator. ```js -let schema = { - show: { type: "function" } +const schema = { + status: { type: "multi", rules: [ + { type: "boolean" }, + { type: "number" } + ], default: true } } +const check = v.compile(schema); -v.validate({ show: function() {} }, schema); // Valid -v.validate({ show: Date.now }, schema); // Valid -v.validate({ show: null }, schema); // Fail +check({ status: true }); // Valid +check({ status: false }); // Valid +check({ status: 1 }); // Valid +check({ status: 0 }); // Valid +check({ status: "yes" }); // Fail ``` +**Shorthand multiple definitions**: +```js +const schema = { + status: [ + "boolean", + "number" + ] +} +const check = v.compile(schema); + +check({ status: true }); // Valid +check({ status: false }); // Valid +check({ status: 1 }); // Valid +check({ status: 0 }); // Valid +check({ status: "yes" }); // Fail +``` ## `number` This is a `Number` validator. ```js -let schema = { +const schema = { age: { type: "number" } } +const check = v.compile(schema); -v.validate({ age: 123 }, schema); // Valid -v.validate({ age: 5.65 }, schema); // Valid -v.validate({ age: "100" }, schema); // Fail +check({ age: 123 }); // Valid +check({ age: 5.65 }); // Valid +check({ age: "100" }); // Fail ``` ### Properties @@ -394,57 +874,170 @@ Property | Default | Description `integer` | `false` | The value must be a non-decimal value. `positive` | `false`| The value must be greater than zero. `negative` | `false`| The value must be less than zero. -`convert` | `false`| if `true` and the type is not `Number`, tries to convert with `parseFloat`. +`convert` | `false`| if `true` and the type is not `Number`, it's converted with `Number()`. _It's a sanitizer, it will change the value in the original object._ ## `object` This is a nested object validator. + ```js -let schema = { - address: { type: "object", props: { +const schema = { + address: { type: "object", strict: true, props: { country: { type: "string" }, city: "string", // short-hand zip: "number" // short-hand } } } +const check = v.compile(schema); -v.validate({ +check({ address: { country: "Italy", city: "Rome", zip: 12345 } -}, schema); // Valid +}); // Valid -v.validate({ +check({ address: { country: "Italy", city: "Rome" } -}, schema); // Fail ("The 'address.zip' field is required!") +}); // Fail ("The 'address.zip' field is required!") + +check({ + address: { + country: "Italy", + city: "Rome", + zip: 12345, + state: "IT" + } +}); // Fail ("The 'address.state' is an additional field!") ``` ### Properties Property | Default | Description -------- | -------- | ----------- -`strict` | `false`| if `true` any properties which are not defined on the schema will throw an error. +`strict` | `false`| If `true` any properties which are not defined on the schema will throw an error. If `remove` all additional properties will be removed from the original object. _It's a sanitizer, it will change the original object._ +`minProps` | `null` | If set to a number N, will throw an error if the object has fewer than N properties. +`maxProps` | `null` | If set to a number N, will throw an error if the object has more than N properties. + +```js +schema = { + address: { type: "object", strict: "remove", props: { + country: { type: "string" }, + city: "string", // short-hand + zip: "number" // short-hand + } } +} + +let obj = { + address: { + country: "Italy", + city: "Rome", + zip: 12345, + state: "IT" + } +}; +const check = v.compile(schema); + +check(obj); // Valid +console.log(obj); +/* +{ + address: { + country: "Italy", + city: "Rome", + zip: 12345 + } +} +*/ +``` +```js +schema = { + address: { + type: "object", + minProps: 2, + props: { + country: { type: "string" }, + city: { type: "string", optional: true }, + zip: { type: "number", optional: true } + } + } +} +const check = v.compile(schema); + + +obj = { + address: { + country: "Italy", + city: "Rome", + zip: 12345, + state: "IT" + } +} + +check(obj); // Valid + +obj = { + address: { + country: "Italy", + } +} + +check(obj); // Fail +// [ +// { +// type: 'objectMinProps', +// message: "The object 'address' must contain at least 2 properties.", +// field: 'address', +// expected: 2, +// actual: 1 +// } +// ] +``` + +## `record` +This validator allows to check an object with arbitrary keys. + +```js +const schema = { + surnameGroups: { + type: 'record', + key: { type: 'string', alpha: true }, + value: { type: 'array', items: 'string' } + } +}; +const check = v.compile(schema); + +check({ surnameGroups: { Doe: ['Jane', 'John'], Williams: ['Bill'] } }); // Valid +check({ surnameGroups: { Doe1: ['Jane', 'John'] } }); // Fail +check({ surnameGroups: { Doe: [1, 'Jane'] } }); // Fail +``` + +### Properties +Property | Default | Description +-------- |----------| ----------- +`key` | `string` | Key validation rule (It is reasonable to use only the `string` rule). +`value` | `any` | Value validation rule. ## `string` -This is a `String`. +This is a `String` validator. ```js -let schema = { +const schema = { name: { type: "string" } } +const check = v.compile(schema); -v.validate({ name: "John" }, schema); // Valid -v.validate({ name: "" }, schema); // Valid -v.validate({ name: 123 }, schema); // Fail +check({ name: "John" }); // Valid +check({ name: "" }); // Valid +check({ name: 123 }); // Fail ``` ### Properties Property | Default | Description -------- | -------- | ----------- -`empty` | `true` | If true, the validator accepts an empty string `""`. +`empty` | `true` | If `true`, the validator accepts an empty string `""`. `min` | `null` | Minimum value length. `max` | `null` | Maximum value length. `length` | `null` | Fixed value length. @@ -455,76 +1048,175 @@ Property | Default | Description `numeric` | `null` | The value must be a numeric string. `alphanum` | `null` | The value must be an alphanumeric string. `alphadash` | `null` | The value must be an alphabetic string that contains dashes. +`hex` | `null` | The value must be a hex string. +`singleLine` | `null` | The value must be a single line string. +`base64` | `null` | The value must be a base64 string. +`trim` | `null` | If `true`, the value will be trimmed. _It's a sanitizer, it will change the value in the original object._ +`trimLeft` | `null` | If `true`, the value will be left trimmed. _It's a sanitizer, it will change the value in the original object._ +`trimRight` | `null` | If `true`, the value will be right trimmed. _It's a sanitizer, it will change the value in the original object._ +`padStart` | `null` | If it's a number, the value will be left padded. _It's a sanitizer, it will change the value in the original object._ +`padEnd` | `null` | If it's a number, the value will be right padded. _It's a sanitizer, it will change the value in the original object._ +`padChar` | `" "` | The padding character for the `padStart` and `padEnd`. +`lowercase` | `null` | If `true`, the value will be lower-cased. _It's a sanitizer, it will change the value in the original object._ +`uppercase` | `null` | If `true`, the value will be upper-cased. _It's a sanitizer, it will change the value in the original object._ +`localeLowercase` | `null` | If `true`, the value will be locale lower-cased. _It's a sanitizer, it will change the value in the original object._ +`localeUppercase` | `null` | If `true`, the value will be locale upper-cased. _It's a sanitizer, it will change the value in the original object._ +`convert` | `false`| if `true` and the type is not a `String`, it's converted with `String()`. _It's a sanitizer, it will change the value in the original object._ + +**Sanitization example** +```js +const schema = { + username: { type: "string", min: 3, trim: true, lowercase: true} +} +const check = v.compile(schema); + +const obj = { + username: " Icebob " +}; + +check(obj); // Valid +console.log(obj); +/* +{ + username: "icebob" +} +*/ +``` + +## `tuple` +This validator checks if a value is an `Array` with the elements order as described by the schema. + +**Simple example:** +```js +const schema = { list: "tuple" }; +const check = v.compile(schema); +check({ list: [] }); // Valid +check({ list: [1, 2] }); // Valid +check({ list: ["RON", 100, true] }); // Valid +check({ list: 94 }); // Fail (not an array) +``` + +**Example with items:** +```js +const schema = { + grade: { type: "tuple", items: ["string", "number"] } +} +const check = v.compile(schema); + +check({ grade: ["David", 85] }); // Valid +check({ grade: [85, "David"] }); // Fail (wrong position) +check({ grade: ["Cami"] }); // Fail (require 2 elements) +``` + +**Example with a more detailed schema:** +```js +const schema = { + location: { type: "tuple", items: [ + "string", + { type: "tuple", empty: false, items: [ + { type: "number", min: 35, max: 45 }, + { type: "number", min: -75, max: -65 } + ] } + ] } +} +const check = v.compile(schema); + +check({ location: ['New York', [40.7127281, -74.0060152]] }); // Valid +check({ location: ['New York', [50.0000000, -74.0060152]] }); // Fail +check({ location: ['New York', []] }); // Fail (empty array) +``` + +### Properties +Property | Default | Description +-------- | -------- | ----------- +`empty` | `true` | If `true`, the validator accepts an empty array `[]`. +`items` | `undefined` | Exact schema of the value items ## `url` This is an URL validator. ```js -let schema = { +const schema = { url: { type: "url" } } +const check = v.compile(schema); -v.validate({ url: "http://google.com" }, schema); // Valid -v.validate({ url: "https://github.com/icebob" }, schema); // Valid -v.validate({ url: "www.facebook.com" }, schema); // Fail +check({ url: "http://google.com" }); // Valid +check({ url: "https://github.com/icebob" }); // Valid +check({ url: "www.facebook.com" }); // Fail ``` +### Properties +Property | Default | Description +-------- | -------- | ----------- +`empty` | `false` | If `true`, the validator accepts an empty string `""`. + ## `uuid` This is an UUID validator. ```js -let schema = { +const schema = { uuid: { type: "uuid" } } +const check = v.compile(schema); -v.validate({ uuid: "10ba038e-48da-487b-96e8-8d3b99b6d18a" }, schema); // Valid UUIDv4 -v.validate({ uuid: "9a7b330a-a736-51e5-af7f-feaf819cdc9f" }, schema); // Valid UUIDv5 -v.validate({ uuid: "10ba038e-48da-487b-96e8-8d3b99b6d18a", version: 5 }, schema); // Fail +check({ uuid: "00000000-0000-0000-0000-000000000000" }); // Valid Nil UUID +check({ uuid: "10ba038e-48da-487b-96e8-8d3b99b6d18a" }); // Valid UUIDv4 +check({ uuid: "9a7b330a-a736-51e5-af7f-feaf819cdc9f" }); // Valid UUIDv5 +check({ uuid: "10ba038e-48da-487b-96e8-8d3b99b6d18a", version: 5 }); // Fail ``` ### Properties Property | Default | Description -------- | -------- | ----------- -`version` | `4` | UUID version in range 1-5. - -## `mac` -This is an MAC addresses validator. +`version` | `null` | UUID version in range 0-6. The `null` disables version checking. +## `objectID` +You can validate BSON/MongoDB ObjectID's ```js -let schema = { - mac: { type: "mac" } +const { ObjectID } = require("mongodb") // or anywhere else + +const schema = { + id: { + type: "objectID", + ObjectID // passing the ObjectID class + } } +const check = v.compile(schema); -v.validate({ mac: "01:C8:95:4B:65:FE" }, schema); // Valid -v.validate({ mac: "01:c8:95:4b:65:fe", schema); // Valid -v.validate({ mac: "01C8.954B.65FE" }, schema); // Valid -v.validate({ mac: "01c8.954b.65fe", schema); // Valid -v.validate({ mac: "01-C8-95-4B-65-FE" }, schema); // Valid -v.validate({ mac: "01-c8-95-4b-65-fe" }, schema); // Valid -v.validate({ mac: "01C8954B65FE" }, schema); // Fail +check({ id: "5f082780b00cc7401fb8e8fc" }) // ok +check({ id: new ObjectID() }) // ok +check({ id: "5f082780b00cc7401fb8e8" }) // Error ``` -## `luhn` -This is an Luhn validator. -[Luhn algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm) checksum -Credit Card numbers, IMEI numbers, National Provider Identifier numbers and others +**Pro tip:** By using defaults props for objectID rule, No longer needed to pass `ObjectID` class in validation schema: ```js -let schema = { - cc: { type: "luhn" } -} +const { ObjectID } = require("mongodb") // or anywhere else -v.validate({ cc: "452373989901198" }, schema); // Valid -v.validate({ cc: 452373989901198 }, schema); // Valid -v.validate({ cc: "4523-739-8990-1198" }, schema); // Valid -v.validate({ cc: "452373989901199" }, schema); // Fail +const v = new Validator({ + defaults: { + objectID: { + ObjectID + } + } +}) + +const schema = { + id: "objectID" +} ``` +### Properties +Property | Default | Description +-------- | -------- | ----------- +`convert` | `false` | If `true`, the validator converts ObjectID HexString representation to ObjectID `instance`, if `hexString` the validator converts to HexString + # Custom validator You can also create your custom validator. ```js -let v = new Validator({ +const v = new Validator({ messages: { // Register our new error message text evenNumber: "The '{field}' field must be an even number! Actual: {actual}" @@ -532,22 +1224,27 @@ let v = new Validator({ }); // Register a custom 'even' validator -v.add("even", value => { - if (value % 2 != 0) - return v.makeError("evenNumber", null, value); - - return true; +v.add("even", function({ schema, messages }, path, context) { + return { + source: ` + if (value % 2 != 0) + ${this.makeError({ type: "evenNumber", actual: "value", messages })} + + return value; + ` + }; }); const schema = { name: { type: "string", min: 3, max: 255 }, age: { type: "even" } }; +const check = v.compile(schema); -console.log(v.validate({ name: "John", age: 20 }, schema)); +console.log(check({ name: "John", age: 20 }, schema)); // Returns: true -console.log(v.validate({ name: "John", age: 19 }, schema)); +console.log(check({ name: "John", age: 19 }, schema)); /* Returns an array with errors: [{ type: 'evenNumber', @@ -561,39 +1258,238 @@ console.log(v.validate({ name: "John", age: 19 }, schema)); Or you can use the `custom` type with an inline checker function: ```js +const v = new Validator({ + useNewCustomCheckerFunction: true, // using new version + messages: { + // Register our new error message text + weightMin: "The weight must be greater than {expected}! Actual: {actual}" + } +}); + +const schema = { + name: { type: "string", min: 3, max: 255 }, + weight: { + type: "custom", + minWeight: 10, + check(value, errors, schema) { + if (value < minWeight) errors.push({ type: "weightMin", expected: schema.minWeight, actual: value }); + if (value > 100) value = 100 + return value + } + } +}; +const check = v.compile(schema); + +console.log(check({ name: "John", weight: 50 }, schema)); +// Returns: true + +console.log(check({ name: "John", weight: 8 }, schema)); +/* Returns an array with errors: + [{ + type: 'weightMin', + expected: 10, + actual: 8, + field: 'weight', + message: 'The weight must be greater than 10! Actual: 8' + }] +*/ +const o = { name: "John", weight: 110 } +console.log(check(o, schema)); +/* Returns: true + o.weight is 100 +*/ +``` +>Please note: the custom function must return the `value`. It means you can also sanitize it. + +## Custom validation for built-in rules +You can define a `custom` function in the schema for built-in rules. With it you can extend any built-in rules. + +```js +const v = new Validator({ + useNewCustomCheckerFunction: true, // using new version + messages: { + // Register our new error message text + phoneNumber: "The phone number must be started with '+'!" + } +}); + +const schema = { + name: { type: "string", min: 3, max: 255 }, + phone: { type: "string", length: 15, custom: (v, errors) => { + if (!v.startsWith("+")) errors.push({ type: "phoneNumber" }) + return v.replace(/[^\d+]/g, ""); // Sanitize: remove all special chars except numbers + } + } +}; +const check = v.compile(schema); + + +console.log(check({ name: "John", phone: "+36-70-123-4567" })); +// Returns: true + +console.log(check({ name: "John", phone: "36-70-123-4567" })); +/* Returns an array with errors: + [{ + message: "The phone number must be started with '+'!", + field: 'phone', + type: 'phoneNumber' + }] +*/ +``` + +>Please note: the custom function must return the `value`. It means you can also sanitize it. + +### Chaining custom functions and global definitions +You can define the `custom` property as an array of functions, allowing you to chain various validation logics. + +Additionally, you can define custom functions globally, making them reusable. +```js + let v = new Validator({ + debug: true, + useNewCustomCheckerFunction: true, messages: { // Register our new error message text - weightMin: "The weight must be greater than {expected}! Actual: {actual}" + evenNumber: "The '{field}' field must be an even number! Actual: {actual}", + realNumber: "The '{field}' field must be a real number! Actual: {actual}", + notPermitNumber: "The '{field}' cannot have the value {actual}", + compareGt: "The '{field}' field must be greater than {gt}! Actual: {actual}", + compareGte: "The '{field}' field must be greater than or equal to {gte}! Actual: {actual}", + compareLt: "The '{field}' field must be less than {lt}! Actual: {actual}", + compareLte: "The '{field}' field must be less than or equal to {lte}! Actual: {actual}" + }, + customFunctions:{ + even: (value, errors)=>{ + if(value % 2 != 0 ){ + errors.push({ type: "evenNumber", actual: value }); + } + return value; + }, + real: (value, errors)=>{ + if(value <0 ){ + errors.push({ type: "realNumber", actual: value }); + } + return value; + }, + compare: (value, errors, schema)=>{ + if( typeof schema.custom.gt==="number" && value <= schema.custom.gt ){ + errors.push({ type: "compareGt", actual: value, gt: schema.custom.gt }); + } + if( typeof schema.custom.gte==="number" && value < schema.custom.gte ){ + errors.push({ type: "compareGte", actual: value, gte: schema.custom.gte }); + } + if( typeof schema.custom.lt==="number" && value >= schema.custom.lt ){ + errors.push({ type: "compareLt", actual: value, lt: schema.custom.lt }); + } + if( typeof schema.custom.lte==="number" && value > schema.custom.lte ){ + errors.push({ type: "compareLte", actual: value, lte: schema.custom.lte }); + } + return value; + } } }); + + const schema = { - name: { type: "string", min: 3, max: 255 }, - weight: { - type: "custom", - minWeight: 10, - check(value, schema) { - return (value < schema.minWeight) - ? this.makeError("weightMin", schema.minWeight, value) - : true; - } + people:{ + type: "number", + custom: [ + "compare|gte:-100|lt:200", // extended definition with additional parameters - equal to: {type:"compare",gte:-100, lt:200}, + "even", + "real", + function (value, errors){ + if(value === "3" ){ + errors.push({ type: "notPermitNumber", actual: value }); + } + return value; + } + ] } }; -console.log(v.validate({ name: "John", weight: 50 }, schema)); -// Returns: true +console.log(v.validate({people:-200}, schema)); +console.log(v.validate({people:200}, schema)); +console.log(v.validate({people:5}, schema)); +console.log(v.validate({people:-5}, schema)); +console.log(v.validate({people:3}, schema)); -console.log(v.validate({ name: "John", weight: 8 }, schema)); -/* Returns an array with errors: - [{ - type: 'weightMin', - expected: 10, - actual: 8, - field: 'weight', - message: 'The weight must be greater than 10! Actual: 8' - }] -*/ +``` + + + + +## Asynchronous custom validations +You can also use async custom validators. This can be useful if you need to check something in a database or in a remote location. +In this case you should use `async/await` keywords, or return a `Promise` in the custom validator functions. + +>This implementation uses `async/await` keywords. So this feature works only on environments which [supports async/await](https://caniuse.com/async-functions): +> +> - Chrome > 55 +> - Firefox > 52 +> - Edge > 15 +> - NodeJS > 8.x (or 7.6 with harmony) +> - Deno (all versions) + +To enable async mode, you should set `$$async: true` in the root of your schema. + +**Example with custom checker function** +```js +const v = new Validator({ + useNewCustomCheckerFunction: true, // using new version + messages: { + // Register our new error message text + unique: "The username is already exist" + } +}); + +const schema = { + $$async: true, + name: { type: "string" }, + username: { + type: "string", + min: 2, + custom: async (v, errors) => { + // E.g. checking in the DB that the value is unique. + const res = await DB.checkUsername(v); + if (!res) + errors.push({ type: "unique", actual: value }); + + return v; + } + } + // ... +}; + +const check = v.compile(schema); + +const res = await check(user); +console.log("Result:", res); +``` + + +The compiled `check` function contains an `async` property, so you can check if it returns a `Promise` or not. +```js +const check = v.compile(schema); +console.log("Is async?", check.async); +``` + +## Meta information for custom validators +You can pass any extra meta information for the custom validators which is available via `context.meta`. + +```js +const schema = { + name: { type: "string", custom: (value, errors, schema, name, parent, context) => { + // Access to the meta + return context.meta.a; + } }, +}; +const check = v.compile(schema); + +const res = check(obj, { + // Passes meta information + meta: { a: "from-meta" } +}); ``` # Custom error messages (l10n) @@ -608,7 +1504,12 @@ const v = new Validator({ } }); -v.validate({ name: "John" }, { name: { type: "string", min: 6 }}); +const schema = { + name: { type: "string", min: 6 } +} +const check = v.compile(schema); + +check({ name: "John" }); /* Returns: [ { @@ -622,7 +1523,7 @@ v.validate({ name: "John" }, { name: { type: "string", min: 6 }}); */ ``` # Personalised Messages -Sometimes the standard messages are too generic. You can customise messages per validation type per field: +Sometimes the standard messages are too generic. You can customize messages per validation type per field: ```js const Validator = require("fastest-validator"); @@ -645,7 +1546,9 @@ const schema = { } } } -v.validate({ firstname: "John", lastname: 23 }, schema ); +const check = v.compile(schema); + +check({ firstname: "John", lastname: 23 }); /* Returns: [ { @@ -665,40 +1568,81 @@ v.validate({ firstname: "John", lastname: 23 }, schema ); ] */ ``` -## Message types +# Plugins +You can apply plugins: +```js +// Plugin Side +function myPlugin(validator){ + // you can modify validator here + // e.g.: validator.add(...) +} + +// Validator Side +const v = new Validator(); +v.plugin(myPlugin) + +``` + +# Message types Name | Default text ------------------- | ------------- -`required` | The '{field}' field is required! -`string` | The '{field}' field must be a string! -`stringEmpty` | The '{field}' field must not be empty! -`stringMin` | The '{field}' field length must be greater than or equal to {expected} characters long! -`stringMax` | The '{field}' field length must be less than or equal to {expected} characters long! -`stringLength` | The '{field}' field length must be {expected} characters long! -`stringPattern` | The '{field}' field fails to match the required pattern! -`stringContains` | The '{field}' field must contain the '{expected}' text! -`stringEnum` | The '{field}' field does not match any of the allowed values! -`number` | The '{field}' field must be a number! -`numberMin` | The '{field}' field must be greater than or equal to {expected}! -`numberMax` | The '{field}' field must be less than or equal to {expected}! -`numberEqual` | The '{field}' field must be equal with {expected}! -`numberNotEqual` | The '{field}' field can't be equal with {expected}! -`numberInteger` | The '{field}' field must be an integer! -`numberPositive` | The '{field}' field must be a positive number! -`numberNegative` | The '{field}' field must be a negative number! -`array` | The '{field}' field must be an array! -`arrayEmpty` | The '{field}' field must not be an empty array! -`arrayMin` | The '{field}' field must contain at least {expected} items! -`arrayMax` | The '{field}' field must contain less than or equal to {expected} items! -`arrayLength` | The '{field}' field must contain {expected} items! -`arrayContains` | The '{field}' field must contain the '{expected}' item! -`arrayEnum` | The '{field} field value '{expected}' does not match any of the allowed values! -`boolean` | The '{field}' field must be a boolean! -`function` | The '{field}' field must be a function! -`date` | The '{field}' field must be a Date! -`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! +`required` | The '{field}' field is required. +`string` | The '{field}' field must be a string. +`stringEmpty` | The '{field}' field must not be empty. +`stringMin` | The '{field}' field length must be greater than or equal to {expected} characters long. +`stringMax` | The '{field}' field length must be less than or equal to {expected} characters long. +`stringLength` | The '{field}' field length must be {expected} characters long. +`stringPattern` | The '{field}' field fails to match the required pattern. +`stringContains` | The '{field}' field must contain the '{expected}' text. +`stringEnum` | The '{field}' field does not match any of the allowed values. +`stringNumeric` | The '{field}' field must be a numeric string. +`stringAlpha` | The '{field}' field must be an alphabetic string. +`stringAlphanum` | The '{field}' field must be an alphanumeric string. +`stringAlphadash` | The '{field}' field must be an alphadash string. +`stringHex` | The '{field}' field must be a hex string. +`stringSingleLine` | The '{field}' field must be a single line string. +`stringBase64` | The '{field}' field must be a base64 string. +`number` | The '{field}' field must be a number. +`numberMin` | The '{field}' field must be greater than or equal to {expected}. +`numberMax` | The '{field}' field must be less than or equal to {expected}. +`numberEqual` | The '{field}' field must be equal to {expected}. +`numberNotEqual` | The '{field}' field can't be equal to {expected}. +`numberInteger` | The '{field}' field must be an integer. +`numberPositive` | The '{field}' field must be a positive number. +`numberNegative` | The '{field}' field must be a negative number. +`array` | The '{field}' field must be an array. +`arrayEmpty` | The '{field}' field must not be an empty array. +`arrayMin` | The '{field}' field must contain at least {expected} items. +`arrayMax` | The '{field}' field must contain less than or equal to {expected} items. +`arrayLength` | The '{field}' field must contain {expected} items. +`arrayContains` | The '{field}' field must contain the '{expected}' item. +`arrayUnique` | The '{actual}' value in '{field}' field does not unique the '{expected}' values. +`arrayEnum` | The '{actual}' value in '{field}' field does not match any of the '{expected}' values. +`tuple` | The '{field}' field must be an array. +`tupleEmpty` | The '{field}' field must not be an empty array. +`tupleLength` | The '{field}' field must contain {expected} items. +`boolean` | The '{field}' field must be a boolean. +`function` | The '{field}' field must be a function. +`date` | The '{field}' field must be a Date. +`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. +`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. +`url` | The '{field}' field must be a valid URL. +`enumValue` | The '{field}' field value '{expected}' does not match any of the allowed values. +`equalValue` | The '{field}' field value must be equal to '{expected}'. +`equalField` | The '{field}' field value must be equal to '{expected}' field value. +`object` | The '{field}' must be an Object. +`objectStrict` | The object '{field}' contains forbidden keys: '{actual}'. +`objectMinProps` | "The object '{field}' must contain at least {expected} properties. +`objectMaxProps` | "The object '{field}' must contain {expected} properties at most. +`uuid` | The '{field}' field must be a valid UUID. +`uuidVersion` | The '{field}' field must be a valid UUID version provided. +`mac` | The '{field}' field must be a valid MAC address. +`luhn` | The '{field}' field must be a valid checksum luhn. ## Message fields Name | Description @@ -706,58 +1650,74 @@ Name | Description `field` | The field name `expected` | The expected value `actual` | The actual value -`type` | The field type -## Development +# 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 ``` -## Test +# Test ``` npm test ``` -### Coverage report +## Coverage report ``` -----------------|----------|----------|----------|----------|-------------------| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | -----------------|----------|----------|----------|----------|-------------------| -All files | 100 | 100 | 100 | 100 | | +All files | 100 | 97.73 | 100 | 100 | | lib | 100 | 100 | 100 | 100 | | messages.js | 100 | 100 | 100 | 100 | | validator.js | 100 | 100 | 100 | 100 | | lib/helpers | 100 | 100 | 100 | 100 | | deep-extend.js | 100 | 100 | 100 | 100 | | flatten.js | 100 | 100 | 100 | 100 | | - lib/rules | 100 | 100 | 100 | 100 | | + lib/rules | 100 | 96.43 | 100 | 100 | | any.js | 100 | 100 | 100 | 100 | | array.js | 100 | 100 | 100 | 100 | | boolean.js | 100 | 100 | 100 | 100 | | - custom.js | 100 | 100 | 100 | 100 | | + custom.js | 100 | 50 | 100 | 100 | 6 | date.js | 100 | 100 | 100 | 100 | | email.js | 100 | 100 | 100 | 100 | | - enum.js | 100 | 100 | 100 | 100 | | + enum.js | 100 | 50 | 100 | 100 | 6 | + equal.js | 100 | 100 | 100 | 100 | | forbidden.js | 100 | 100 | 100 | 100 | | function.js | 100 | 100 | 100 | 100 | | luhn.js | 100 | 100 | 100 | 100 | | mac.js | 100 | 100 | 100 | 100 | | + multi.js | 100 | 100 | 100 | 100 | | number.js | 100 | 100 | 100 | 100 | | object.js | 100 | 100 | 100 | 100 | | - string.js | 100 | 100 | 100 | 100 | | + string.js | 100 | 95.83 | 100 | 100 | 55,63 | + tuple.js | 100 | 100 | 100 | 100 | | url.js | 100 | 100 | 100 | 100 | | uuid.js | 100 | 100 | 100 | 100 | | -----------------|----------|----------|----------|----------|-------------------| ``` -## Contribution +# Contribution Please send pull requests improving the usage and fixing bugs, improving documentation and providing better examples, or providing some tests, because these things are important. -## License +# License fastest-validator is available under the [MIT license](https://tldrlegal.com/license/mit-license). -## Contact +# Contact -Copyright (C) 2017 Icebob +Copyright (C) 2019 Icebob [](https://github.com/icebob) [](https://twitter.com/Icebobcsi) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..143785b --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,9 @@ +# Security Policy + +## Supported Versions + +Security updates are applied only to the most recent releases. + +## Reporting a Vulnerability + +Please report security issues to security@moleculer.services diff --git a/benchmark/suites/async.js b/benchmark/suites/async.js new file mode 100644 index 0000000..f26ea19 --- /dev/null +++ b/benchmark/suites/async.js @@ -0,0 +1,142 @@ +"use strict"; + +const Benchmarkify = require("benchmarkify"); +const benchmark = new Benchmarkify("Fastest validator benchmark").printHeader(); + +let bench = benchmark.createSuite("Simple object"); + +const Validator = require("../../index"); +const v = new Validator({ async: true }); + +const obj = { + name: "John Doe", + email: "john.doe@company.space", + firstName: "John", + phone: "123-4567", + age: 33 +}; + +const wrongObj = { + name: "John Doe", + email: "john.doe@company.space", + firstName: "John", + phone: "123-4567", + age: 5 +}; + +const schema = { + name: { + type: "string", + min: 4, + max: 25 + }, + email: { type: "email" }, + firstName: { type: "string" }, + phone: { type: "string"}, + age: { + type: "number", + min: 18 + } +}; + +const schema2 = { + name: { + type: "string", + min: 4, + max: 25, + messages: { + string: "Csak szöveges érték", + stringMin: "Túl rövid!", + stringMax: "Túl hosszú" + } + }, + email: { type: "email" }, + firstName: { type: "string" }, + phone: { type: "string"}, + age: { + type: "number", + min: 18 + } +}; + +const schema3 = { + name: { + type: "string", + min: 4, + max: 25, + trim: true + }, + email: { type: "email" }, + firstName: { type: "string", trim: true }, + phone: { type: "string", trim: true }, + age: { + type: "number", + min: 18, + convert: true + } +}; +/* +bench.ref("compile & validate", () => { + const res = v.validate(obj, schema); + if (res !== true) + throw new Error("Validation error!", res); +}); + +bench.add("compile & validate with custom messages", () => { + const res = v.validate(obj, schema2); + if (res !== true) + throw new Error("Validation error!", res); +}); +*/ +const check = v.compile(schema); +const check3 = v.compile(schema3); + +bench.add("validate", async done => { + const res = await check(obj); + if (res !== true) + throw new Error("Validation error!", res); + done(); +}); + +bench.add("validate with sanitizations", async done => { + const res = await check3(obj); + if (res !== true) + throw new Error("Validation error!", res); + done(); +}); + +bench.add("validate with wrong obj", async done => { + const res = check(wrongObj); + if (res === true) + throw new Error("Validation error!", res); + done(); +}); + +bench.run(); + + +/* + +=============================== + Fastest validator benchmark +=============================== + +Platform info: +============== + Windows_NT 10.0.19041 x64 + Node.JS: 12.14.1 + V8: 7.7.299.13-node.16 + Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz × 8 + +Suite: Simple object +√ validate* 1,220,837 rps +√ validate with sanitizations* 1,077,153 rps +√ validate with wrong obj* 679,003 rps + + validate* 0% (1,220,837 rps) (avg: 819ns) + validate with sanitizations* -11.77% (1,077,153 rps) (avg: 928ns) + validate with wrong obj* -44.38% (679,003 rps) (avg: 1μs) +----------------------------------------------------------------------- + + +*/ diff --git a/benchmark/suites/email.js b/benchmark/suites/email.js index 272dfce..0166ff2 100644 --- a/benchmark/suites/email.js +++ b/benchmark/suites/email.js @@ -43,4 +43,26 @@ const obj = { })(); -bench.run(); \ No newline at end of file +bench.run(); + +/* +=============================== + Fastest validator benchmark +=============================== + +Platform info: +============== + Windows_NT 10.0.18363 x64 + Node.JS: 12.14.1 + V8: 7.7.299.13-node.16 + Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz × 8 + +Suite: Email validating methods +√ mode: 'precise' 9,479,300 rps +√ mode: 'basic' 10,351,349 rps + + mode: 'precise' -8.42% (9,479,300 rps) (avg: 105ns) + mode: 'basic' 0% (10,351,349 rps) (avg: 96ns) +----------------------------------------------------------------------- + +*/ diff --git a/benchmark/suites/simple.js b/benchmark/suites/simple.js index 04ff9fe..3038269 100644 --- a/benchmark/suites/simple.js +++ b/benchmark/suites/simple.js @@ -59,6 +59,23 @@ const schema2 = { } }; +const schema3 = { + name: { + type: "string", + min: 4, + max: 25, + trim: true + }, + email: { type: "email" }, + firstName: { type: "string", trim: true }, + phone: { type: "string", trim: true }, + age: { + type: "number", + min: 18, + convert: true + } +}; +/* bench.ref("compile & validate", () => { const res = v.validate(obj, schema); if (res !== true) @@ -70,15 +87,22 @@ bench.add("compile & validate with custom messages", () => { if (res !== true) throw new Error("Validation error!", res); }); - +*/ const check = v.compile(schema); +const check3 = v.compile(schema3); -bench.add("validate with pre-compiled schema", () => { +bench.add("validate", () => { const res = check(obj); if (res !== true) throw new Error("Validation error!", res); }); +bench.add("validate with sanitizations", () => { + const res = check3(obj); + if (res !== true) + throw new Error("Validation error!", res); +}); + bench.add("validate with wrong obj", () => { const res = check(wrongObj); if (res === true) @@ -96,19 +120,19 @@ bench.run(); Platform info: ============== - Windows_NT 6.1.7601 x64 - Node.JS: 8.11.0 - V8: 6.2.414.50 + Windows_NT 10.0.18363 x64 + Node.JS: 12.14.1 + V8: 7.7.299.13-node.16 Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz × 8 Suite: Simple object -√ compile & validate 1,115,239 rps -√ validate with pre-compiled schema 3,986,017 rps -√ validate with wrong obj 704,992 rps +√ validate 8,259,364 rps +√ validate with sanitizations 6,096,325 rps +√ validate with wrong obj 1,466,290 rps - compile & validate (#) 0% (1,115,239 rps) (avg: 896ns) - validate with pre-compiled schema +257.41% (3,986,017 rps) (avg: 250ns) - validate with wrong obj -36.79% (704,992 rps) (avg: 1μs) + validate 0% (8,259,364 rps) (avg: 121ns) + validate with sanitizations -26.19% (6,096,325 rps) (avg: 164ns) + validate with wrong obj -82.25% (1,466,290 rps) (avg: 681ns) ----------------------------------------------------------------------- */ diff --git a/deno-test/index.test.ts b/deno-test/index.test.ts new file mode 100644 index 0000000..41ee8ff --- /dev/null +++ b/deno-test/index.test.ts @@ -0,0 +1,17 @@ +import FastestValidator from "https://esm.sh/fastest-validator"; +import { assert } from "https://deno.land/std/testing/asserts.ts"; + +Deno.test({ + name: "deno basic", + fn: () => { + const v = new FastestValidator(); + const check = v.compile({ + name: "string", + age: "number", + }); + + assert(check({ name: "Erf", age: 18 }) === true); + assert(Array.isArray(check({ name: "Erf" })) === true); + assert(Array.isArray(check({ name: "18" })) === true); + }, +}); diff --git a/examples/async.js b/examples/async.js new file mode 100644 index 0000000..bd63146 --- /dev/null +++ b/examples/async.js @@ -0,0 +1,59 @@ +const Validator = require("../index"); + +const v = new Validator({ + debug: true, + useNewCustomCheckerFunction: true +}); + +// Register a custom 'even' validator +v.add("even", function({ messages }) { + return { + source: ` + if (value % 2 != 0) + ${this.makeError({ type: "evenNumber", actual: "value", messages })} + + await new Promise(resolve => setTimeout(resolve, 1000)); + + return value; + ` + }; +}); +v.addMessage("evenNumber", "The '{field}' field must be an even number! Actual: {actual}"); + +const schema = { + $$async: true, + name: { + type: "string", + min: 4, + max: 25, + custom: async (v, errors, schema, name, parent, context) => { + await new Promise(resolve => setTimeout(resolve, 1000)); + return context.meta.name; + } + }, + + username: { + type: "custom", + custom: async (v) => { + // E.g. checking in the DB that whether is unique. + await new Promise(resolve => setTimeout(resolve, 1000)); + return v.trim(); + } + }, + + age: { + type: "even" + } +}; + +const check = v.compile(schema); +console.log("Is async?", check.async); + +(async function() { + const data = { + name: "John Doe", + username: "johndoe ", + age: 21 + }; + console.log(await check(data, { meta: { name: "Jane Doe" }}), data); +})(); diff --git a/examples/browser-with-rollup/README.md b/examples/browser-with-rollup/README.md deleted file mode 100644 index e78ddf1..0000000 --- a/examples/browser-with-rollup/README.md +++ /dev/null @@ -1,21 +0,0 @@ - -# Fastest Validator Browser with Rollup Example - - -## Installation - -Run `npm i` - -## Usage - -To build using `npm`: - -```javascript -$ npm run build -``` - -To run: - -```javascript -$ npm run start -``` diff --git a/examples/browser-with-rollup/package.json b/examples/browser-with-rollup/package.json deleted file mode 100644 index b718648..0000000 --- a/examples/browser-with-rollup/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "fastest-validator-example", - "devDependencies": { - "rollup": "^0.49.2", - "rollup-plugin-commonjs": "^8.0.2", - "rollup-plugin-node-resolve": "^3.0.0" - }, - "dependencies": { - "fastest-validator": "^0.6.5", - "serve": "^11.2.0" - }, - "scripts": { - "build": "rollup -c", - "dev": "rollup -c -w", - "start": "serve public" - } -} diff --git a/examples/browser-with-rollup/public/index.html b/examples/browser-with-rollup/public/index.html deleted file mode 100644 index b824913..0000000 --- a/examples/browser-with-rollup/public/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - -
- - -