├── .editorconfig ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md └── workflows │ ├── ci.yml │ └── nest.yml ├── .gitignore ├── LICENSE ├── README.md ├── egg.json ├── mod.ts ├── release.sh ├── src ├── interfaces.ts ├── messages.ts ├── rules.ts ├── rules │ ├── date_after.ts │ ├── date_after_or_equal.ts │ ├── date_before.ts │ ├── date_before_or_equal.ts │ ├── date_between.ts │ ├── either.ts │ ├── ends_with.ts │ ├── is_array.ts │ ├── is_bool.ts │ ├── is_date.ts │ ├── is_email.ts │ ├── is_float.ts │ ├── is_in.ts │ ├── is_int.ts │ ├── is_ipv4.ts │ ├── is_ipv6.ts │ ├── is_number.ts │ ├── is_numeric.ts │ ├── is_string.ts │ ├── length_between.ts │ ├── match.ts │ ├── max_length.ts │ ├── max_number.ts │ ├── min_length.ts │ ├── min_number.ts │ ├── not_in.ts │ ├── not_null.ts │ ├── nullable.ts │ ├── number_between.ts │ ├── required.ts │ ├── required_if.ts │ ├── required_unless.ts │ ├── required_when.ts │ ├── starts_with.ts │ ├── validate_array.ts │ └── validate_object.ts ├── types.ts ├── utils.ts └── validate.ts ├── tests ├── deps.ts ├── rules │ ├── date_after.test.ts │ ├── date_after_or_equal.test.ts │ ├── date_before.test.ts │ ├── date_before_or_equal.test.ts │ ├── date_between.test.ts │ ├── either.test.ts │ ├── ends_with.test.ts │ ├── is_array.test.ts │ ├── is_bool.test.ts │ ├── is_date.test.ts │ ├── is_email.test.ts │ ├── is_float.test.ts │ ├── is_in.test.ts │ ├── is_int.test.ts │ ├── is_ipv4.test.ts │ ├── is_ipv6.test.ts │ ├── is_number.test.ts │ ├── is_numeric.test.ts │ ├── is_string.test.ts │ ├── length_between.test.ts │ ├── match.test.ts │ ├── max_length.test.ts │ ├── max_number.test.ts │ ├── min_length.test.ts │ ├── min_number.test.ts │ ├── not_in.test.ts │ ├── not_null.test.ts │ ├── nullable.test.ts │ ├── number_between.test.ts │ ├── required.test.ts │ ├── required_if.test.ts │ ├── required_unless.test.ts │ ├── required_when.test.ts │ ├── starts_with.test.ts │ └── validate_array.test.ts ├── utils.test.ts ├── utils.ts └── validate.test.ts └── validasaur.svg /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{ts,md}] 4 | indent_size = 2 5 | indent_style = space 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [INSERT CONTACT METHOD]. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 121 | 122 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 123 | enforcement ladder](https://github.com/mozilla/diversity). 124 | 125 | [homepage]: https://www.contributor-covenant.org 126 | 127 | For answers to common questions about this code of conduct, see the FAQ at 128 | https://www.contributor-covenant.org/faq. Translations are available at 129 | https://www.contributor-covenant.org/translations. 130 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 4 | 5 | This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. 6 | 7 | The following is a set of guidelines for contributing to Validasaur. 8 | These are just guidelines, not rules, use your best judgment and feel free to 9 | propose changes to this document in a pull request. 10 | 11 | ## Process of issue filing 12 | 13 | Check if there is an existing issue covering your intention. 14 | 15 | 1. If you have the same problem but the outcome is different (or vice versa), add a comment stating the difference. 16 | 2. If you have the exact same problem add a like (:+1:). 17 | 3. Otherwise you can create a new issue. 18 | 19 | ### Bugs 20 | 21 | When facing a bug, give steps to reproduce as well as the error. If you have a hypothesis to why this issue erupted, mention this. If you have already isolated the issue and have found a fix, you can open a PR. 22 | 23 | ### Ideas 24 | 25 | Ideas are welcome, and if there is a good reason and/or many users agree, there is a good chance it will be incorporated. Before making a PR, get feedback from the maintainers and community. 26 | 27 | ### Questions 28 | 29 | If you can't find the answers in one of the open (or closed) issues, you can create a new one. 30 | 31 | ## When creating a PR 32 | 33 | Before pushing commits, go through this checklist: 34 | 35 | * You have run `deno fmt`. 36 | * All tests are running successfully locally (will save you time). 37 | 38 | For a PR to be accepted, the following needs to be applied: 39 | 40 | * Every function added, must have corresponding test. 41 | * Pipeline is green. 42 | * Nothing more than what the PR is supposed to solve is changed (unless discussed and approved). 43 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | fmt: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, macos-latest] 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: denolib/setup-deno@master 15 | with: 16 | deno-version: 1.3.0 17 | 18 | - run: deno --version 19 | - run: deno fmt --check 20 | test: 21 | runs-on: ${{ matrix.os }} 22 | strategy: 23 | matrix: 24 | os: [ubuntu-latest, macos-latest, windows-latest] 25 | 26 | steps: 27 | - uses: actions/checkout@v2 28 | - uses: denolib/setup-deno@master 29 | with: 30 | deno-version: 1.4.0 31 | 32 | - run: deno --version 33 | - run: deno test -A tests 34 | -------------------------------------------------------------------------------- /.github/workflows/nest.yml: -------------------------------------------------------------------------------- 1 | name: nest 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-18.04 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: denolib/setup-deno@master 14 | with: 15 | deno-version: 1.x 16 | - run: deno --version 17 | - name: Install eggs CLI 18 | run: | 19 | deno install -A -f --unstable -n eggs https://x.nest.land/eggs@0.2.1/mod.ts 20 | - name: Adding deno bin PATH 21 | run: | 22 | echo "::add-path::$HOME/.deno/bin" 23 | - name: Linking key 24 | env: 25 | NEST_KEY: ${{ secrets.NEST_KEY }} 26 | run: | 27 | eggs link $NEST_KEY 28 | - name: Publish update 29 | run: | 30 | eggs publish 31 | 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _* 2 | .vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Muhammad Syifa 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Validasaur](https://raw.githubusercontent.com/emsifa/validasaur/master/validasaur.svg)](#) 2 | 3 | [![tag](https://img.shields.io/github/tag/emsifa/validasaur.svg)](https://github.com/emsifa/validasaur) 4 | [![CI](https://github.com/emsifa/validasaur/workflows/ci/badge.svg)](https://github.com/emsifa/validasaur/actions) 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/emsifa/validasaur/blob/master/LICENSE) 6 | [![tag](https://img.shields.io/badge/deno->=1.0.0-green.svg)](https://github.com/denoland/deno) 7 | [![nest badge](https://nest.land/badge.svg)](https://nest.land/package/validasaur) 8 | 9 | Validasaur is Deno validation library slightly inspired by Laravel Validation. 10 | 11 | ## Table of Contents 12 | 13 | - [Table of Contents](#table-of-contents) 14 | - [Examples](#examples) 15 | - [Basic Usage](#basic-usage) 16 | - [Formatting Errors](#formatting-errors) 17 | - [Custom Error Message](#custom-error-message) 18 | - [Validating Array and Object](#validating-array-and-object) 19 | - [Make Your own Simple Rule Validation](#make-your-own-simple-rule-validation) 20 | - [Make More Advanced Rule Validation](#make-more-advanced-rule-validation) 21 | - [Available Rules](#available-rules) 22 | - [`required`](#required) 23 | - [`either(ruleSets: (Rule|Rule[])[], errorCode: string = 'either')`](##eitherrulesets-rulerule-errorcode-string--either) 24 | - [`endsWith(str: string)`](#endswithstr-string) 25 | - [`isArray`](#isarray) 26 | - [`isBool`](#isbool) 27 | - [`isDate`](#isdate) 28 | - [`isEmail`](#isemail) 29 | - [`isFloat`](#isfloat) 30 | - [`isIn(allowedValues: PrimitiveTypes[])`](#isinallowedvalues-primitivetypes) 31 | - [`isInt`](#isint) 32 | - [`isIPv4`](#isipv4) 33 | - [`isIPv6`](#isipv6) 34 | - [`isNumber`](#isnumber) 35 | - [`isNumeric`](#isnumeric) 36 | - [`isString`](#isstring) 37 | - [`lengthBetween(minLength: number, maxLength: number)`](#lengthbetweenminlength-number-maxlength-number) 38 | - [`match(regex: RegExp, trim: boolean = false)`](#matchregex-regexp-trim-boolean--false) 39 | - [`maxLength(minValue: number)`](#maxlengthminvalue-number) 40 | - [`maxNumber(maxValue: number)`](#maxnumbermaxvalue-number) 41 | - [`minLength(minValue: number)`](#minlengthminvalue-number) 42 | - [`minNumber(minValue: number)`](#minnumberminvalue-number) 43 | - [`notIn(disallowedValues: PrimitiveTypes[])`](#notindisallowedvalues-primitivetypes) 44 | - [`notNull`](#notnull) 45 | - [`nullable`](#nullable) 46 | - [`numberBetween(minValue: number, maxValue: number)`](#numberbetweenminvalue-number-maxvalue-number) 47 | - [`requiredIf(field: string, fieldValue: any)`](#requirediffield-string-fieldvalue-any) 48 | - [`requiredUnless(field: string, fieldValue: any)`](#requiredunlessfield-string-fieldvalue-any) 49 | - [`requiredWhen(callback: (value: any, utils: ValidationUtils) => boolean|Promise)`](#requiredwhencallback-value-any-utils-validationutils--booleanpromiseboolean) 50 | - [`startsWith(str: string)`](#startswithstr-string) 51 | - [Contributing](#contributing) 52 | 53 | ## Examples 54 | 55 | #### Basic Usage 56 | 57 | Write your `example.ts` like this: 58 | 59 | ```ts 60 | import { validate, required, isNumber } from "https://deno.land/x/validasaur/mod.ts"; 61 | 62 | const inputs = { 63 | name: "", 64 | age: "20" 65 | }; 66 | 67 | const [ passes, errors ] = await validate(inputs, { 68 | name: required, 69 | age: [required, isNumber] 70 | }); 71 | 72 | console.log({ passes, errors }); 73 | ``` 74 | 75 | Run code above with: 76 | 77 | ```bash 78 | deno run example.ts 79 | ``` 80 | 81 | And this is the result: 82 | 83 | ```json 84 | { 85 | "passes": false, 86 | "errors": { 87 | "name": { 88 | "required": "name is required" 89 | }, 90 | "age": { 91 | "isNumber": "age must be a number" 92 | } 93 | } 94 | } 95 | ``` 96 | 97 | #### Formatting Errors 98 | 99 | If you want a simpler error message, 100 | you can use `flattenMessages` or `firstMessages` to format error messages. 101 | 102 | For example: 103 | 104 | ```ts 105 | import { 106 | validate, 107 | flattenMessages, 108 | firstMessages, 109 | required, 110 | isNumber 111 | } from "https://deno.land/x/validasaur/mod.ts"; 112 | 113 | const inputs = { 114 | name: "", 115 | age: "20" 116 | }; 117 | 118 | const [ passes, errors ] = await validate(inputs, { 119 | name: required, 120 | age: [required, isNumber] 121 | }); 122 | 123 | const firstErrors = firstMessages(errors); 124 | const flattenErrors = flattenMessages(errors); 125 | 126 | // Show the difference 127 | console.log({ 128 | defaultErrors: errors, 129 | firstErrors, 130 | flattenErrors 131 | }); 132 | ``` 133 | 134 | Result: 135 | 136 | ```json 137 | { 138 | "defaultErrors": { 139 | "name": { 140 | "required": "name is required" 141 | }, 142 | "age": { 143 | "isNumber": "age must be a number" 144 | } 145 | }, 146 | "firstErrors": { 147 | "name": "name is required", 148 | "age": "age must be a number" 149 | }, 150 | "flattenErrors": { 151 | "name.required": "name is required", 152 | "age.isNumber": "age must be a number", 153 | "name": "name is required", 154 | "age": "age must be a number" 155 | } 156 | } 157 | ``` 158 | 159 | #### Custom Error Message 160 | 161 | ```ts 162 | import { 163 | validate, 164 | InvalidParams, 165 | required, 166 | isNumber, 167 | isIn, 168 | isString, 169 | } from "https://deno.land/x/validasaur/mod.ts"; 170 | 171 | const inputs = { 172 | name: "", 173 | age: "12", 174 | sex: "unknown", 175 | }; 176 | 177 | const [passes, errors] = await validate(inputs, { 178 | name: required, 179 | age: [required, isNumber], 180 | sex: [required, isString, isIn(["male", "female"])] 181 | }, { 182 | messages: { 183 | "name": "Nama tidak boleh kosong", 184 | "age.required": "Usia tidak boleh kosong", 185 | "age.isNumber": "Usia harus berupa angka", 186 | // Using function 187 | "isIn": (params: InvalidParams): string => { 188 | const allowedValues = params.allowedValues.join("/"); 189 | return `${params.attr} field doesn't allow '${params.value}', it only allows ${allowedValues}`; 190 | }, 191 | // Use this if you want same message for any rule fail 192 | // "age": "Usia tidak valid", 193 | }, 194 | }); 195 | 196 | console.log({ passes, errors }); 197 | ``` 198 | 199 | Result: 200 | 201 | ```json 202 | { 203 | "passes": false, 204 | "errors": { 205 | "name": { 206 | "required": "Nama tidak boleh kosong" 207 | }, 208 | "age": { 209 | "isNumber": "Usia harus berupa angka" 210 | } 211 | } 212 | } 213 | ``` 214 | 215 | #### Validating Array and Object 216 | 217 | ```ts 218 | import { 219 | validate, 220 | flattenMessages, 221 | required, 222 | isNumber, 223 | isString, 224 | validateArray, 225 | validateObject 226 | } from "https://deno.land/x/validasaur/mod.ts"; 227 | 228 | const inputs = { 229 | name: "", 230 | age: "20", 231 | skills: ["PHP", "Node.js", 0, "Deno"], 232 | address: { 233 | street: null, 234 | city: "Jakarta", 235 | country: "Indonesia", 236 | } 237 | }; 238 | 239 | const [ passes, errors ] = await validate(inputs, { 240 | name: required, 241 | age: [required, isNumber], 242 | 243 | // validateArray(required: boolean, rules: Rule[]) 244 | skills: validateArray(true, [isString]), 245 | 246 | // validateObject(required: boolean, rules: ValidationRule) 247 | address: validateObject(true, { 248 | street: required, 249 | city: required, 250 | country: required, 251 | }), 252 | }); 253 | 254 | const flattenErrors = flattenMessages(errors); 255 | 256 | console.log({ passes, flattenErrors }); 257 | ``` 258 | 259 | Result: 260 | 261 | ```json 262 | { 263 | "passes": false, 264 | "flattenErrors": { 265 | "name.required": "name is required", 266 | "age.isNumber": "age must be a number", 267 | "skills.2.isString": "2 must be a string", 268 | "address.street.required": "street is required", 269 | "name": "name is required", 270 | "age": "age must be a number", 271 | "skills.2": "2 must be a string", 272 | "address.street": "street is required" 273 | } 274 | } 275 | ``` 276 | 277 | #### Make Your own Simple Rule Validation 278 | 279 | In this example we will make an `isOdd` rule validation that check odd number. 280 | 281 | First, let's make `is_odd.ts` like this: 282 | 283 | ```ts 284 | import { invalid, Validity } from "https://deno.land/x/validasaur/mod.ts"; 285 | 286 | export function isOdd(value: any): Validity { 287 | if (typeof value !== "number") { 288 | return invalid("isOdd", { value }); 289 | } 290 | 291 | if (value % 2 !== 1) { 292 | return invalid("isOdd", { value }); 293 | } 294 | } 295 | 296 | ``` 297 | 298 | Now, we can use it like this: 299 | 300 | ```ts 301 | import { 302 | validate, 303 | flattenMessages, 304 | firstMessages, 305 | required, 306 | isNumber 307 | } from "https://deno.land/x/validasaur/mod.ts"; 308 | 309 | import { isOdd } from "./is_odd.ts"; 310 | 311 | const inputs = { 312 | number: 20 313 | }; 314 | 315 | const [ passes, errors ] = await validate(inputs, { 316 | number: [required, isNumber, isOdd] 317 | }); 318 | 319 | console.log({ passes, errors }); 320 | ``` 321 | 322 | #### Make More Advanced Rule Validation 323 | 324 | In this example we will make a `unique` rule that check value availability in the database. 325 | This rule accepts `table` and `column` as arguments, then calling database function to check availability based on those arguments. 326 | 327 | First, let's make our `unique.ts`: 328 | 329 | ```ts 330 | import db from "./your_db_service.ts"; 331 | import { invalid, Validity, Rule } from "https://deno.land/x/validasaur/mod.ts"; 332 | 333 | export function unique(table: string, column: string): Rule { 334 | return async function uniqueRule(value: any): Promise { 335 | if (typeof value !== "string" && typeof value !== "number") { 336 | return invalid("unique", { value, table, column }); 337 | } 338 | 339 | const data = await db.findOne(table, { [column]: value }); 340 | if (data !== null) { 341 | return invalid("unique", { value, table, column }); 342 | } 343 | }; 344 | } 345 | 346 | ``` 347 | 348 | Now we can use it like this: 349 | 350 | ```ts 351 | import { 352 | validate, 353 | flattenMessages, 354 | firstMessages, 355 | required, 356 | isEmail 357 | } from "https://deno.land/x/validasaur/mod.ts"; 358 | 359 | import { unique } from "./unique.ts"; 360 | 361 | const inputs = { 362 | email: "emsifa@gmail.com" 363 | }; 364 | 365 | const [ passes, errors ] = await validate(inputs, { 366 | email: [required, isEmail, unique("users", "email")] 367 | }); 368 | 369 | console.log({ passes, errors }); 370 | ``` 371 | 372 | ## Available Rules 373 | 374 | #### `required` 375 | 376 | Value under this field should not be `null`, `undefined`, or an empty string (`""`). 377 | 378 | * Invalid values: `null`, `undefined`, `""` 379 | * Valid values: `"0"`, `[]`, `{}`, `0`, etc. 380 | 381 | > When you don't put `required` on a field, that field will be considered as optional field. Which means when it's value is `undefined`, `null`, or `""`, that field will be considered as valid without checking for next rules. 382 | 383 | #### `either(ruleSets: (Rule|Rule[])[], errorCode: string = 'either')` 384 | 385 | Use this as an `OR` operator. 386 | 387 | For example you may want to accept `ipv4` or `ipv6`, you can use `either` rule like below: 388 | 389 | Example: 390 | 391 | ```ts 392 | const [ passes, errors ] = await validate({ 393 | value1: "1.2.3.4", 394 | value2: "::1", 395 | value3: "not an IP address" 396 | }, { 397 | value1: [required, either([isIPv4, isIPv6])], // valid 398 | value2: [required, either([isIPv4, isIPv6])], // valid 399 | value3: [required, either([isIPv4, isIPv6])], // invalid 400 | }) 401 | ``` 402 | 403 | You may want define `errorCode` to use appropriate custom message: 404 | 405 | ```ts 406 | const [ passes, errors ] = await validate({ 407 | value: "foobarbaz" 408 | }, { 409 | value: [required, either([isIPv4, isIPv6], "ipAddress")], 410 | }, { 411 | messages: { 412 | "ipAddress": ":attr is not valid IP address" 413 | } 414 | }) 415 | ``` 416 | 417 | #### `endsWith(str: string)` 418 | 419 | Value under this field must be a string that ends with given `str`. 420 | 421 | Example: 422 | 423 | ```ts 424 | const [ passes, errors ] = await validate({ 425 | value1: null, 426 | value2: "barfoo", 427 | value3: "foobar" 428 | }, { 429 | value1: endsWith("bar"), // invalid 430 | value2: endsWith("bar"), // invalid 431 | value3: endsWith("bar"), // valid 432 | }) 433 | ``` 434 | 435 | #### `isArray` 436 | 437 | Value under this field must be an array. 438 | 439 | * Invalid values: `""`, `10`, `0.5`, etc. 440 | * Valid values: `[]`, `[1, 2, 3]`, `[{x: 10}, {x: 12}]`, etc. 441 | 442 | #### `isBool` 443 | 444 | Value under this field must be a boolean. 445 | 446 | * Invalid values: `""`, `10`, `0.5`, etc. 447 | * Valid values: `true` and `false`. 448 | 449 | #### `isDate` 450 | 451 | Value under this field must be a string that has length >= 10 and can be parsed by `Date.parse()`. 452 | 453 | * Invalid values: `2020`, `"01-2020-10"`, `"2020-01"`, etc. 454 | * Valid values: `"2020-01-02"`, `"2020-01-02 10:20:30"`, `"2020/01/02"`, etc. 455 | 456 | #### `isEmail` 457 | 458 | Value under this field must be valid email address. 459 | 460 | * Invalid values: `"someone name"`, `123`, `foo.bar.baz`, `foo@bar@baz`, etc. 461 | * Valid values: `"someone@mail.com"`, `"someone@mail.co.id"`, `"someone@[1.2.3.4]"`, etc. 462 | 463 | #### `isFloat` 464 | 465 | Value under this field must be a float number. 466 | 467 | * Invalid values: `"0.1"`, `[]`, `0`, `1`, `123`, etc. 468 | * Valid values: `0.1`, `1.2`, `12.345`, etc. 469 | 470 | #### `isIn(allowedValues: PrimitiveTypes[])` 471 | 472 | Value under this field must be one of allowed values. 473 | 474 | Example: 475 | 476 | ```ts 477 | const [ passes, errors ] = await validate({ 478 | value1: "yes", 479 | value2: "no", 480 | value3: "maybe" 481 | }, { 482 | value1: isIn(["yes", "no"]), // passes 483 | value2: isIn(["yes", "no"]), // passes 484 | value3: isIn(["yes", "no"]), // fail 485 | }) 486 | ``` 487 | 488 | #### `isInt` 489 | 490 | Value under this field must be an integer. 491 | 492 | * Invalid values: `0.5`, `"123"`, etc. 493 | * Valid values: `0`, `123`, etc. 494 | 495 | #### `isIPv4` 496 | 497 | Value under this field must be valid IPv4. 498 | 499 | * Invalid values: `"foo"`, `"a.b.c.d"`, `"1.2.3.256"`, `"1.02.3.4"`, etc. 500 | * Valid values: `"0.0.0.0"`, `"1.2.3.4"`, `"255.255.255.255"`, etc. 501 | 502 | #### `isIPv6` 503 | 504 | Value under this field must be valid IPv6. 505 | 506 | * Invalid values: `"2001:af40:::"`, `"2001:af40:::1234"`, `"2001::af40::1234"`, `"1080:0:0:0:8:800:200C:417G"`, etc. 507 | * Valid values: `"FEDC:BA98:7654:3210:FEDC:BA98:7654:3210"`, `"::1:2:3:4:5:6:7"`, `"::1"`, `"::"`, etc. 508 | 509 | #### `isNumber` 510 | 511 | Value under this field must be a float or an integer. 512 | 513 | * Invalid values: `"1"`, `"1.5"`, etc. 514 | * Valid values: `1`, `1.5`, etc. 515 | 516 | #### `isNumeric` 517 | 518 | Same as `asNumber`, but it allows numeric string. 519 | 520 | * Invalid values: `"1.0abc"`, `"x.1"`, etc. 521 | * Valid values: `1`, `1.5`, `"2"`, `"2.5"`, etc. 522 | 523 | #### `isString` 524 | 525 | Value under this field must be a string. 526 | 527 | * Invalid values: `1`, `1.5`, etc. 528 | * Valid values: `"1"`, `"1.5"`, `"foo"`, etc. 529 | 530 | #### `lengthBetween(minLength: number, maxLength: number)` 531 | 532 | Value under this field must be a string that has char length between `minLength` and `maxLength`. 533 | 534 | Example: 535 | 536 | ```ts 537 | const [ passes, errors ] = await validate({ 538 | value1: 'foo', 539 | value2: 'foobar', 540 | value3: 'fo', 541 | value4: 'foobars', 542 | }, { 543 | value1: lengthBetween(3, 6), // passes 544 | value2: lengthBetween(3, 6), // passes 545 | value3: lengthBetween(3, 6), // fail 546 | value4: lengthBetween(3, 6), // fail 547 | }) 548 | ``` 549 | 550 | #### `match(regex: RegExp, trim: boolean = false)` 551 | 552 | Value under this field must be a string that match with given `regex`. 553 | 554 | ```ts 555 | const [ passes, errors ] = await validate({ 556 | value1: 'foo$', 557 | value2: '$foo', 558 | value3: 'foo1', 559 | value4: 'foo2', 560 | value5: ' foo3', 561 | value6: ' foo4', 562 | }, { 563 | value1: match(/^[a-z0-9]{4}$/), // fail 564 | value2: match(/^[a-z0-9]{4}$/), // fail 565 | value3: match(/^[a-z0-9]{4}$/), // passes 566 | value4: match(/^[a-z0-9]{4}$/), // passes 567 | value5: match(/^[a-z0-9]{4}$/), // fail 568 | value6: match(/^[a-z0-9]{4}$/, true), // passes after trim 569 | }) 570 | ``` 571 | 572 | #### `maxLength(minValue: number)` 573 | 574 | Value under this field must be a string that has char length lower or equals `maxValue`. 575 | 576 | Example: 577 | 578 | ```ts 579 | const [ passes, errors ] = await validate({ 580 | value1: 'foobarbaz', 581 | value2: 'foobar', 582 | }, { 583 | value1: maxLength(6), // fail 584 | value2: maxLength(6), // passes 585 | }) 586 | ``` 587 | 588 | #### `maxNumber(maxValue: number)` 589 | 590 | Value under this field should be a number that is not higher than `maxValue`. 591 | 592 | Example: 593 | 594 | ```ts 595 | const [ passes, errors ] = await validate({ 596 | value1: 6, 597 | value2: 5.01, 598 | value3: 5, 599 | value4: 4 600 | }, { 601 | value1: maxNumber(5), // fail 602 | value2: maxNumber(5), // fail 603 | value3: maxNumber(5), // passes 604 | value4: maxNumber(5), // passes 605 | }) 606 | ``` 607 | 608 | #### `minLength(minValue: number)` 609 | 610 | Value under this field must be a string that has char length higher or equals `minValue`. 611 | 612 | Example: 613 | 614 | ```ts 615 | const [ passes, errors ] = await validate({ 616 | value1: 'foo', 617 | value2: 'foobar', 618 | }, { 619 | value1: minLength(6), // fail 620 | value2: minLength(6), // passes 621 | }) 622 | ``` 623 | 624 | #### `minNumber(minValue: number)` 625 | 626 | Value under this field should be a number that is not lower than `minValue`. 627 | 628 | Example: 629 | 630 | ```ts 631 | const [ passes, errors ] = await validate({ 632 | value1: 1, 633 | value2: 4.99, 634 | value3: 5, 635 | value4: 5.01, 636 | }, { 637 | value1: minNumber(5), // fail 638 | value2: minNumber(5), // fail 639 | value3: minNumber(5), // passes 640 | value4: minNumber(5), // passes 641 | }) 642 | ``` 643 | 644 | #### `notIn(disallowedValues: PrimitiveTypes[])` 645 | 646 | Value under this field must not be one of disallowed values. 647 | 648 | Example: 649 | 650 | ```ts 651 | const [ passes, errors ] = await validate({ 652 | value1: "yes", 653 | value2: "no", 654 | value3: "maybe" 655 | }, { 656 | value1: notIn(["yes", "no"]), // fail 657 | value2: notIn(["yes", "no"]), // fail 658 | value3: notIn(["yes", "no"]), // passes 659 | }) 660 | ``` 661 | 662 | #### `notNull` 663 | 664 | Value under this field must not be `null`. 665 | 666 | #### `nullable` 667 | 668 | In case you need a `required` field that accept `null` value, 669 | you can put `nullable` after `required` field. So when the value is `null`, validator will consider your value as valid without checking for next rules. 670 | 671 | Example: 672 | 673 | ```ts 674 | const [ passes, errors ] = await validate({ 675 | value1: null, 676 | value2: null, 677 | value3: "3", 678 | value4: 4, 679 | }, { 680 | value1: [required, isNumber], // failed on required 681 | value2: [required, nullable, isNumber], // passes 682 | value3: [required, nullable, isNumber], // failed on isNumber 683 | value4: [required, nullable, isNumber], // passes 684 | }) 685 | ``` 686 | 687 | #### `numberBetween(minValue: number, maxValue: number)` 688 | 689 | Value under this field must be a number between `minValue` and `maxValue`. 690 | 691 | ```ts 692 | const [ passes, errors ] = await validate({ 693 | value1: 5, 694 | value2: 10, 695 | value3: 4.99, 696 | value4: 10.01, 697 | }, { 698 | value1: numberBetween(5, 10), // passes 699 | value2: numberBetween(5, 10), // passes 700 | value3: numberBetween(5, 10), // fail 701 | value4: numberBetween(5, 10), // fail 702 | }) 703 | ``` 704 | 705 | #### `requiredIf(field: string, fieldValue: any)` 706 | 707 | Field within this rule will be required if given `field` match `fieldValue`. 708 | 709 | ```ts 710 | const [ passes, errors ] = await validate({ 711 | value1: null, 712 | value2: "2", 713 | value3: null, 714 | otherField: 1, 715 | }, { 716 | value1: [requiredIf('otherField', 1), isNumber], // failed at required 717 | value2: [requiredIf('otherField', 1), isNumber], // failed at isNumber 718 | value3: [requiredIf('otherField', 0), isNumber], // passes because value3 becomes optional 719 | }) 720 | ``` 721 | 722 | #### `requiredUnless(field: string, fieldValue: any)` 723 | 724 | Field within this rule will be required if given `field` doesn't match with `fieldValue`. 725 | 726 | ```ts 727 | const [ passes, errors ] = await validate({ 728 | value1: null, 729 | value2: "2", 730 | value3: null, 731 | otherField: 1, 732 | }, { 733 | value1: [requiredUnless('otherField', 9), isNumber], // failed at required 734 | value2: [requiredUnless('otherField', 6), isNumber], // failed at isNumber 735 | value3: [requiredUnless('otherField', 1), isNumber], // passes because value3 becomes optional 736 | }) 737 | ``` 738 | 739 | #### `requiredWhen(callback: (value: any, utils: ValidationUtils) => boolean|Promise)` 740 | 741 | Field within this rule will be required if callback returns `true`. 742 | 743 | ```ts 744 | const [ passes, errors ] = await validate({ 745 | value1: null, 746 | value2: "2", 747 | value3: null, 748 | value4: null, 749 | otherField: 10, 750 | }, { 751 | value1: [requiredWhen(() => true), isNumber], // failed at required 752 | value2: [requiredWhen(() => true), isNumber], // failed at isNumber 753 | value3: [requiredWhen(() => false), isNumber], // passes because value3 becomes optional 754 | value4: [ 755 | requiredWhen((_, { getValue }): boolean => { 756 | const x = getValue('otherField'); 757 | return typeof x !== "number" || x % 2 === 0; 758 | }), 759 | isNumber 760 | ], // this will fail because value4 is null and otherField|x is 10 where 10 % 2 === 0 761 | }) 762 | ``` 763 | 764 | #### `startsWith(str: string)` 765 | 766 | Value under this field must be a string that starts with given `str`. 767 | 768 | Example: 769 | 770 | ```ts 771 | const [ passes, errors ] = await validate({ 772 | value1: null, 773 | value2: "barfoo", 774 | value3: "foobar" 775 | }, { 776 | value1: startsWith("foo"), // invalid 777 | value2: startsWith("foo"), // invalid 778 | value3: startsWith("foo"), // valid 779 | }) 780 | ``` 781 | 782 | # Contributing 783 | 784 | All contributions are welcome, make sure to read the [contribution guideline](./.github/CONTRIBUTING.md). 785 | 786 | -------------------------------------------------------------------------------- /egg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "validasaur", 3 | "description": "Deno validation library that slightly inspired by Laravel Validation", 4 | "version": "v0.15.0", 5 | "stable": false, 6 | "repository": "https://github.com/emsifa/validasaur", 7 | "files": [ 8 | "./**/*", 9 | "./README.md" 10 | ] 11 | } -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | export * from "./src/types.ts"; 2 | export * from "./src/interfaces.ts"; 3 | export * from "./src/utils.ts"; 4 | export * from "./src/messages.ts"; 5 | export * from "./src/validate.ts"; 6 | export * from "./src/rules.ts"; 7 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Replace version name in egg.json 5 | sed -i "s/\"version\": \".*\"/\"version\": \"$1\"/" egg.json 6 | 7 | git add egg.json 8 | git commit -m $1 9 | git tag $1 -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | import type { Rule, MessageFunction, Validity } from "./types.ts"; 2 | 3 | export interface ValidationRules { 4 | [key: string]: Rule | Rule[]; 5 | } 6 | 7 | export interface InvalidParams { 8 | [key: string]: any; 9 | } 10 | 11 | export interface InvalidPayload { 12 | rule: string; 13 | params: InvalidParams; 14 | implicit: boolean; 15 | } 16 | 17 | export interface OptionalValidity extends InvalidPayload { 18 | noContext: boolean; 19 | } 20 | 21 | export interface InputData { 22 | [key: string]: any; 23 | } 24 | 25 | export interface ValidationMessages { 26 | [key: string]: MessageFunction | string; 27 | } 28 | 29 | export interface ValidationUtils { 30 | getValue(key: string): any; 31 | hasValue(key: string): boolean; 32 | } 33 | 34 | export interface ValidationAttributes { 35 | [key: string]: string; 36 | } 37 | 38 | export interface ValidationErrors { 39 | [key: string]: { 40 | [ruleName: string]: string | ValidationErrors; 41 | }; 42 | } 43 | 44 | export interface RawValidationResult { 45 | [key: string]: InvalidPayload[] | RawValidationResult | RawValidationResult[]; 46 | } 47 | 48 | export interface ValidationOptions { 49 | messages?: ValidationMessages; 50 | attributes?: ValidationAttributes; 51 | } 52 | 53 | export interface FirstMessages { 54 | [key: string]: string | FirstMessages; 55 | } 56 | 57 | export interface FlattenMessages { 58 | [key: string]: string; 59 | } 60 | -------------------------------------------------------------------------------- /src/messages.ts: -------------------------------------------------------------------------------- 1 | import type { ValidationMessages } from "./interfaces.ts"; 2 | 3 | export const defaultMessages: ValidationMessages = { 4 | "fileExists:pathCheck": "file :value doesn't exists", 5 | "fileExists:stringCheck": "file path must be a string", 6 | isArray: ":attr must be an array", 7 | isBool: ":attr must be a boolean", 8 | isEmail: ":attr is not a valid email address", 9 | isFloat: ":attr must be a float number", 10 | isIn: ":value is not allowed", 11 | isInt: ":attr must be an integer", 12 | isNumber: ":attr must be a number", 13 | isNumeric: ":attr must be numeric", 14 | isString: ":attr must be a string", 15 | lengthBetween: 16 | ":attr characters length must be between :minLength-:maxLength", 17 | match: ":attr format is incorrect", 18 | maxLength: ":attr cannot be higher than :maxValue characters", 19 | maxNumber: ":attr cannot be higher than :maxValue", 20 | minLength: ":attr cannot be lower than :minValue characters", 21 | minNumber: ":attr cannot be lower than :minValue", 22 | notIn: ":value is not allowed", 23 | notNull: ":value cannot be null", 24 | numberBetween: ":value must be between :minValue - :maxValue", 25 | required: ":attr is required", 26 | default: ":attr is invalid", 27 | }; 28 | -------------------------------------------------------------------------------- /src/rules.ts: -------------------------------------------------------------------------------- 1 | export * from "./rules/date_after_or_equal.ts"; 2 | export * from "./rules/date_after.ts"; 3 | export * from "./rules/date_before_or_equal.ts"; 4 | export * from "./rules/date_before.ts"; 5 | export * from "./rules/date_between.ts"; 6 | export * from "./rules/either.ts"; 7 | export * from "./rules/ends_with.ts"; 8 | export * from "./rules/is_array.ts"; 9 | export * from "./rules/is_bool.ts"; 10 | export * from "./rules/is_float.ts"; 11 | export * from "./rules/is_in.ts"; 12 | export * from "./rules/is_int.ts"; 13 | export * from "./rules/is_ipv4.ts"; 14 | export * from "./rules/is_ipv6.ts"; 15 | export * from "./rules/is_number.ts"; 16 | export * from "./rules/is_numeric.ts"; 17 | export * from "./rules/is_date.ts"; 18 | export * from "./rules/is_email.ts"; 19 | export * from "./rules/is_string.ts"; 20 | export * from "./rules/length_between.ts"; 21 | export * from "./rules/max_length.ts"; 22 | export * from "./rules/max_number.ts"; 23 | export * from "./rules/min_length.ts"; 24 | export * from "./rules/min_number.ts"; 25 | export * from "./rules/not_in.ts"; 26 | export * from "./rules/not_null.ts"; 27 | export * from "./rules/nullable.ts"; 28 | export * from "./rules/number_between.ts"; 29 | export * from "./rules/match.ts"; 30 | export * from "./rules/required_if.ts"; 31 | export * from "./rules/required_unless.ts"; 32 | export * from "./rules/required_when.ts"; 33 | export * from "./rules/required.ts"; 34 | export * from "./rules/starts_with.ts"; 35 | export * from "./rules/validate_array.ts"; 36 | export * from "./rules/validate_object.ts"; 37 | -------------------------------------------------------------------------------- /src/rules/date_after.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import { clearTimes, dateChecks } from "../utils.ts"; 3 | 4 | export function dateAfter(date: Date): Rule { 5 | return function dateAfterRule(value: any): Validity { 6 | return dateChecks(value, "dateAfter", { date }, (input: Date): boolean => { 7 | return clearTimes(input).getTime() > clearTimes(date).getTime(); 8 | }); 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /src/rules/date_after_or_equal.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import { clearTimes, dateChecks } from "../utils.ts"; 3 | 4 | export function dateAfterOrEqual(date: Date): Rule { 5 | return function dateAfterOrEqualRule(value: any): Validity { 6 | return dateChecks( 7 | value, 8 | "dateAfterOrEqual", 9 | { date }, 10 | (input: Date): boolean => { 11 | return clearTimes(input).getTime() >= clearTimes(date).getTime(); 12 | }, 13 | ); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/rules/date_before.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import { clearTimes, dateChecks } from "../utils.ts"; 3 | 4 | export function dateBefore(date: Date): Rule { 5 | return function dateBeforeRule(value: any): Validity { 6 | return dateChecks(value, "dateBefore", { date }, (input: Date): boolean => { 7 | return clearTimes(input).getTime() < clearTimes(date).getTime(); 8 | }); 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /src/rules/date_before_or_equal.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import { clearTimes, dateChecks } from "../utils.ts"; 3 | 4 | export function dateBeforeOrEqual(date: Date): Rule { 5 | return function dateBeforeOrEqualRule(value: any): Validity { 6 | return dateChecks( 7 | value, 8 | "dateBeforeOrEqual", 9 | { date }, 10 | (input: Date): boolean => { 11 | return clearTimes(input).getTime() <= clearTimes(date).getTime(); 12 | }, 13 | ); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/rules/date_between.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import { clearTimes, dateChecks } from "../utils.ts"; 3 | 4 | export function dateBetween(minDate: Date, maxDate: Date): Rule { 5 | return function dateBetweenRule(value: any): Validity { 6 | return dateChecks( 7 | value, 8 | "dateBetween", 9 | { minDate, maxDate }, 10 | (input: Date): boolean => { 11 | const inputDateTime = clearTimes(input).getTime(); 12 | const minDateTime = clearTimes(minDate).getTime(); 13 | const maxDateTime = clearTimes(maxDate).getTime(); 14 | 15 | return inputDateTime >= minDateTime && inputDateTime <= maxDateTime; 16 | }, 17 | ); 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/rules/either.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import type { ValidationUtils } from "../interfaces.ts"; 3 | import { invalid } from "../utils.ts"; 4 | import { validateValue } from "../validate.ts"; 5 | 6 | export function either( 7 | ruleSets: (Rule | Rule[])[], 8 | errorCode: string = "either", 9 | ): Rule { 10 | return async function eitherRule( 11 | value: any, 12 | utils: ValidationUtils, 13 | ): Promise { 14 | for (const ruleSet of ruleSets) { 15 | const errs = await validateValue( 16 | value, 17 | ruleSet instanceof Array ? ruleSet : [ruleSet], 18 | utils, 19 | ); 20 | 21 | if (errs.length === 0) { 22 | return undefined; 23 | } 24 | } 25 | 26 | return invalid(errorCode, { value }); 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/rules/ends_with.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function endsWith(str: string): Rule { 5 | return function endsWithRule(value: any): Validity { 6 | if (typeof value !== "string") { 7 | return invalid("endsWith", { value, str }, false); 8 | } 9 | 10 | if (value.endsWith(str) === false) { 11 | return invalid("endsWith", { value, str }, false); 12 | } 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/rules/is_array.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function isArray(value: any): Validity { 5 | if (false === value instanceof Array) { 6 | return invalid("isArray", { value }); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/rules/is_bool.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function isBool(value: any): Validity { 5 | if (typeof value !== "boolean") { 6 | return invalid("isBool", { value }); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/rules/is_date.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../types.ts"; 2 | import { dateChecks } from "../utils.ts"; 3 | 4 | export function isDate(value: any): Validity { 5 | return dateChecks(value, "isDate"); 6 | } 7 | -------------------------------------------------------------------------------- /src/rules/is_email.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function isEmail(value: any): Validity { 5 | // https://stackoverflow.com/a/46181 6 | const regex = 7 | /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 8 | if (typeof value !== "string" || !regex.test(value.toLowerCase())) { 9 | return invalid("isEmail", { value }); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/rules/is_float.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function isFloat(value: any): Validity { 5 | if (typeof value !== "number" || value % 1 === 0) { 6 | return invalid("isFloat", { value }); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/rules/is_in.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule, PrimitiveTypes } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function isIn(allowedValues: PrimitiveTypes[]): Rule { 5 | return function isInRule(value: any): Validity { 6 | if (allowedValues.indexOf(value) < 0) { 7 | return invalid("isIn", { value, allowedValues }); 8 | } 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /src/rules/is_int.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | import { isNumber } from "./is_number.ts"; 4 | 5 | export function isInt(value: any): Validity { 6 | if (typeof value !== "number" || value % 1 !== 0) { 7 | return invalid("isInt", { value }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/rules/is_ipv4.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function isIPv4(value: any): Validity { 5 | if (typeof value !== "string") { 6 | return invalid("isIPv4", { value }); 7 | } 8 | 9 | if (!value.match(/^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/)) { 10 | return invalid("isIPv4", { value }); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/rules/is_ipv6.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function isIPv6(value: any): Validity { 5 | const invalidResult = invalid("isIPv6", { value }); 6 | 7 | if (typeof value !== "string") { 8 | return invalidResult; 9 | } 10 | 11 | const segments = value.split(":"); 12 | 13 | const invalidSegments = segments.filter( 14 | (s) => !s.match(/^(|[0-9a-f]{1,4})$/i), 15 | ); 16 | if (invalidSegments.length > 0) { 17 | return invalidResult; 18 | } 19 | 20 | const emptySegmentsCount = segments.filter((s) => s === "").length; 21 | const startsWithLeadingZeros = value.match(/^::/) ? true : false; 22 | const endsWithLeadingZeros = value.match(/::$/) ? true : false; 23 | 24 | const maxSegments = startsWithLeadingZeros || endsWithLeadingZeros ? 9 : 8; 25 | 26 | let maxEmptySegments = 1; 27 | if (startsWithLeadingZeros) { 28 | maxEmptySegments += 1; 29 | } 30 | if (endsWithLeadingZeros) { 31 | maxEmptySegments += 1; 32 | } 33 | 34 | if (segments.length > maxSegments || emptySegmentsCount > maxEmptySegments) { 35 | return invalidResult; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/rules/is_number.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function isNumber(value: any): Validity { 5 | if (typeof value !== "number") { 6 | return invalid("isNumber", { value }); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/rules/is_numeric.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function isNumeric(value: any): Validity { 5 | if (typeof value !== "string" && typeof value !== "number") { 6 | return invalid("isNumeric", { value }); 7 | } 8 | 9 | if (typeof value === "string" && !(value as string).match(/\d+(\.\d+)?/)) { 10 | return invalid("isNumeric", { value }); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/rules/is_string.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function isString(value: any): Validity { 5 | if (typeof value !== "string") { 6 | return invalid("isString", { value }); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/rules/length_between.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function lengthBetween(minLength: number, maxLength: number): Rule { 5 | return function lengthBetweenRule(value: any): Validity { 6 | if (typeof value !== "string") { 7 | return invalid("lengthBetween", { value, minLength, maxLength }, false); 8 | } 9 | 10 | if (value.length < minLength || value.length > maxLength) { 11 | return invalid("lengthBetween", { value, minLength, maxLength }, false); 12 | } 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/rules/match.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function match(regex: RegExp, trim: boolean = false): Rule { 5 | return function matchRule(value: any): Validity { 6 | if (typeof value !== "string") { 7 | return invalid("match", { value, regex }, false); 8 | } 9 | 10 | if (trim) { 11 | value = value.trim(); 12 | } 13 | 14 | if (!value.match(regex)) { 15 | return invalid("match", { value, regex }, false); 16 | } 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/rules/max_length.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function maxLength(maxValue: number): Rule { 5 | return function maxLengthRule(value: any): Validity { 6 | if (typeof value !== "string") { 7 | return invalid("maxLength", { value, maxValue }, false); 8 | } 9 | 10 | if (value.length > maxValue) { 11 | return invalid("maxLength", { value, maxValue }, false); 12 | } 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/rules/max_number.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function maxNumber(maxValue: number): Rule { 5 | return function maxRule(value: any): Validity { 6 | if (typeof value !== "number" || value > maxValue) { 7 | return invalid("maxNumber", { value, maxValue }); 8 | } 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /src/rules/min_length.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function minLength(minValue: number): Rule { 5 | return function minLengthRule(value: any): Validity { 6 | if (typeof value !== "string") { 7 | return invalid("minLength", { value, minValue }, false); 8 | } 9 | 10 | if (value.length < minValue) { 11 | return invalid("minLength", { value, minValue }, false); 12 | } 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/rules/min_number.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function minNumber(minValue: number): Rule { 5 | return function minRule(value: any): Validity { 6 | if (typeof value !== "number" || value < minValue) { 7 | return invalid("minNumber", { value, minValue }); 8 | } 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /src/rules/not_in.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule, PrimitiveTypes } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function notIn(disallowedValues: PrimitiveTypes[]): Rule { 5 | return function notInRule(value: any): Validity { 6 | return disallowedValues.indexOf(value) > -1 7 | ? invalid("notIn", { value, disallowedValues }) 8 | : undefined; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /src/rules/not_null.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function notNull(value: any): Validity { 5 | return value === null ? invalid("notNull", { value }, true) : undefined; 6 | } 7 | -------------------------------------------------------------------------------- /src/rules/nullable.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function nullable(value: any): Validity { 5 | if (typeof value === "undefined") { 6 | return invalid("nullable", { value }, true); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/rules/number_between.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function numberBetween(minValue: number, maxValue: number): Rule { 5 | return function maxRule(value: any): Validity { 6 | if (typeof value !== "number" || value < minValue || value > maxValue) { 7 | return invalid("numberBetween", { value, maxValue, minValue }); 8 | } 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /src/rules/required.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../types.ts"; 2 | import { invalid, isOptionalValue } from "../utils.ts"; 3 | 4 | export function required(value: any): Validity { 5 | return isOptionalValue(value) 6 | ? invalid("required", { value }, true) 7 | : undefined; 8 | } 9 | -------------------------------------------------------------------------------- /src/rules/required_if.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import { required } from "./required.ts"; 3 | import { optionallyValid } from "../utils.ts"; 4 | 5 | export function requiredIf(field: string, fieldValue: any): Rule { 6 | return function requiredIfRule(value: any, { getValue }): Validity { 7 | const val = getValue(field); 8 | if (val === fieldValue) { 9 | return required(value); 10 | } 11 | return optionallyValid(true); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/rules/required_unless.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import { required } from "./required.ts"; 3 | import { optionallyValid } from "../utils.ts"; 4 | 5 | export function requiredUnless(field: string, fieldValue: any): Rule { 6 | return function requiredUnlessRule(value: any, { getValue }): Validity { 7 | const val = getValue(field); 8 | if (val !== fieldValue) { 9 | return required(value); 10 | } 11 | return optionallyValid(true); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/rules/required_when.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import type { ValidationUtils } from "../interfaces.ts"; 3 | import { required } from "./required.ts"; 4 | import { optionallyValid } from "../utils.ts"; 5 | 6 | export function requiredWhen( 7 | callback: ( 8 | fieldValue: any, 9 | validationUtils: ValidationUtils, 10 | ) => boolean | Promise, 11 | ): Rule { 12 | return async function requiredWhenRule( 13 | value: any, 14 | utils: ValidationUtils, 15 | ): Promise { 16 | const result = callback(value, utils); 17 | const isRequired = result instanceof Promise ? await result : result; 18 | if (isRequired) { 19 | return required(value); 20 | } 21 | return optionallyValid(true); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/rules/starts_with.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import { invalid } from "../utils.ts"; 3 | 4 | export function startsWith(str: string): Rule { 5 | return function startsWithRule(value: any): Validity { 6 | if (typeof value !== "string") { 7 | return invalid("startsWith", { value, str }, false); 8 | } 9 | 10 | if (value.startsWith(str) === false) { 11 | return invalid("startsWith", { value, str }, false); 12 | } 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/rules/validate_array.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import type { RawValidationResult, ValidationUtils } from "../interfaces.ts"; 3 | import { invalid, isOptionalValue } from "../utils.ts"; 4 | import { required } from "./required.ts"; 5 | import { validateValue } from "../validate.ts"; 6 | 7 | export interface ValidateArrayOptions { 8 | minLength?: number; 9 | maxLength?: number; 10 | } 11 | 12 | export function validateArray( 13 | isRequired: boolean, 14 | rules: Rule[], 15 | { minLength, maxLength }: ValidateArrayOptions = { 16 | minLength: 0, 17 | }, 18 | ): Rule[] { 19 | return [ 20 | ...(isRequired ? [required] : []), 21 | async function ruleArray( 22 | value: any, 23 | utils: ValidationUtils, 24 | ): Promise { 25 | if (isRequired === false && isOptionalValue(value)) { 26 | return; 27 | } 28 | 29 | if (!Array.isArray(value)) { 30 | return invalid("validateArray:arrayCheck", { value }, true); 31 | } 32 | 33 | if (typeof minLength === "number" && value.length < minLength) { 34 | return invalid("validateArray:minLengthCheck", { 35 | value, 36 | minLength: minLength, 37 | }); 38 | } 39 | 40 | if (typeof maxLength === "number" && value.length > maxLength) { 41 | return invalid("validateArray:maxLengthCheck", { 42 | value, 43 | maxLength: maxLength, 44 | }); 45 | } 46 | 47 | const errors: RawValidationResult = {}; 48 | for (let i in value) { 49 | const errs = await validateValue(value[i], rules, utils); 50 | if (errs.length) { 51 | errors[i.toString()] = [...errs]; 52 | } 53 | } 54 | 55 | if (Object.keys(errors).length > 0) { 56 | return invalid("validateArray", { value, errors }, true); 57 | } 58 | }, 59 | ]; 60 | } 61 | -------------------------------------------------------------------------------- /src/rules/validate_object.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../types.ts"; 2 | import type { 3 | ValidationRules, 4 | InputData, 5 | ValidationUtils, 6 | } from "../interfaces.ts"; 7 | import { invalid, isOptionalValue } from "../utils.ts"; 8 | import { required } from "./required.ts"; 9 | import { validateData } from "../validate.ts"; 10 | 11 | export function validateObject( 12 | isRequired: boolean, 13 | rules: ValidationRules, 14 | ): Rule[] { 15 | return [ 16 | ...(isRequired ? [required] : []), 17 | async function ruleObject( 18 | value: any, 19 | utils: ValidationUtils, 20 | ): Promise { 21 | if (isRequired === true && isOptionalValue(value)) { 22 | return; 23 | } 24 | 25 | // Make sure value is object and not null 26 | if (typeof value !== "object" || value === null) { 27 | return invalid("validateObject", { value }, true); 28 | } 29 | 30 | const errors = await validateData(value as InputData, rules); 31 | 32 | if (Object.keys(errors).length > 0) { 33 | return invalid("validateObject", { value, errors }, true); 34 | } 35 | }, 36 | ]; 37 | } 38 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ValidationErrors, 3 | InvalidPayload, 4 | ValidationUtils, 5 | InvalidParams, 6 | } from "./interfaces.ts"; 7 | 8 | export type OptionalValue = null | undefined | ""; 9 | 10 | export type Validity = InvalidPayload | undefined; 11 | 12 | export type Rule = ( 13 | value: any, 14 | utils: ValidationUtils, 15 | ) => Validity | Promise; 16 | 17 | export type MessageFunction = ( 18 | params: InvalidParams, 19 | checkType: string, 20 | ) => string; 21 | 22 | export type ValidationResult = [boolean, ValidationErrors]; 23 | 24 | export type PrimitiveTypes = 25 | | null 26 | | boolean 27 | | string 28 | | number 29 | | undefined 30 | | Symbol; 31 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Rule, MessageFunction, Validity } from "./types.ts"; 2 | import type { 3 | InvalidParams, 4 | InvalidPayload, 5 | ValidationErrors, 6 | FirstMessages, 7 | FlattenMessages, 8 | RawValidationResult, 9 | ValidationOptions, 10 | ValidationMessages, 11 | InputData, 12 | ValidationUtils, 13 | OptionalValidity, 14 | } from "./interfaces.ts"; 15 | import { required } from "./rules/required.ts"; 16 | import { nullable } from "./rules/nullable.ts"; 17 | 18 | export function invalid( 19 | rule: string, 20 | params: InvalidParams = {}, 21 | implicit = false, 22 | ): InvalidPayload { 23 | return { rule, params, implicit }; 24 | } 25 | 26 | export function optionallyValid( 27 | noContext: boolean, 28 | rule: string = "", 29 | params: InvalidParams = {}, 30 | implicit: boolean = false, 31 | ): OptionalValidity { 32 | return { noContext, rule, params, implicit }; 33 | } 34 | 35 | export function isNullable(rules: Rule[]): boolean { 36 | return rules.find((rule: Rule) => rule === nullable) ? true : false; 37 | } 38 | 39 | export function isOptional(rules: Rule[]): boolean { 40 | return rules.find((rule: Rule) => rule === required) ? false : true; 41 | } 42 | 43 | export function isOptionalValue(value: any): boolean { 44 | return value === undefined || value === null || value === ""; 45 | } 46 | 47 | export function firstMessages(messages: ValidationErrors): FirstMessages { 48 | const results: FirstMessages = {}; 49 | 50 | for (let key in messages) { 51 | const ruleNames = Object.keys(messages[key]); 52 | const firstRule = ruleNames[0]; 53 | const firstMessage = messages[key][firstRule]; 54 | 55 | if ( 56 | (firstRule === "validateObject" || firstRule === "validateArray") && 57 | typeof firstMessage !== "string" 58 | ) { 59 | results[key] = firstMessages(firstMessage as ValidationErrors); 60 | } else { 61 | results[key] = firstMessage; 62 | } 63 | } 64 | 65 | return results; 66 | } 67 | 68 | export function flattenMessages( 69 | messages: ValidationErrors, 70 | firstMessagesOnly: boolean = false, 71 | ): FlattenMessages { 72 | const flatten = (data: any, prefix: string = ""): FlattenMessages => { 73 | if (typeof data !== "object") { 74 | return {}; 75 | } 76 | 77 | let results: FlattenMessages = {}; 78 | for (let key in data) { 79 | const d = data[key]; 80 | const resKey = `${prefix ? prefix + "." : ""}${key}`.replace( 81 | /\.validate(Array|Object)/g, 82 | "", 83 | ); 84 | if (typeof d === "object" && d !== null) { 85 | results = { ...results, ...flatten(d, resKey) }; 86 | } else { 87 | results[resKey] = d; 88 | } 89 | } 90 | return results; 91 | }; 92 | 93 | const results: FlattenMessages = { 94 | ...(firstMessagesOnly ? {} : flatten(messages)), 95 | ...flatten(firstMessages(messages)), 96 | }; 97 | 98 | return results; 99 | } 100 | 101 | export const resolveErrorMessage = ( 102 | msg: string | MessageFunction, 103 | params: InvalidParams, 104 | attr: string, 105 | checkType?: string, 106 | ): string => { 107 | params.attr = attr; 108 | 109 | if (typeof msg === "function") { 110 | return msg(params, checkType || ""); 111 | } else { 112 | for (let key in params) { 113 | msg = msg.replace(`:${key}`, params[key] as string); 114 | } 115 | 116 | return msg; 117 | } 118 | }; 119 | 120 | export const getCheckType = (rule: string): string => { 121 | const split = rule.split(":"); 122 | split.shift(); 123 | 124 | return split.join(":"); 125 | }; 126 | 127 | export const findBestMessage = ( 128 | messages: ValidationMessages, 129 | key: string, 130 | ruleName: string, 131 | ruleKey: string, 132 | defaultMessage: string | MessageFunction, 133 | ): string | MessageFunction => { 134 | return ( 135 | messages[`${key}.${ruleName}`] || 136 | messages[`${key}.${ruleKey}`] || 137 | messages[key] || 138 | messages[ruleName] || 139 | messages[ruleKey] || 140 | defaultMessage 141 | ); 142 | }; 143 | 144 | export const resolveErrorMessages = ( 145 | rawErrors: RawValidationResult, 146 | { messages, attributes }: ValidationOptions, 147 | ): ValidationErrors => { 148 | const errorMessages: ValidationErrors = {}; 149 | const defaultMessage = (messages || {})["default"] || ":attr is invalid"; 150 | for (let key in rawErrors) { 151 | const errs = rawErrors[key] as InvalidPayload[]; 152 | const attr = (attributes || {})[key] || key; 153 | 154 | errorMessages[key] = {} as { [k: string]: string }; 155 | 156 | for (let err of errs) { 157 | const checkType = getCheckType(err.rule); 158 | 159 | // Remove checkType from err.rule 160 | const ruleKey = checkType 161 | ? err.rule.substr(0, err.rule.length - checkType.length - 1) 162 | : err.rule; 163 | 164 | if (err.rule === "validateObject" && err.params.errors) { 165 | errorMessages[key][ruleKey] = resolveErrorMessages(err.params.errors, { 166 | messages, 167 | attributes, 168 | }); 169 | } else if (err.rule === "validateArray" && err.params.errors) { 170 | errorMessages[key][ruleKey] = resolveErrorMessages(err.params.errors, { 171 | messages, 172 | attributes, 173 | }); 174 | } else { 175 | const msg = findBestMessage( 176 | messages || {}, 177 | key, 178 | err.rule, 179 | ruleKey, 180 | defaultMessage, 181 | ); 182 | errorMessages[key][ruleKey] = resolveErrorMessage( 183 | msg, 184 | err.params, 185 | attr, 186 | checkType, 187 | ); 188 | } 189 | } 190 | } 191 | return errorMessages; 192 | }; 193 | 194 | export const isStringInt = (value: string): boolean => { 195 | return value.match(/^\d+$/) ? true : false; 196 | }; 197 | 198 | export const getValue = (input: InputData, key: string): any => { 199 | if (typeof input[key] !== "undefined") { 200 | return input[key]; 201 | } 202 | 203 | const paths = key.split("."); 204 | const value = paths.reduce( 205 | (data: any, path: string): any => { 206 | if (data && typeof data === "object") { 207 | return data[path]; 208 | } else if (data instanceof Array && isStringInt(path)) { 209 | const index = parseInt(path); 210 | return data[index]; 211 | } 212 | }, 213 | { ...input }, 214 | ); 215 | 216 | return value; 217 | }; 218 | 219 | export const hasValue = (input: InputData, key: string): boolean => { 220 | const value = getValue(input, key); 221 | return typeof value !== "undefined"; 222 | }; 223 | 224 | export const makeValidationUtils = (input: InputData): ValidationUtils => { 225 | return { 226 | getValue: (key: string): any => getValue(input, key), 227 | hasValue: (key: string): boolean => hasValue(input, key), 228 | }; 229 | }; 230 | 231 | export const clearTimes = (date: Date): Date => { 232 | return new Date( 233 | date.getFullYear(), 234 | date.getMonth(), 235 | date.getDate(), 236 | 0, 237 | 0, 238 | 0, 239 | 0, 240 | ); 241 | }; 242 | 243 | export const dateChecks = ( 244 | value: any, 245 | ruleName: string, 246 | customParams?: InvalidParams, 247 | fnValidator?: (date: Date) => boolean, 248 | ): Validity => { 249 | if (typeof value !== "string" && value instanceof Date === false) { 250 | return invalid(`${ruleName}:typeCheck`, { ...customParams, value }); 251 | } 252 | 253 | if (typeof value === "string" && value.length < 10) { 254 | return invalid(`${ruleName}:lengthCheck`, { ...customParams, value }); 255 | } 256 | 257 | const date = new Date(value); 258 | if (isNaN(date.getTime())) { 259 | return invalid(`${ruleName}:dateCheck`, { ...customParams, value }); 260 | } 261 | 262 | if (fnValidator && fnValidator(date) === false) { 263 | return invalid(`${ruleName}`, { ...customParams, value }); 264 | } 265 | }; 266 | -------------------------------------------------------------------------------- /src/validate.ts: -------------------------------------------------------------------------------- 1 | import type { ValidationResult, Rule, Validity } from "./types.ts"; 2 | import type { 3 | ValidationRules, 4 | ValidationOptions, 5 | RawValidationResult, 6 | InputData, 7 | InvalidPayload, 8 | ValidationUtils, 9 | OptionalValidity, 10 | } from "./interfaces.ts"; 11 | import { 12 | isOptional, 13 | isOptionalValue, 14 | resolveErrorMessages, 15 | isNullable, 16 | makeValidationUtils, 17 | } from "./utils.ts"; 18 | import { defaultMessages } from "./messages.ts"; 19 | 20 | const getValue = (input: InputData, key: string): any => { 21 | return input[key]; 22 | }; 23 | 24 | const optionallyRequired = new Set([ 25 | "requiredWhenRule", 26 | "requiredIfRule", 27 | "requiredUnlessRule", 28 | ]); 29 | 30 | export const validateValue = async ( 31 | value: any, 32 | rules: Rule[], 33 | utils: ValidationUtils, 34 | ): Promise => { 35 | const results = []; 36 | if (isOptionalValue(value) && isOptional(rules)) { 37 | const optionallyRequiredRules = rules.filter((r) => 38 | optionallyRequired.has(r.name) 39 | ); 40 | if (optionallyRequiredRules.length === 0) { 41 | return []; 42 | } 43 | for (let rule of rules.filter((r) => optionallyRequired.has(r.name))) { 44 | let res = rule(value, utils); 45 | if (res instanceof Promise) { 46 | res = await res; 47 | } 48 | if (res !== undefined && (res as OptionalValidity).noContext) { 49 | return []; 50 | } 51 | if (res !== undefined) { 52 | results.push(res); 53 | if (res.implicit) { 54 | return results; 55 | } 56 | } 57 | } 58 | rules = rules.filter((r) => !optionallyRequired.has(r.name)); 59 | } 60 | 61 | if (typeof value === "object" && value === null && isNullable(rules)) { 62 | return []; 63 | } 64 | 65 | for (let rule of rules) { 66 | let res = rule(value, utils); 67 | if (res instanceof Promise) { 68 | res = await res; 69 | } 70 | 71 | if (res !== undefined && !(res as OptionalValidity).noContext) { 72 | results.push(res); 73 | if (res.implicit === true) { 74 | break; 75 | } 76 | } 77 | } 78 | return results; 79 | }; 80 | 81 | export const validateData = async ( 82 | input: InputData, 83 | rules: ValidationRules, 84 | ): Promise => { 85 | const results: RawValidationResult = {}; 86 | const utils: ValidationUtils = makeValidationUtils(input); 87 | for (let key in rules) { 88 | const keyRules = (rules[key] instanceof Array 89 | ? rules[key] 90 | : [rules[key]]) as Rule[]; 91 | const value: any = getValue(input, key); 92 | const errors: InvalidPayload[] = await validateValue( 93 | value, 94 | keyRules, 95 | utils, 96 | ); 97 | if (errors.length) { 98 | results[key] = errors; 99 | } 100 | } 101 | return results; 102 | }; 103 | 104 | export const validate = async ( 105 | input: InputData, 106 | rules: ValidationRules, 107 | options: ValidationOptions = { 108 | messages: defaultMessages, 109 | }, 110 | ): Promise => { 111 | const rawErrors = await validateData(input, rules); 112 | const passes = Object.keys(rawErrors).length === 0; 113 | 114 | const errors = passes ? {} : resolveErrorMessages(rawErrors, options); 115 | 116 | return [passes, errors]; 117 | }; 118 | -------------------------------------------------------------------------------- /tests/deps.ts: -------------------------------------------------------------------------------- 1 | export { 2 | assertEquals, 3 | assertNotEquals, 4 | } from "https://deno.land/std@0.65.0/testing/asserts.ts"; 5 | -------------------------------------------------------------------------------- /tests/rules/date_after.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { dateAfter } from "../../src/rules/date_after.ts"; 3 | import { invalid } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test("rules.dateAfter() type check", () => { 7 | const date = new Date(); 8 | assertInvalid( 9 | dateAfter(date)(5, fakeUtils) as Validity, 10 | invalid("dateAfter:typeCheck", { value: 5, date }), 11 | ); 12 | assertInvalid( 13 | dateAfter(date)(null, fakeUtils) as Validity, 14 | invalid("dateAfter:typeCheck", { value: null, date }), 15 | ); 16 | assertInvalid( 17 | dateAfter(date)(undefined, fakeUtils) as Validity, 18 | invalid("dateAfter:typeCheck", { value: undefined, date }), 19 | ); 20 | assertInvalid( 21 | dateAfter(date)([], fakeUtils) as Validity, 22 | invalid("dateAfter:typeCheck", { value: [], date }), 23 | ); 24 | assertInvalid( 25 | dateAfter(date)({}, fakeUtils) as Validity, 26 | invalid("dateAfter:typeCheck", { value: {}, date }), 27 | ); 28 | }); 29 | 30 | Deno.test("rules.dateAfter() length check", () => { 31 | const date = new Date(); 32 | assertInvalid( 33 | dateAfter(date)("20201002", fakeUtils) as Validity, 34 | invalid("dateAfter:lengthCheck", { value: "20201002", date }), 35 | ); 36 | }); 37 | 38 | Deno.test("rules.dateAfter() date check", () => { 39 | const date = new Date("2020-01-02 10:20:30"); 40 | 41 | // same date 42 | assertInvalid( 43 | dateAfter(date)("2020-01-02", fakeUtils) as Validity, 44 | invalid("dateAfter", { value: "2020-01-02", date }), 45 | ); 46 | assertInvalid( 47 | dateAfter(date)(new Date("2020-01-02"), fakeUtils) as Validity, 48 | invalid("dateAfter", { value: new Date("2020-01-02"), date }), 49 | ); 50 | 51 | // date before 52 | assertInvalid( 53 | dateAfter(date)("2020-01-01", fakeUtils) as Validity, 54 | invalid("dateAfter", { value: "2020-01-01", date }), 55 | ); 56 | assertInvalid( 57 | dateAfter(date)(new Date("2020-01-01"), fakeUtils) as Validity, 58 | invalid("dateAfter", { value: new Date("2020-01-01"), date }), 59 | ); 60 | 61 | // date After 62 | assertValid(dateAfter(date)("2020-01-03", fakeUtils) as Validity); 63 | assertValid(dateAfter(date)(new Date("2020-01-03"), fakeUtils) as Validity); 64 | }); 65 | -------------------------------------------------------------------------------- /tests/rules/date_after_or_equal.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { dateAfterOrEqual } from "../../src/rules/date_after_or_equal.ts"; 3 | import { invalid } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test("rules.dateAfterOrEqual() type check", () => { 7 | const date = new Date(); 8 | assertInvalid( 9 | dateAfterOrEqual(date)(5, fakeUtils) as Validity, 10 | invalid("dateAfterOrEqual:typeCheck", { value: 5, date }), 11 | ); 12 | assertInvalid( 13 | dateAfterOrEqual(date)(null, fakeUtils) as Validity, 14 | invalid("dateAfterOrEqual:typeCheck", { value: null, date }), 15 | ); 16 | assertInvalid( 17 | dateAfterOrEqual(date)(undefined, fakeUtils) as Validity, 18 | invalid("dateAfterOrEqual:typeCheck", { value: undefined, date }), 19 | ); 20 | assertInvalid( 21 | dateAfterOrEqual(date)([], fakeUtils) as Validity, 22 | invalid("dateAfterOrEqual:typeCheck", { value: [], date }), 23 | ); 24 | assertInvalid( 25 | dateAfterOrEqual(date)({}, fakeUtils) as Validity, 26 | invalid("dateAfterOrEqual:typeCheck", { value: {}, date }), 27 | ); 28 | }); 29 | 30 | Deno.test("rules.dateAfterOrEqual() length check", () => { 31 | const date = new Date(); 32 | assertInvalid( 33 | dateAfterOrEqual(date)("20201002", fakeUtils) as Validity, 34 | invalid("dateAfterOrEqual:lengthCheck", { value: "20201002", date }), 35 | ); 36 | }); 37 | 38 | Deno.test("rules.dateAfterOrEqual() date check", () => { 39 | const date = new Date("2020-01-02 10:20:30"); 40 | 41 | // same date 42 | assertValid(dateAfterOrEqual(date)("2020-01-02", fakeUtils) as Validity); 43 | assertValid( 44 | dateAfterOrEqual(date)(new Date("2020-01-02"), fakeUtils) as Validity, 45 | ); 46 | 47 | // date before 48 | assertInvalid( 49 | dateAfterOrEqual(date)("2020-01-01", fakeUtils) as Validity, 50 | invalid("dateAfterOrEqual", { value: "2020-01-01", date }), 51 | ); 52 | assertInvalid( 53 | dateAfterOrEqual(date)(new Date("2020-01-01"), fakeUtils) as Validity, 54 | invalid("dateAfterOrEqual", { value: new Date("2020-01-01"), date }), 55 | ); 56 | 57 | // date After 58 | assertValid(dateAfterOrEqual(date)("2020-01-03", fakeUtils) as Validity); 59 | assertValid( 60 | dateAfterOrEqual(date)(new Date("2020-01-03"), fakeUtils) as Validity, 61 | ); 62 | }); 63 | -------------------------------------------------------------------------------- /tests/rules/date_before.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { dateBefore } from "../../src/rules/date_before.ts"; 3 | import { invalid } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test("rules.dateBefore() type check", () => { 7 | const date = new Date(); 8 | assertInvalid( 9 | dateBefore(date)(5, fakeUtils) as Validity, 10 | invalid("dateBefore:typeCheck", { value: 5, date }), 11 | ); 12 | assertInvalid( 13 | dateBefore(date)(null, fakeUtils) as Validity, 14 | invalid("dateBefore:typeCheck", { value: null, date }), 15 | ); 16 | assertInvalid( 17 | dateBefore(date)(undefined, fakeUtils) as Validity, 18 | invalid("dateBefore:typeCheck", { value: undefined, date }), 19 | ); 20 | assertInvalid( 21 | dateBefore(date)([], fakeUtils) as Validity, 22 | invalid("dateBefore:typeCheck", { value: [], date }), 23 | ); 24 | assertInvalid( 25 | dateBefore(date)({}, fakeUtils) as Validity, 26 | invalid("dateBefore:typeCheck", { value: {}, date }), 27 | ); 28 | }); 29 | 30 | Deno.test("rules.dateBefore() length check", () => { 31 | const date = new Date(); 32 | assertInvalid( 33 | dateBefore(date)("20201002", fakeUtils) as Validity, 34 | invalid("dateBefore:lengthCheck", { value: "20201002", date }), 35 | ); 36 | }); 37 | 38 | Deno.test("rules.dateBefore() date check", () => { 39 | const date = new Date("2020-01-02 10:20:30"); 40 | 41 | // same date 42 | assertInvalid( 43 | dateBefore(date)("2020-01-02", fakeUtils) as Validity, 44 | invalid("dateBefore", { value: "2020-01-02", date }), 45 | ); 46 | assertInvalid( 47 | dateBefore(date)(new Date("2020-01-02"), fakeUtils) as Validity, 48 | invalid("dateBefore", { value: new Date("2020-01-02"), date }), 49 | ); 50 | 51 | // date after 52 | assertInvalid( 53 | dateBefore(date)("2020-01-03", fakeUtils) as Validity, 54 | invalid("dateBefore", { value: "2020-01-03", date }), 55 | ); 56 | assertInvalid( 57 | dateBefore(date)(new Date("2020-01-03"), fakeUtils) as Validity, 58 | invalid("dateBefore", { value: new Date("2020-01-03"), date }), 59 | ); 60 | 61 | // date before 62 | assertValid(dateBefore(date)("2020-01-01", fakeUtils) as Validity); 63 | assertValid(dateBefore(date)(new Date("2020-01-01"), fakeUtils) as Validity); 64 | }); 65 | -------------------------------------------------------------------------------- /tests/rules/date_before_or_equal.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { dateBeforeOrEqual } from "../../src/rules/date_before_or_equal.ts"; 3 | import { invalid } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test("rules.dateBeforeOrEqual() type check", () => { 7 | const date = new Date(); 8 | assertInvalid( 9 | dateBeforeOrEqual(date)(5, fakeUtils) as Validity, 10 | invalid("dateBeforeOrEqual:typeCheck", { value: 5, date }), 11 | ); 12 | assertInvalid( 13 | dateBeforeOrEqual(date)(null, fakeUtils) as Validity, 14 | invalid("dateBeforeOrEqual:typeCheck", { value: null, date }), 15 | ); 16 | assertInvalid( 17 | dateBeforeOrEqual(date)(undefined, fakeUtils) as Validity, 18 | invalid("dateBeforeOrEqual:typeCheck", { value: undefined, date }), 19 | ); 20 | assertInvalid( 21 | dateBeforeOrEqual(date)([], fakeUtils) as Validity, 22 | invalid("dateBeforeOrEqual:typeCheck", { value: [], date }), 23 | ); 24 | assertInvalid( 25 | dateBeforeOrEqual(date)({}, fakeUtils) as Validity, 26 | invalid("dateBeforeOrEqual:typeCheck", { value: {}, date }), 27 | ); 28 | }); 29 | 30 | Deno.test("rules.dateBeforeOrEqual() length check", () => { 31 | const date = new Date(); 32 | assertInvalid( 33 | dateBeforeOrEqual(date)("20201002", fakeUtils) as Validity, 34 | invalid("dateBeforeOrEqual:lengthCheck", { value: "20201002", date }), 35 | ); 36 | }); 37 | 38 | Deno.test("rules.dateBeforeOrEqual() date check", () => { 39 | const date = new Date("2020-01-02 10:20:30"); 40 | 41 | // same date 42 | assertValid(dateBeforeOrEqual(date)("2020-01-02", fakeUtils) as Validity); 43 | assertValid( 44 | dateBeforeOrEqual(date)(new Date("2020-01-02"), fakeUtils) as Validity, 45 | ); 46 | 47 | // date after 48 | assertInvalid( 49 | dateBeforeOrEqual(date)("2020-01-03", fakeUtils) as Validity, 50 | invalid("dateBeforeOrEqual", { value: "2020-01-03", date }), 51 | ); 52 | assertInvalid( 53 | dateBeforeOrEqual(date)(new Date("2020-01-03"), fakeUtils) as Validity, 54 | invalid("dateBeforeOrEqual", { value: new Date("2020-01-03"), date }), 55 | ); 56 | 57 | // date before 58 | assertValid(dateBeforeOrEqual(date)("2020-01-01", fakeUtils) as Validity); 59 | assertValid( 60 | dateBeforeOrEqual(date)(new Date("2020-01-01"), fakeUtils) as Validity, 61 | ); 62 | }); 63 | -------------------------------------------------------------------------------- /tests/rules/date_between.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { dateBetween } from "../../src/rules/date_between.ts"; 3 | import { invalid } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test("rules.dateBetween() type check", () => { 7 | const minDate = new Date("2020-01-02"); 8 | const maxDate = new Date("2020-01-09"); 9 | assertInvalid( 10 | dateBetween(minDate, maxDate)(5, fakeUtils) as Validity, 11 | invalid("dateBetween:typeCheck", { value: 5, minDate, maxDate }), 12 | ); 13 | assertInvalid( 14 | dateBetween(minDate, maxDate)(null, fakeUtils) as Validity, 15 | invalid("dateBetween:typeCheck", { value: null, minDate, maxDate }), 16 | ); 17 | assertInvalid( 18 | dateBetween(minDate, maxDate)(undefined, fakeUtils) as Validity, 19 | invalid("dateBetween:typeCheck", { value: undefined, minDate, maxDate }), 20 | ); 21 | assertInvalid( 22 | dateBetween(minDate, maxDate)([], fakeUtils) as Validity, 23 | invalid("dateBetween:typeCheck", { value: [], minDate, maxDate }), 24 | ); 25 | assertInvalid( 26 | dateBetween(minDate, maxDate)({}, fakeUtils) as Validity, 27 | invalid("dateBetween:typeCheck", { value: {}, minDate, maxDate }), 28 | ); 29 | }); 30 | 31 | Deno.test("rules.dateBetween() length check", () => { 32 | const minDate = new Date("2020-01-02"); 33 | const maxDate = new Date("2020-01-09"); 34 | assertInvalid( 35 | dateBetween(minDate, maxDate)("20201002", fakeUtils) as Validity, 36 | invalid("dateBetween:lengthCheck", { value: "20201002", minDate, maxDate }), 37 | ); 38 | }); 39 | 40 | Deno.test("rules.dateBetween() date check", () => { 41 | const minDate = new Date("2020-01-02"); 42 | const maxDate = new Date("2020-01-09"); 43 | 44 | // date min 45 | assertValid( 46 | dateBetween(minDate, maxDate)("2020-01-02", fakeUtils) as Validity, 47 | ); 48 | assertValid( 49 | dateBetween(minDate, maxDate)( 50 | new Date("2020-01-02"), 51 | fakeUtils, 52 | ) as Validity, 53 | ); 54 | // date max 55 | assertValid( 56 | dateBetween(minDate, maxDate)("2020-01-09", fakeUtils) as Validity, 57 | ); 58 | assertValid( 59 | dateBetween(minDate, maxDate)( 60 | new Date("2020-01-09"), 61 | fakeUtils, 62 | ) as Validity, 63 | ); 64 | 65 | // below date min 66 | assertInvalid( 67 | dateBetween(minDate, maxDate)("2020-01-01", fakeUtils) as Validity, 68 | invalid("dateBetween", { value: "2020-01-01", minDate, maxDate }), 69 | ); 70 | assertInvalid( 71 | dateBetween(minDate, maxDate)( 72 | new Date("2020-01-01"), 73 | fakeUtils, 74 | ) as Validity, 75 | invalid("dateBetween", { value: new Date("2020-01-01"), minDate, maxDate }), 76 | ); 77 | 78 | // above date max 79 | assertInvalid( 80 | dateBetween(minDate, maxDate)("2020-01-10", fakeUtils) as Validity, 81 | invalid("dateBetween", { value: "2020-01-10", minDate, maxDate }), 82 | ); 83 | assertInvalid( 84 | dateBetween(minDate, maxDate)( 85 | new Date("2020-01-10"), 86 | fakeUtils, 87 | ) as Validity, 88 | invalid("dateBetween", { value: new Date("2020-01-10"), minDate, maxDate }), 89 | ); 90 | 91 | // date between 92 | assertValid( 93 | dateBetween(minDate, maxDate)("2020-01-07", fakeUtils) as Validity, 94 | ); 95 | assertValid( 96 | dateBetween(minDate, maxDate)( 97 | new Date("2020-01-07"), 98 | fakeUtils, 99 | ) as Validity, 100 | ); 101 | }); 102 | -------------------------------------------------------------------------------- /tests/rules/either.test.ts: -------------------------------------------------------------------------------- 1 | import { either } from "../../src/rules/either.ts"; 2 | import { invalid } from "../../src/utils.ts"; 3 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 4 | import { isString, isInt } from "../../src/rules.ts"; 5 | 6 | Deno.test("rules.either with invalid values", async () => { 7 | const invalidValues = [ 8 | [], 9 | {}, 10 | 1.24, 11 | ]; 12 | 13 | for (const value of invalidValues) { 14 | assertInvalid( 15 | await either([isString, isInt])(value, fakeUtils), 16 | invalid("either", { value }), 17 | `${value} is not either string or number`, 18 | ); 19 | } 20 | }); 21 | 22 | Deno.test("rules.either with valid values", async () => { 23 | const validValues = [ 24 | 1, 25 | "2", 26 | 3, 27 | "lorem", 28 | "ipsum", 29 | 5, 30 | 6, 31 | 7, 32 | "eight", 33 | "nine", 34 | "ten", 35 | ]; 36 | 37 | for (const value of validValues) { 38 | assertValid( 39 | await either([isString, isInt])(value, fakeUtils), 40 | `${value} is either string or number`, 41 | ); 42 | } 43 | }); 44 | 45 | Deno.test("rules.either with invalid values and custom errorCode", async () => { 46 | const invalidValues = [ 47 | [], 48 | {}, 49 | 1.24, 50 | ]; 51 | 52 | for (const value of invalidValues) { 53 | assertInvalid( 54 | await either([isString, isInt], "stringOrInt")(value, fakeUtils), 55 | invalid("stringOrInt", { value }), 56 | `${value} is not either string or number`, 57 | ); 58 | } 59 | }); 60 | -------------------------------------------------------------------------------- /tests/rules/ends_with.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { endsWith } from "../../src/rules/ends_with.ts"; 3 | import { invalid } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test("rules.endsWith('foo')(null) should be invalid", () => { 7 | assertInvalid( 8 | endsWith("foo")(null, fakeUtils) as Validity, 9 | invalid("endsWith", { value: null, str: "foo" }), 10 | ); 11 | }); 12 | 13 | Deno.test("rules.endsWith('foo')('bar') should be invalid", () => { 14 | assertInvalid( 15 | endsWith("foo")("bar", fakeUtils) as Validity, 16 | invalid("endsWith", { value: "bar", str: "foo" }), 17 | ); 18 | }); 19 | 20 | Deno.test("rules.endsWith('foo')('foobar') should be invalid", () => { 21 | assertInvalid( 22 | endsWith("foo")("foobar", fakeUtils) as Validity, 23 | invalid("endsWith", { value: "foobar", str: "foo" }), 24 | ); 25 | }); 26 | 27 | Deno.test("rules.endsWith('bar')('foobar') should be valid", () => { 28 | assertValid(endsWith("bar")("foobar", fakeUtils) as Validity); 29 | }); 30 | 31 | Deno.test("rules.endsWith('foobarbaz')('foobarbaz') should be valid", () => { 32 | assertValid(endsWith("foobarbaz")("foobarbaz", fakeUtils) as Validity); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/rules/is_array.test.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from "../../src/rules/is_array.ts"; 2 | import { invalid } from "../../src/utils.ts"; 3 | import { assertInvalid, assertValid } from "../utils.ts"; 4 | 5 | Deno.test("rules.isArray(null) should be invalid", () => { 6 | assertInvalid(isArray(null), invalid("isArray", { value: null })); 7 | }); 8 | 9 | Deno.test("rules.isArray(undefined) should be invalid", () => { 10 | assertInvalid(isArray(undefined), invalid("isArray", { value: undefined })); 11 | }); 12 | 13 | Deno.test("rules.isArray('') should be invalid", () => { 14 | assertInvalid(isArray(""), invalid("isArray", { value: "" })); 15 | }); 16 | 17 | Deno.test("rules.isArray(false) should be invalid", () => { 18 | assertInvalid(isArray(false), invalid("isArray", { value: false })); 19 | }); 20 | 21 | Deno.test("rules.isArray(0) should be invalid", () => { 22 | assertInvalid(isArray(0), invalid("isArray", { value: 0 })); 23 | }); 24 | 25 | Deno.test("rules.isArray([]) should be valid", () => { 26 | assertValid(isArray([])); 27 | }); 28 | -------------------------------------------------------------------------------- /tests/rules/is_bool.test.ts: -------------------------------------------------------------------------------- 1 | import { isBool } from "../../src/rules/is_bool.ts"; 2 | import { invalid } from "../../src/utils.ts"; 3 | import { assertInvalid, assertValid } from "../utils.ts"; 4 | 5 | Deno.test("rules.isBool(null) should be invalid", () => { 6 | assertInvalid(isBool(null), invalid("isBool", { value: null })); 7 | }); 8 | 9 | Deno.test("rules.isBool(undefined) should be invalid", () => { 10 | assertInvalid(isBool(undefined), invalid("isBool", { value: undefined })); 11 | }); 12 | 13 | Deno.test("rules.isBool('') should be invalid", () => { 14 | assertInvalid(isBool(""), invalid("isBool", { value: "" })); 15 | }); 16 | 17 | Deno.test("rules.isBool(0) should be invalid", () => { 18 | assertInvalid(isBool(0), invalid("isBool", { value: 0 })); 19 | }); 20 | 21 | Deno.test("rules.isBool(true) should be valid", () => { 22 | assertValid(isBool(true)); 23 | }); 24 | 25 | Deno.test("rules.isBool(false) should be valid", () => { 26 | assertValid(isBool(false)); 27 | }); 28 | -------------------------------------------------------------------------------- /tests/rules/is_date.test.ts: -------------------------------------------------------------------------------- 1 | import { isDate } from "../../src/rules/is_date.ts"; 2 | import { invalid } from "../../src/utils.ts"; 3 | import { assertInvalid, assertValid } from "../utils.ts"; 4 | 5 | Deno.test("rules.isDate(null) should be invalid", () => { 6 | assertInvalid(isDate(null), invalid("isDate:typeCheck", { value: null })); 7 | }); 8 | 9 | Deno.test("rules.isDate(undefined) should be invalid", () => { 10 | assertInvalid( 11 | isDate(undefined), 12 | invalid("isDate:typeCheck", { value: undefined }), 13 | ); 14 | }); 15 | 16 | Deno.test("rules.isDate('') should be invalid", () => { 17 | assertInvalid(isDate(""), invalid("isDate:lengthCheck", { value: "" })); 18 | }); 19 | 20 | Deno.test("rules.isDate(2020) should be invalid", () => { 21 | assertInvalid(isDate(2020), invalid("isDate:typeCheck", { value: 2020 })); 22 | }); 23 | 24 | Deno.test("rules.isDate('2020-01') should be invalid", () => { 25 | assertInvalid( 26 | isDate("2020-01"), 27 | invalid("isDate:lengthCheck", { value: "2020-01" }), 28 | ); 29 | }); 30 | 31 | Deno.test("rules.isDate('2020-01-02') should be valid", () => { 32 | assertValid(isDate("2020-01-02")); 33 | }); 34 | 35 | Deno.test("rules.isDate('2020-01-02 10:20:30') should be valid", () => { 36 | assertValid(isDate("2020-01-02 10:20:30")); 37 | }); 38 | -------------------------------------------------------------------------------- /tests/rules/is_email.test.ts: -------------------------------------------------------------------------------- 1 | import { isEmail } from "../../src/rules/is_email.ts"; 2 | import { invalid } from "../../src/utils.ts"; 3 | import { assertInvalid, assertValid } from "../utils.ts"; 4 | 5 | Deno.test("rules.isEmail(null) should be invalid", () => { 6 | assertInvalid(isEmail(null), invalid("isEmail", { value: null })); 7 | }); 8 | 9 | Deno.test("rules.isEmail(undefined) should be invalid", () => { 10 | assertInvalid(isEmail(undefined), invalid("isEmail", { value: undefined })); 11 | }); 12 | 13 | Deno.test("rules.isEmail('') should be invalid", () => { 14 | assertInvalid(isEmail(""), invalid("isEmail", { value: "" })); 15 | }); 16 | 17 | Deno.test("rules.isEmail('foo.mail.com') should be invalid", () => { 18 | assertInvalid( 19 | isEmail("foo.mail.com"), 20 | invalid("isEmail", { value: "foo.mail.com" }), 21 | ); 22 | }); 23 | 24 | Deno.test("rules.isEmail('foo@mail.com') should be valid", () => { 25 | assertValid(isEmail("foo@mail.com")); 26 | }); 27 | 28 | Deno.test("rules.isEmail('foo.bar@mail.com') should be valid", () => { 29 | assertValid(isEmail("foo.bar@mail.com")); 30 | }); 31 | 32 | Deno.test("rules.isEmail('foo_bar@mail.com') should be valid", () => { 33 | assertValid(isEmail("foo_bar@mail.com")); 34 | }); 35 | 36 | Deno.test("rules.isEmail('foobar@[1.2.3.4]') should be valid", () => { 37 | assertValid(isEmail("foobar@[1.2.3.4]")); 38 | }); 39 | -------------------------------------------------------------------------------- /tests/rules/is_float.test.ts: -------------------------------------------------------------------------------- 1 | import { isFloat } from "../../src/rules/is_float.ts"; 2 | import { invalid } from "../../src/utils.ts"; 3 | import { assertInvalid, assertValid } from "../utils.ts"; 4 | 5 | Deno.test("rules.isFloat(null) should be invalid", () => { 6 | assertInvalid(isFloat(null), invalid("isFloat", { value: null })); 7 | }); 8 | 9 | Deno.test("rules.isFloat(undefined) should be invalid", () => { 10 | assertInvalid(isFloat(undefined), invalid("isFloat", { value: undefined })); 11 | }); 12 | 13 | Deno.test("rules.isFloat('') should be invalid", () => { 14 | assertInvalid(isFloat(""), invalid("isFloat", { value: "" })); 15 | }); 16 | 17 | Deno.test("rules.isFloat(0) should be invalid", () => { 18 | assertInvalid(isFloat(0), invalid("isFloat", { value: 0 })); 19 | }); 20 | 21 | Deno.test("rules.isFloat(123) should be invalid", () => { 22 | assertInvalid(isFloat(123), invalid("isFloat", { value: 123 })); 23 | }); 24 | 25 | Deno.test("rules.isFloat(0.1) should be valid", () => { 26 | assertValid(isFloat(0.1)); 27 | }); 28 | 29 | Deno.test("rules.isFloat(1.23) should be valid", () => { 30 | assertValid(isFloat(1.23)); 31 | }); 32 | -------------------------------------------------------------------------------- /tests/rules/is_in.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { isIn } from "../../src/rules/is_in.ts"; 3 | import { invalid } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test("rules.isIn([0, 1, 2])(null) should be invalid", () => { 7 | assertInvalid( 8 | isIn([0, 1, 2])(null, fakeUtils) as Validity, 9 | invalid("isIn", { value: null, allowedValues: [0, 1, 2] }), 10 | ); 11 | }); 12 | 13 | Deno.test("rules.isIn([0, 1, 2])('0') should be invalid", () => { 14 | assertInvalid( 15 | isIn([0, 1, 2])("0", fakeUtils) as Validity, 16 | invalid("isIn", { value: "0", allowedValues: [0, 1, 2] }), 17 | ); 18 | }); 19 | 20 | Deno.test("rules.isIn([0, 1, 2])(1) should be valid", () => { 21 | assertValid(isIn([0, 1, 2])(1, fakeUtils) as Validity); 22 | }); 23 | 24 | Deno.test("rules.isIn([0, 1, 2])(0) should be valid", () => { 25 | assertValid(isIn([0, 1, 2])(0, fakeUtils) as Validity); 26 | }); 27 | 28 | Deno.test("rules.isIn([0, 1, 2])(2) should be valid", () => { 29 | assertValid(isIn([0, 1, 2])(2, fakeUtils) as Validity); 30 | }); 31 | -------------------------------------------------------------------------------- /tests/rules/is_int.test.ts: -------------------------------------------------------------------------------- 1 | import { isInt } from "../../src/rules/is_int.ts"; 2 | import { invalid } from "../../src/utils.ts"; 3 | import { assertInvalid, assertValid } from "../utils.ts"; 4 | 5 | Deno.test("rules.isInt(null) should be invalid", () => { 6 | assertInvalid(isInt(null), invalid("isInt", { value: null })); 7 | }); 8 | 9 | Deno.test("rules.isInt(undefined) should be invalid", () => { 10 | assertInvalid(isInt(undefined), invalid("isInt", { value: undefined })); 11 | }); 12 | 13 | Deno.test("rules.isInt('') should be invalid", () => { 14 | assertInvalid(isInt(""), invalid("isInt", { value: "" })); 15 | }); 16 | 17 | Deno.test("rules.isInt(0.1) should be invalid", () => { 18 | assertInvalid(isInt(0.1), invalid("isInt", { value: 0.1 })); 19 | }); 20 | 21 | Deno.test("rules.isInt(123.1) should be invalid", () => { 22 | assertInvalid(isInt(123.1), invalid("isInt", { value: 123.1 })); 23 | }); 24 | 25 | Deno.test("rules.isInt(0) should be valid", () => { 26 | assertValid(isInt(0)); 27 | }); 28 | 29 | Deno.test("rules.isInt(123) should be valid", () => { 30 | assertValid(isInt(123)); 31 | }); 32 | -------------------------------------------------------------------------------- /tests/rules/is_ipv4.test.ts: -------------------------------------------------------------------------------- 1 | import { isIPv4 } from "../../src/rules/is_ipv4.ts"; 2 | import { invalid } from "../../src/utils.ts"; 3 | import { assertInvalid, assertValid } from "../utils.ts"; 4 | 5 | Deno.test("rules.isIPv4(null) should be invalid", () => { 6 | assertInvalid(isIPv4(null), invalid("isIPv4", { value: null })); 7 | }); 8 | 9 | Deno.test("rules.isIPv4(undefined) should be invalid", () => { 10 | assertInvalid(isIPv4(undefined), invalid("isIPv4", { value: undefined })); 11 | }); 12 | 13 | Deno.test("rules.isIPv4('1.2.3.b') should be invalid", () => { 14 | assertInvalid(isIPv4("1.2.3.b"), invalid("isIPv4", { value: "1.2.3.b" })); 15 | }); 16 | 17 | Deno.test("rules.isIPv4('0.1.2.3') should be valid", () => { 18 | assertValid(isIPv4("0.1.2.3")); 19 | }); 20 | 21 | Deno.test("rules.isIPv4('255.255.255.255') should be valid", () => { 22 | assertValid(isIPv4("255.255.255.255")); 23 | }); 24 | 25 | Deno.test("rules.isIPv4('1.2.3.256') should be invalid", () => { 26 | assertInvalid(isIPv4("1.2.3.256"), invalid("isIPv4", { value: "1.2.3.256" })); 27 | }); 28 | -------------------------------------------------------------------------------- /tests/rules/is_ipv6.test.ts: -------------------------------------------------------------------------------- 1 | import { isIPv6 } from "../../src/rules/is_ipv6.ts"; 2 | import { invalid } from "../../src/utils.ts"; 3 | import { assertInvalid, assertValid } from "../utils.ts"; 4 | 5 | Deno.test("rules.isIPv6 with invalid values", () => { 6 | const invalidValues = [ 7 | null, 8 | 123, 9 | "2001:af40:::", 10 | "2001:af40:::1234", 11 | "2001::af40::1234", 12 | "1080:0:0:0:8:800:200C:417G", 13 | ]; 14 | 15 | for (const value of invalidValues) { 16 | assertInvalid( 17 | isIPv6(value), 18 | invalid("isIPv6", { value }), 19 | `${value} should be invalid ipv6`, 20 | ); 21 | } 22 | }); 23 | 24 | Deno.test("rules.isIPv6 with valid values", () => { 25 | const validValues = [ 26 | "2001:db8::7", 27 | "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210", 28 | "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210", 29 | "1080:0:0:0:8:800:200C:417A", 30 | "::1:2:3:4:5:6:7", 31 | "::1:2:3:4:5:6", 32 | "1::1:2:3:4:5:6", 33 | "::1:2:3:4:5", 34 | "1::1:2:3:4:5", 35 | "2:1::1:2:3:4:5", 36 | "::1:2:3:4", 37 | "1::1:2:3:4", 38 | "2:1::1:2:3:4", 39 | "3:2:1::1:2:3:4", 40 | "::1:2:3", 41 | "1::1:2:3", 42 | "2:1::1:2:3", 43 | "3:2:1::1:2:3", 44 | "4:3:2:1::1:2:3", 45 | "::1:2", 46 | "1::1:2", 47 | "2:1::1:2", 48 | "3:2:1::1:2", 49 | "4:3:2:1::1:2", 50 | "5:4:3:2:1::1:2", 51 | "::1", 52 | "1::1", 53 | "2:1::1", 54 | "3:2:1::1", 55 | "4:3:2:1::1", 56 | "5:4:3:2:1::1", 57 | "6:5:4:3:2:1::1", 58 | "::", 59 | "1::", 60 | "2:1::", 61 | "3:2:1::", 62 | "4:3:2:1::", 63 | "5:4:3:2:1::", 64 | "6:5:4:3:2:1::", 65 | "7:6:5:4:3:2:1::", 66 | ]; 67 | 68 | for (const value of validValues) { 69 | assertValid(isIPv6(value), `${value} should be valid ipv6`); 70 | } 71 | }); 72 | -------------------------------------------------------------------------------- /tests/rules/is_number.test.ts: -------------------------------------------------------------------------------- 1 | import { isNumber } from "../../src/rules/is_number.ts"; 2 | import { invalid } from "../../src/utils.ts"; 3 | import { assertInvalid, assertValid } from "../utils.ts"; 4 | 5 | Deno.test("rules.isNumber(null) should be invalid", () => { 6 | assertInvalid(isNumber(null), invalid("isNumber", { value: null })); 7 | }); 8 | 9 | Deno.test("rules.isNumber(undefined) should be invalid", () => { 10 | assertInvalid(isNumber(undefined), invalid("isNumber", { value: undefined })); 11 | }); 12 | 13 | Deno.test("rules.isNumber([]) should be invalid", () => { 14 | assertInvalid(isNumber([]), invalid("isNumber", { value: [] })); 15 | }); 16 | 17 | Deno.test("rules.isNumber([0]) should be invalid", () => { 18 | assertInvalid(isNumber([0]), invalid("isNumber", { value: [0] })); 19 | }); 20 | 21 | Deno.test("rules.isNumber('0') should be invalid", () => { 22 | assertInvalid(isNumber("0"), invalid("isNumber", { value: "0" })); 23 | }); 24 | 25 | Deno.test("rules.isNumber({}) should be invalid", () => { 26 | assertInvalid(isNumber({}), invalid("isNumber", { value: {} })); 27 | }); 28 | 29 | Deno.test("rules.isNumber(1.23) should be valid", () => { 30 | assertValid(isNumber(1.23)); 31 | }); 32 | 33 | Deno.test("rules.isNumber(123) should be valid", () => { 34 | assertValid(isNumber(123)); 35 | }); 36 | -------------------------------------------------------------------------------- /tests/rules/is_numeric.test.ts: -------------------------------------------------------------------------------- 1 | import { isNumeric } from "../../src/rules/is_numeric.ts"; 2 | import { invalid } from "../../src/utils.ts"; 3 | import { assertInvalid, assertValid } from "../utils.ts"; 4 | 5 | Deno.test("rules.isNumeric(null) should be invalid", () => { 6 | assertInvalid(isNumeric(null), invalid("isNumeric", { value: null })); 7 | }); 8 | 9 | Deno.test("rules.isNumeric(undefined) should be invalid", () => { 10 | assertInvalid( 11 | isNumeric(undefined), 12 | invalid("isNumeric", { value: undefined }), 13 | ); 14 | }); 15 | 16 | Deno.test("rules.isNumeric('') should be invalid", () => { 17 | assertInvalid(isNumeric(""), invalid("isNumeric", { value: "" })); 18 | }); 19 | 20 | Deno.test("rules.isNumeric(0) should be valid", () => { 21 | assertValid(isNumeric(0)); 22 | }); 23 | 24 | Deno.test("rules.isNumeric(0.5) should be valid", () => { 25 | assertValid(isNumeric(0.5)); 26 | }); 27 | 28 | Deno.test("rules.isNumeric('0') should be valid", () => { 29 | assertValid(isNumeric("0")); 30 | }); 31 | 32 | Deno.test("rules.isNumeric('0.5') should be valid", () => { 33 | assertValid(isNumeric("0.5")); 34 | }); 35 | -------------------------------------------------------------------------------- /tests/rules/is_string.test.ts: -------------------------------------------------------------------------------- 1 | import { isString } from "../../src/rules/is_string.ts"; 2 | import { invalid } from "../../src/utils.ts"; 3 | import { assertInvalid, assertValid } from "../utils.ts"; 4 | 5 | Deno.test("rules.isString(null) should be invalid", () => { 6 | assertInvalid(isString(null), invalid("isString", { value: null })); 7 | }); 8 | 9 | Deno.test("rules.isString(undefined) should be invalid", () => { 10 | assertInvalid(isString(undefined), invalid("isString", { value: undefined })); 11 | }); 12 | 13 | Deno.test("rules.isString(0.1) should be invalid", () => { 14 | assertInvalid(isString(0.1), invalid("isString", { value: 0.1 })); 15 | }); 16 | 17 | Deno.test("rules.isString('') should be valid", () => { 18 | assertValid(isString("")); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/rules/length_between.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { lengthBetween } from "../../src/rules/length_between.ts"; 3 | import { invalid } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test("rules.lengthBetween(4, 6)(3) should be invalid", () => { 7 | assertInvalid( 8 | lengthBetween(4, 6)(3, fakeUtils) as Validity, 9 | invalid("lengthBetween", { value: 3, minLength: 4, maxLength: 6 }), 10 | ); 11 | }); 12 | 13 | Deno.test("rules.lengthBetween(4, 5)('foobar') should be invalid", () => { 14 | assertInvalid( 15 | lengthBetween(4, 5)("foobar", fakeUtils) as Validity, 16 | invalid("lengthBetween", { value: "foobar", minLength: 4, maxLength: 5 }), 17 | ); 18 | }); 19 | 20 | Deno.test("rules.lengthBetween(4, 5)('foo') should be invalid", () => { 21 | assertInvalid( 22 | lengthBetween(4, 5)("foo", fakeUtils) as Validity, 23 | invalid("lengthBetween", { value: "foo", minLength: 4, maxLength: 5 }), 24 | ); 25 | }); 26 | 27 | Deno.test("rules.lengthBetween(4, 6)('foobar') should be valid", () => { 28 | assertValid(lengthBetween(4, 6)("foobar", fakeUtils) as Validity); 29 | }); 30 | 31 | Deno.test("rules.lengthBetween(3, 6)('foo') should be valid", () => { 32 | assertValid(lengthBetween(3, 6)("foo", fakeUtils) as Validity); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/rules/match.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { match } from "../../src/rules/match.ts"; 3 | import { invalid } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test("rules.match(/^[a-z0-9]{3}$/)(3) should be invalid", () => { 7 | assertInvalid( 8 | match(/^[a-z0-9]{3}$/)(3, fakeUtils) as Validity, 9 | invalid("match", { value: 3, regex: /^[a-z0-9]{3}$/ }), 10 | ); 11 | }); 12 | 13 | Deno.test("rules.match(/^[a-z0-9]{3}$/)('fo$') should be invalid", () => { 14 | assertInvalid( 15 | match(/^[a-z0-9]{3}$/)("fo$", fakeUtils) as Validity, 16 | invalid("match", { value: "fo$", regex: /^[a-z0-9]{3}$/ }), 17 | ); 18 | }); 19 | 20 | Deno.test("rules.match(/^[a-z0-9]{3}$/)('fo1') should be valid", () => { 21 | assertValid(match(/^[a-z0-9]{3}$/)("fo1", fakeUtils) as Validity); 22 | }); 23 | 24 | Deno.test("rules.match(/^[a-z0-9]{3}$/)(' fo1') should be invalid", () => { 25 | assertInvalid( 26 | match(/^[a-z0-9]{3}$/)(" fo1", fakeUtils) as Validity, 27 | invalid("match", { value: " fo1", regex: /^[a-z0-9]{3}$/ }), 28 | ); 29 | }); 30 | 31 | Deno.test("rules.match(/^[a-z0-9]{3}$/)(' fo1') should be valid", () => { 32 | assertValid(match(/^[a-z0-9]{3}$/, true)("fo1", fakeUtils) as Validity); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/rules/max_length.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { maxLength } from "../../src/rules/max_length.ts"; 3 | import { invalid } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test("rules.maxLength(4)(3) should be invalid", () => { 7 | assertInvalid( 8 | maxLength(4)(3, fakeUtils) as Validity, 9 | invalid("maxLength", { value: 3, maxValue: 4 }), 10 | ); 11 | }); 12 | 13 | Deno.test("rules.maxLength(4)('foobar') should be invalid", () => { 14 | assertInvalid( 15 | maxLength(4)("foobar", fakeUtils) as Validity, 16 | invalid("maxLength", { value: "foobar", maxValue: 4 }), 17 | ); 18 | }); 19 | 20 | Deno.test("rules.maxLength(6)('foobar') should be valid", () => { 21 | assertValid(maxLength(6)("foobar", fakeUtils) as Validity); 22 | }); 23 | 24 | Deno.test("rules.maxLength(6)('foo') should be valid", () => { 25 | assertValid(maxLength(6)("foo", fakeUtils) as Validity); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/rules/max_number.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { maxNumber } from "../../src/rules/max_number.ts"; 3 | import { invalid } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test("rules.maxNumber(5)(10) should be invalid", () => { 7 | assertInvalid( 8 | maxNumber(5)(10, fakeUtils) as Validity, 9 | invalid("maxNumber", { value: 10, maxValue: 5 }), 10 | ); 11 | }); 12 | 13 | Deno.test("rules.maxNumber(5)(5) should be valid", () => { 14 | assertValid(maxNumber(5)(5, fakeUtils) as Validity); 15 | }); 16 | 17 | Deno.test("rules.maxNumber(5)(4.999) should be valid", () => { 18 | assertValid(maxNumber(5)(4.999, fakeUtils) as Validity); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/rules/min_length.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { minLength } from "../../src/rules/min_length.ts"; 3 | import { invalid } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test("rules.minLength(4)(5) should be invalid", () => { 7 | assertInvalid( 8 | minLength(4)(5, fakeUtils) as Validity, 9 | invalid("minLength", { value: 5, minValue: 4 }), 10 | ); 11 | }); 12 | 13 | Deno.test("rules.minLength(4)('foo') should be invalid", () => { 14 | assertInvalid( 15 | minLength(4)("foo", fakeUtils) as Validity, 16 | invalid("minLength", { value: "foo", minValue: 4 }), 17 | ); 18 | }); 19 | 20 | Deno.test("rules.minLength(6)('foobar') should be valid", () => { 21 | assertValid(minLength(6)("foobar", fakeUtils) as Validity); 22 | }); 23 | 24 | Deno.test("rules.minLength(6)('foobarbaz') should be valid", () => { 25 | assertValid(minLength(6)("foobarbaz", fakeUtils) as Validity); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/rules/min_number.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { minNumber } from "../../src/rules/min_number.ts"; 3 | import { invalid } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test("rules.minNumber(5)(4) should be invalid", () => { 7 | assertInvalid( 8 | minNumber(5)(4, fakeUtils) as Validity, 9 | invalid("minNumber", { value: 4, minValue: 5 }), 10 | ); 11 | }); 12 | 13 | Deno.test("rules.minNumber(5)(6) should be valid", () => { 14 | assertValid(minNumber(5)(6, fakeUtils) as Validity); 15 | }); 16 | 17 | Deno.test("rules.minNumber(5)(5) should be valid", () => { 18 | assertValid(minNumber(5)(5, fakeUtils) as Validity); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/rules/not_in.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { notIn } from "../../src/rules/not_in.ts"; 3 | import { invalid } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test("rules.notIn([0, 1, 2])(0) should be invalid", () => { 7 | assertInvalid( 8 | notIn([0, 1, 2])(0, fakeUtils) as Validity, 9 | invalid("notIn", { value: 0, disallowedValues: [0, 1, 2] }), 10 | ); 11 | }); 12 | 13 | Deno.test("rules.notIn([0, 1, 2])(2) should be invalid", () => { 14 | assertInvalid( 15 | notIn([0, 1, 2])(2, fakeUtils) as Validity, 16 | invalid("notIn", { value: 2, disallowedValues: [0, 1, 2] }), 17 | ); 18 | }); 19 | 20 | Deno.test("rules.notIn([0, 1, 2])('0') should be valid", () => { 21 | assertValid(notIn([0, 1, 2])("0", fakeUtils) as Validity); 22 | }); 23 | 24 | Deno.test("rules.notIn([0, 1, 2])(null) should be valid", () => { 25 | assertValid(notIn([0, 1, 2])(null, fakeUtils) as Validity); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/rules/not_null.test.ts: -------------------------------------------------------------------------------- 1 | import { notNull } from "../../src/rules/not_null.ts"; 2 | import { invalid } from "../../src/utils.ts"; 3 | import { assertInvalid, assertValid } from "../utils.ts"; 4 | import { Validity } from "../../src/types.ts"; 5 | 6 | Deno.test("rules.notNull(null) should be invalid", () => { 7 | assertInvalid(notNull(null), invalid("notNull", { value: null }, true)); 8 | }); 9 | 10 | Deno.test("rules.notNull(0) should be valid", () => { 11 | assertValid(notNull(0)); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/rules/nullable.test.ts: -------------------------------------------------------------------------------- 1 | import { nullable } from "../../src/rules/nullable.ts"; 2 | import { invalid } from "../../src/utils.ts"; 3 | import { assertInvalid, assertValid } from "../utils.ts"; 4 | 5 | Deno.test("rules.nullable(undefined) should be invalid", () => { 6 | assertInvalid( 7 | nullable(undefined), 8 | invalid("nullable", { value: undefined }, true), 9 | ); 10 | }); 11 | 12 | Deno.test("rules.nullable(null) should be valid", () => { 13 | assertValid(nullable(null)); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/rules/number_between.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { numberBetween } from "../../src/rules/number_between.ts"; 3 | import { invalid } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test("rules.numberBetween(1, 5)(5.1) should be invalid", () => { 7 | assertInvalid( 8 | numberBetween(1, 5)(5.1, fakeUtils) as Validity, 9 | invalid("numberBetween", { value: 5.1, maxValue: 5, minValue: 1 }), 10 | ); 11 | }); 12 | 13 | Deno.test("rules.numberBetween(1, 5)(0.9) should be invalid", () => { 14 | assertInvalid( 15 | numberBetween(1, 5)(0.9, fakeUtils) as Validity, 16 | invalid("numberBetween", { value: 0.9, maxValue: 5, minValue: 1 }), 17 | ); 18 | }); 19 | 20 | Deno.test("rules.numberBetween(1, 5)(1) should be valid", () => { 21 | assertValid(numberBetween(1, 5)(1, fakeUtils) as Validity); 22 | }); 23 | 24 | Deno.test("rules.numberBetween(1, 5)(5) should be valid", () => { 25 | assertValid(numberBetween(1, 5)(5, fakeUtils) as Validity); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/rules/required.test.ts: -------------------------------------------------------------------------------- 1 | import { required } from "../../src/rules/required.ts"; 2 | import { invalid } from "../../src/utils.ts"; 3 | import { assertInvalid, assertValid } from "../utils.ts"; 4 | 5 | Deno.test("rules.required(null) should be invalid", () => { 6 | assertInvalid(required(null), invalid("required", { value: null }, true)); 7 | }); 8 | 9 | Deno.test("rules.required(undefined) should be invalid", () => { 10 | assertInvalid( 11 | required(undefined), 12 | invalid("required", { value: undefined }, true), 13 | ); 14 | }); 15 | 16 | Deno.test("rules.required('') should be invalid", () => { 17 | assertInvalid(required(""), invalid("required", { value: "" }, true)); 18 | }); 19 | 20 | Deno.test("rules.required(0) should be valid", () => { 21 | assertValid(required(0)); 22 | }); 23 | 24 | Deno.test("rules.required([]) should be valid", () => { 25 | assertValid(required([])); 26 | }); 27 | 28 | Deno.test("rules.required({}) should be valid", () => { 29 | assertValid(required({})); 30 | }); 31 | 32 | Deno.test("rules.required('0') should be valid", () => { 33 | assertValid(required("0")); 34 | }); 35 | -------------------------------------------------------------------------------- /tests/rules/required_if.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { requiredIf } from "../../src/rules/required_if.ts"; 3 | import { invalid, makeValidationUtils } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test( 7 | "rules.requiredIf('confirmed', '1')(null, { confirmed: '1' }) should be invalid", 8 | () => { 9 | const utils = makeValidationUtils({ 10 | confirmed: "1", 11 | }); 12 | 13 | assertInvalid( 14 | requiredIf("confirmed", "1")(null, utils) as Validity, 15 | invalid("required", { value: null }, true), 16 | ); 17 | }, 18 | ); 19 | 20 | Deno.test( 21 | "rules.requiredIf('confirmed', '1')(null, { confirmed: '0' }) should be valid", 22 | () => { 23 | const utils = makeValidationUtils({ 24 | confirmed: "0", 25 | }); 26 | 27 | assertValid(requiredIf("confirmed", "1")(null, utils) as Validity); 28 | }, 29 | ); 30 | 31 | Deno.test( 32 | "rules.requiredIf('confirmed', '1')(10, { confirmed: '1' }) should be valid", 33 | () => { 34 | const utils = makeValidationUtils({ 35 | confirmed: "1", 36 | }); 37 | 38 | assertValid(requiredIf("confirmed", "1")(10, utils) as Validity); 39 | }, 40 | ); 41 | -------------------------------------------------------------------------------- /tests/rules/required_unless.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { requiredUnless } from "../../src/rules/required_unless.ts"; 3 | import { invalid, makeValidationUtils } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test( 7 | "rules.requiredUnless('confirmed', '1')(null, { confirmed: '0' }) should be invalid", 8 | () => { 9 | const utils = makeValidationUtils({ 10 | confirmed: "0", 11 | }); 12 | 13 | assertInvalid( 14 | requiredUnless("confirmed", "1")(null, utils) as Validity, 15 | invalid("required", { value: null }, true), 16 | ); 17 | }, 18 | ); 19 | 20 | Deno.test( 21 | "rules.requiredUnless('confirmed', '1')(null, { confirmed: '1' }) should be valid", 22 | () => { 23 | const utils = makeValidationUtils({ 24 | confirmed: "1", 25 | }); 26 | 27 | assertValid(requiredUnless("confirmed", "1")(null, utils) as Validity); 28 | }, 29 | ); 30 | 31 | Deno.test( 32 | "rules.requiredUnless('confirmed', '1')(10, { confirmed: '0' }) should be valid", 33 | () => { 34 | const utils = makeValidationUtils({ 35 | confirmed: "0", 36 | }); 37 | 38 | assertValid(requiredUnless("confirmed", "1")(10, utils) as Validity); 39 | }, 40 | ); 41 | -------------------------------------------------------------------------------- /tests/rules/required_when.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { requiredWhen } from "../../src/rules/required_when.ts"; 3 | import { invalid, makeValidationUtils } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test( 7 | "rules.requiredWhen(() => true)(null) should be invalid", 8 | async () => { 9 | assertInvalid( 10 | (await requiredWhen(() => true)(null, fakeUtils)) as Validity, 11 | invalid("required", { value: null }, true), 12 | ); 13 | }, 14 | ); 15 | 16 | Deno.test("rules.requiredWhen(() => false)(null) should be valid", async () => { 17 | assertValid((await requiredWhen(() => false)(null, fakeUtils)) as Validity); 18 | }); 19 | 20 | Deno.test( 21 | "rules.requiredWhen((_, { getValue }) => getValue('x') == 1)(null, { x: '1' }) should be invalid", 22 | async () => { 23 | const utils = makeValidationUtils({ 24 | x: "1", 25 | }); 26 | 27 | assertInvalid( 28 | (await requiredWhen((_, { getValue }) => getValue("x") == 1)( 29 | null, 30 | utils, 31 | )) as Validity, 32 | invalid("required", { value: null }, true), 33 | ); 34 | }, 35 | ); 36 | 37 | Deno.test( 38 | "rules.requiredWhen((_, { getValue }) => getValue('x') == 1)(25, { x: '1' }) should be valid", 39 | async () => { 40 | const utils = makeValidationUtils({ 41 | x: "1", 42 | }); 43 | 44 | assertValid( 45 | (await requiredWhen((_, { getValue }) => getValue("x") == 1)( 46 | 25, 47 | utils, 48 | )) as Validity, 49 | ); 50 | }, 51 | ); 52 | -------------------------------------------------------------------------------- /tests/rules/starts_with.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../../src/types.ts"; 2 | import { startsWith } from "../../src/rules/starts_with.ts"; 3 | import { invalid } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | 6 | Deno.test("rules.startsWith('foo')(null) should be invalid", () => { 7 | assertInvalid( 8 | startsWith("foo")(null, fakeUtils) as Validity, 9 | invalid("startsWith", { value: null, str: "foo" }), 10 | ); 11 | }); 12 | 13 | Deno.test("rules.startsWith('foo')('bar') should be invalid", () => { 14 | assertInvalid( 15 | startsWith("foo")("bar", fakeUtils) as Validity, 16 | invalid("startsWith", { value: "bar", str: "foo" }), 17 | ); 18 | }); 19 | 20 | Deno.test("rules.startsWith('bar')('foobar') should be invalid", () => { 21 | assertInvalid( 22 | startsWith("bar")("foobar", fakeUtils) as Validity, 23 | invalid("startsWith", { value: "foobar", str: "bar" }), 24 | ); 25 | }); 26 | 27 | Deno.test("rules.startsWith('foo')('foobar') should be valid", () => { 28 | assertValid(startsWith("foo")("foobar", fakeUtils) as Validity); 29 | }); 30 | 31 | Deno.test("rules.startsWith('foobarbaz')('foobarbaz') should be valid", () => { 32 | assertValid(startsWith("foobarbaz")("foobarbaz", fakeUtils) as Validity); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/rules/validate_array.test.ts: -------------------------------------------------------------------------------- 1 | import type { Validity, Rule } from "../../src/types.ts"; 2 | import { validateArray } from "../../src/rules/validate_array.ts"; 3 | import { invalid } from "../../src/utils.ts"; 4 | import { assertInvalid, assertValid, fakeUtils } from "../utils.ts"; 5 | import { isNumber } from "../../src/rules/is_number.ts"; 6 | import { assertEquals, assertNotEquals } from "../deps.ts"; 7 | import { required } from "../../src/rules/required.ts"; 8 | 9 | Deno.test( 10 | "rules.validateArray(true, [isNumber]) should return required rule first", 11 | () => { 12 | const rules = validateArray(true, [isNumber]); 13 | assertEquals(rules[0], required); 14 | }, 15 | ); 16 | 17 | Deno.test( 18 | "rules.validateArray(false, [isNumber]) shouldn't return required rule first", 19 | () => { 20 | const rules = validateArray(false, [isNumber]); 21 | assertNotEquals(rules[0], required); 22 | }, 23 | ); 24 | 25 | Deno.test( 26 | "rules.validateArray(false, [isNumber])[0](null) should be valid", 27 | async () => { 28 | const rule = validateArray(false, [isNumber])[0] as Rule; 29 | assertNotEquals(rule, required); 30 | assertEquals(typeof rule, "function"); 31 | const result = (await rule(null, fakeUtils)) as Validity; 32 | assertValid(result); 33 | }, 34 | ); 35 | 36 | Deno.test( 37 | "rules.validateArray(true, [isNumber])[1]([]) should be valid", 38 | async () => { 39 | const rule = validateArray(true, [isNumber])[1] as Rule; 40 | assertNotEquals(rule, required); 41 | assertEquals(typeof rule, "function"); 42 | const result = (await rule([], fakeUtils)) as Validity; 43 | assertValid(result); 44 | }, 45 | ); 46 | 47 | Deno.test( 48 | "rules.validateArray(true, [isNumber], { minLength: 1 })[1]([]) should be invalid", 49 | async () => { 50 | const rule = validateArray(true, [isNumber], { minLength: 1 })[1] as Rule; 51 | assertNotEquals(rule, required); 52 | assertEquals(typeof rule, "function"); 53 | const result = (await rule([], fakeUtils)) as Validity; 54 | assertInvalid( 55 | result, 56 | invalid("validateArray:minLengthCheck", { value: [], minLength: 1 }), 57 | ); 58 | }, 59 | ); 60 | 61 | Deno.test( 62 | "rules.validateArray(true, [isNumber], { minLength: 1 })[1]([100]) should be valid", 63 | async () => { 64 | const rule = validateArray(true, [isNumber], { minLength: 1 })[1] as Rule; 65 | assertNotEquals(rule, required); 66 | assertEquals(typeof rule, "function"); 67 | const result = (await rule([100], fakeUtils)) as Validity; 68 | assertValid(result); 69 | }, 70 | ); 71 | 72 | Deno.test( 73 | "rules.validateArray(true, [isNumber], { maxLength: 3 })[1]([1, 2, 3]) should be valid", 74 | async () => { 75 | const rule = validateArray(true, [isNumber], { maxLength: 3 })[1] as Rule; 76 | assertNotEquals(rule, required); 77 | assertEquals(typeof rule, "function"); 78 | const result = (await rule([1, 2, 3], fakeUtils)) as Validity; 79 | assertValid(result); 80 | }, 81 | ); 82 | 83 | Deno.test( 84 | "rules.validateArray(true, [isNumber], { maxLength: 3 })[1]([1, 2, 3, 4]) should be invalid", 85 | async () => { 86 | const rule = validateArray(true, [isNumber], { maxLength: 3 })[1] as Rule; 87 | assertNotEquals(rule, required); 88 | assertEquals(typeof rule, "function"); 89 | const result = (await rule([1, 2, 3, 4], fakeUtils)) as Validity; 90 | assertInvalid( 91 | result, 92 | invalid("validateArray:maxLengthCheck", { 93 | value: [1, 2, 3, 4], 94 | maxLength: 3, 95 | }), 96 | ); 97 | }, 98 | ); 99 | -------------------------------------------------------------------------------- /tests/utils.test.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ValidationErrors, 3 | RawValidationResult, 4 | InputData, 5 | InvalidParams, 6 | } from "../src/interfaces.ts"; 7 | import * as utils from "../src/utils.ts"; 8 | import { assertEquals } from "./deps.ts"; 9 | import { required, isNumber, isInt, isIn, nullable } from "../src/rules.ts"; 10 | 11 | const sampleErrorMessages = (): ValidationErrors => ({ 12 | x: { 13 | rule1: "something wrong with x.rule1", 14 | rule2: "something wrong with x.rule2", 15 | }, 16 | obj: { 17 | validateObject: { 18 | x: { 19 | rule1: "something wrong with obj.x.rule1", 20 | rule2: "something wrong with obj.x.rule2", 21 | }, 22 | }, 23 | }, 24 | arr: { 25 | validateArray: { 26 | "1": { 27 | rule1: "something wrong with arr[1].rule1", 28 | rule2: "something wrong with arr[1].rule2", 29 | }, 30 | }, 31 | }, 32 | arrObj: { 33 | validateArray: { 34 | "1": { 35 | validateObject: { 36 | x: { 37 | rule1: "something wrong with arrObj[1].x.rule1", 38 | rule2: "something wrong with arrObj[1].x.rule2", 39 | }, 40 | }, 41 | }, 42 | }, 43 | }, 44 | }); 45 | 46 | Deno.test("utils.invalid()", () => { 47 | assertEquals(utils.invalid("foo", { x: 10, y: "bar" }, true), { 48 | rule: "foo", 49 | params: { 50 | x: 10, 51 | y: "bar", 52 | }, 53 | implicit: true, 54 | }); 55 | }); 56 | 57 | Deno.test("utils.isNullable()", () => { 58 | assertEquals( 59 | utils.isNullable([required, nullable, isNumber, isInt, isIn([1, 2, 3])]), 60 | true, 61 | `isNullable should be true when there is nullable rule`, 62 | ); 63 | assertEquals( 64 | utils.isNullable([required, isNumber, isInt, isIn([1, 2, 3])]), 65 | false, 66 | `isNullable should be false when there is no nullable rule`, 67 | ); 68 | }); 69 | 70 | Deno.test("utils.isOptional()", () => { 71 | assertEquals( 72 | utils.isOptional([isNumber, isInt, isIn([1, 2, 3])]), 73 | true, 74 | `isOptional should be true when there is no required rule`, 75 | ); 76 | assertEquals( 77 | utils.isOptional([required, isNumber, isInt, isIn([1, 2, 3])]), 78 | false, 79 | `isOptional should be false when there is required rule`, 80 | ); 81 | }); 82 | 83 | Deno.test("utils.isOptionalValue()", () => { 84 | assertEquals(utils.isOptionalValue(null), true); 85 | assertEquals(utils.isOptionalValue(undefined), true); 86 | assertEquals(utils.isOptionalValue(""), true); 87 | assertEquals(utils.isOptionalValue(0), false); 88 | assertEquals(utils.isOptionalValue([]), false); 89 | assertEquals(utils.isOptionalValue({}), false); 90 | }); 91 | 92 | Deno.test("utils.firstMessages()", () => { 93 | const errorMessages = sampleErrorMessages(); 94 | const result = utils.firstMessages(errorMessages); 95 | 96 | assertEquals(result, { 97 | x: "something wrong with x.rule1", 98 | obj: { 99 | x: "something wrong with obj.x.rule1", 100 | }, 101 | arr: { 102 | "1": "something wrong with arr[1].rule1", 103 | }, 104 | arrObj: { 105 | "1": { 106 | x: "something wrong with arrObj[1].x.rule1", 107 | }, 108 | }, 109 | }); 110 | }); 111 | 112 | Deno.test("utils.flattenMessages()", () => { 113 | const errorMessages = sampleErrorMessages(); 114 | const result = utils.flattenMessages(errorMessages); 115 | 116 | assertEquals(result, { 117 | "x.rule1": "something wrong with x.rule1", 118 | "x.rule2": "something wrong with x.rule2", 119 | x: "something wrong with x.rule1", 120 | "obj.x.rule1": "something wrong with obj.x.rule1", 121 | "obj.x.rule2": "something wrong with obj.x.rule2", 122 | "obj.x": "something wrong with obj.x.rule1", 123 | "arr.1.rule1": "something wrong with arr[1].rule1", 124 | "arr.1.rule2": "something wrong with arr[1].rule2", 125 | "arr.1": "something wrong with arr[1].rule1", 126 | "arrObj.1.x.rule1": "something wrong with arrObj[1].x.rule1", 127 | "arrObj.1.x.rule2": "something wrong with arrObj[1].x.rule2", 128 | "arrObj.1.x": "something wrong with arrObj[1].x.rule1", 129 | }); 130 | }); 131 | 132 | Deno.test("utils.findBestMessage()", () => { 133 | // Message templates sorted by priority 134 | const messageTemplates = { 135 | "x.rule1:stuff1": "x.rule1:stuff1 is invalid", 136 | "x.rule1": "x.rule1 is invalid", 137 | x: "x is invalid", 138 | "rule1:stuff1": "invalid rule1:stuff1", 139 | rule1: "invalid rule1", 140 | }; 141 | const defaultMsg = "default message"; 142 | 143 | assertEquals( 144 | utils.findBestMessage( 145 | messageTemplates, 146 | "x", 147 | "rule1:stuff1", 148 | "rule1", 149 | defaultMsg, 150 | ), 151 | "x.rule1:stuff1 is invalid", 152 | ); 153 | assertEquals( 154 | utils.findBestMessage( 155 | messageTemplates, 156 | "x", 157 | "rule1:stuffX", 158 | "rule1", 159 | defaultMsg, 160 | ), 161 | "x.rule1 is invalid", 162 | ); 163 | assertEquals( 164 | utils.findBestMessage( 165 | messageTemplates, 166 | "x", 167 | "ruleX:stuffY", 168 | "ruleX", 169 | defaultMsg, 170 | ), 171 | "x is invalid", 172 | ); 173 | assertEquals( 174 | utils.findBestMessage( 175 | messageTemplates, 176 | "y", 177 | "rule1:stuff1", 178 | "rule1", 179 | defaultMsg, 180 | ), 181 | "invalid rule1:stuff1", 182 | ); 183 | assertEquals( 184 | utils.findBestMessage( 185 | messageTemplates, 186 | "y", 187 | "rule1:stuffX", 188 | "rule1", 189 | defaultMsg, 190 | ), 191 | "invalid rule1", 192 | ); 193 | assertEquals( 194 | utils.findBestMessage( 195 | messageTemplates, 196 | "y", 197 | "ruleX:stuffY", 198 | "ruleX", 199 | defaultMsg, 200 | ), 201 | defaultMsg, 202 | ); 203 | }); 204 | 205 | Deno.test("utils.resolveErrorMessage()", () => { 206 | assertEquals( 207 | utils.resolveErrorMessage( 208 | ":attr can't be :value, it must be between :min-:max", 209 | { value: 10, min: 11, max: 15 }, 210 | "x", 211 | ), 212 | "x can't be 10, it must be between 11-15", 213 | ); 214 | }); 215 | 216 | Deno.test("utils.resolveErrorMessages()", () => { 217 | const sampleRawErrors: RawValidationResult = { 218 | x: [ 219 | { 220 | rule: "rule1", 221 | params: {}, 222 | implicit: false, 223 | }, 224 | { 225 | rule: "rule2", 226 | params: { a: 10 }, 227 | implicit: false, 228 | }, 229 | ], 230 | arr: [ 231 | { 232 | rule: "validateArray", 233 | params: { 234 | errors: { 235 | "1": [ 236 | { 237 | rule: "rule1", 238 | params: {}, 239 | implicit: false, 240 | }, 241 | { 242 | rule: "rule2", 243 | params: {}, 244 | implicit: false, 245 | }, 246 | ], 247 | }, 248 | }, 249 | implicit: true, 250 | }, 251 | ], 252 | obj: [ 253 | { 254 | rule: "validateObject", 255 | params: { 256 | errors: { 257 | x: [ 258 | { 259 | rule: "rule1", 260 | params: {}, 261 | implicit: false, 262 | }, 263 | { 264 | rule: "rule2", 265 | params: {}, 266 | implicit: false, 267 | }, 268 | ], 269 | }, 270 | }, 271 | implicit: true, 272 | }, 273 | ], 274 | arrObj: [ 275 | { 276 | rule: "validateArray", 277 | params: { 278 | errors: { 279 | "1": [ 280 | { 281 | rule: "validateObject", 282 | params: { 283 | errors: { 284 | x: [ 285 | { 286 | rule: "rule1", 287 | params: {}, 288 | implicit: false, 289 | }, 290 | { 291 | rule: "rule2", 292 | params: {}, 293 | implicit: false, 294 | }, 295 | ], 296 | }, 297 | }, 298 | implicit: true, 299 | }, 300 | ], 301 | }, 302 | }, 303 | implicit: true, 304 | }, 305 | ], 306 | }; 307 | 308 | const result = utils.resolveErrorMessages(sampleRawErrors, { 309 | messages: { 310 | rule1: "invalid rule1", 311 | rule2: "invalid rule2", 312 | }, 313 | }); 314 | 315 | assertEquals(result, { 316 | x: { 317 | rule1: "invalid rule1", 318 | rule2: "invalid rule2", 319 | }, 320 | arr: { 321 | validateArray: { 322 | "1": { 323 | rule1: "invalid rule1", 324 | rule2: "invalid rule2", 325 | }, 326 | }, 327 | }, 328 | obj: { 329 | validateObject: { 330 | x: { 331 | rule1: "invalid rule1", 332 | rule2: "invalid rule2", 333 | }, 334 | }, 335 | }, 336 | arrObj: { 337 | validateArray: { 338 | "1": { 339 | validateObject: { 340 | x: { 341 | rule1: "invalid rule1", 342 | rule2: "invalid rule2", 343 | }, 344 | }, 345 | }, 346 | }, 347 | }, 348 | }); 349 | }); 350 | 351 | Deno.test("utils.isStringInt", () => { 352 | assertEquals(utils.isStringInt("123"), true, "'123' should be string int"); 353 | assertEquals( 354 | utils.isStringInt("12a"), 355 | false, 356 | "'12a' shouldn't be string int", 357 | ); 358 | assertEquals( 359 | utils.isStringInt("a12"), 360 | false, 361 | "'a12' shouldn't be string int", 362 | ); 363 | assertEquals( 364 | utils.isStringInt("12.5"), 365 | false, 366 | "'12.5' shouldn't be string int", 367 | ); 368 | assertEquals(utils.isStringInt("0.1"), false, "'0.1' should't be string int"); 369 | assertEquals(utils.isStringInt(".1"), false, "'.1' shouldn't be string int"); 370 | }); 371 | 372 | Deno.test("utils.getValue", () => { 373 | const data: InputData = { 374 | str: "foo", 375 | obj: { 376 | str: "bar", 377 | }, 378 | num: 12, 379 | empty: null, 380 | arrObj: [ 381 | { 382 | str: "baz", 383 | }, 384 | { 385 | str: "qux", 386 | }, 387 | ], 388 | }; 389 | 390 | assertEquals(utils.getValue(data, "str"), "foo", "data.str shouldbe 'foo'"); 391 | assertEquals( 392 | utils.getValue(data, "obj"), 393 | { str: "bar" }, 394 | "data.obj should be {str: 'bar'}", 395 | ); 396 | assertEquals( 397 | utils.getValue(data, "obj.str"), 398 | "bar", 399 | "data.obj.str should be 'bar'", 400 | ); 401 | assertEquals( 402 | utils.getValue(data, "obj.nothing"), 403 | undefined, 404 | "data.obj.nothing should be undefined", 405 | ); 406 | assertEquals( 407 | utils.getValue(data, "obj.1.x"), 408 | undefined, 409 | "data.1.x should be undefined", 410 | ); 411 | assertEquals(utils.getValue(data, "num"), 12, "data.num should be 12"); 412 | assertEquals( 413 | utils.getValue(data, "num.nothing"), 414 | undefined, 415 | "data.num.nothing should be undefined", 416 | ); 417 | assertEquals( 418 | utils.getValue(data, "empty"), 419 | null, 420 | "data.empty should be null", 421 | ); 422 | assertEquals( 423 | utils.getValue(data, "empty.nothing"), 424 | undefined, 425 | "data.empty.nothing should be undefined", 426 | ); 427 | assertEquals( 428 | utils.getValue(data, "arrObj.0"), 429 | { str: "baz" }, 430 | "arrObj.0 should be {str: 'baz'}", 431 | ); 432 | assertEquals( 433 | utils.getValue(data, "arrObj.0.str"), 434 | "baz", 435 | "arrObj.0.str should be 'baz'", 436 | ); 437 | assertEquals( 438 | utils.getValue(data, "arrObj.1"), 439 | { str: "qux" }, 440 | "arrObj.1 should be {str: 'qux'}", 441 | ); 442 | assertEquals( 443 | utils.getValue(data, "arrObj.1.str"), 444 | "qux", 445 | "arrObj.1.str should be 'qux'", 446 | ); 447 | assertEquals( 448 | utils.getValue(data, "arrObj.nothing"), 449 | undefined, 450 | "arrObj.nothing should be undefined", 451 | ); 452 | assertEquals( 453 | utils.getValue(data, "arrObj.2"), 454 | undefined, 455 | "arrObj.2 should be undefined", 456 | ); 457 | }); 458 | 459 | Deno.test("utils.getCheckType", () => { 460 | assertEquals(utils.getCheckType("foo"), ""); 461 | assertEquals(utils.getCheckType("foo:bar"), "bar"); 462 | assertEquals(utils.getCheckType("foo:barBaz"), "barBaz"); 463 | assertEquals(utils.getCheckType("foo:barBaz:qux"), "barBaz:qux"); 464 | }); 465 | 466 | Deno.test("utils.resolveErrorMessage() with MessageFunction", () => { 467 | const message = (params: InvalidParams, checkType?: string): string => { 468 | switch (checkType) { 469 | case "numberCheck": 470 | return `Value must be a number, ${typeof params.value} given`; 471 | default: 472 | return `${params.attr} can't be ${params.value}, it must be between ${params.min}-${params.max}`; 473 | } 474 | }; 475 | 476 | assertEquals( 477 | utils.resolveErrorMessage(message, { value: 10, min: 11, max: 15 }, "x"), 478 | "x can't be 10, it must be between 11-15", 479 | ); 480 | 481 | assertEquals( 482 | utils.resolveErrorMessage(message, { value: "10" }, "x", "numberCheck"), 483 | "Value must be a number, string given", 484 | ); 485 | }); 486 | 487 | Deno.test("utils.clearTimes()", () => { 488 | const input = new Date("2020-12-25 20:30:40:50"); 489 | const expect = new Date("2020-12-25 00:00:00:00"); 490 | assertEquals(utils.clearTimes(input), expect); 491 | }); 492 | -------------------------------------------------------------------------------- /tests/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Validity } from "../src/types.ts"; 2 | import type { 3 | InvalidPayload, 4 | ValidationUtils, 5 | OptionalValidity, 6 | } from "../src/interfaces.ts"; 7 | import { assertEquals, assertNotEquals } from "./deps.ts"; 8 | 9 | export const assertInvalid = ( 10 | result: Validity, 11 | payload: InvalidPayload, 12 | message?: string, 13 | ): void => { 14 | assertNotEquals(result, undefined, message || "Result should be invalid"); 15 | if (result !== undefined) { 16 | assertEquals(result.rule, payload.rule); 17 | assertEquals(result.params, payload.params); 18 | assertEquals( 19 | result.implicit, 20 | payload.implicit, 21 | `Result implicity should be: ${result.implicit ? "true" : "false"}`, 22 | ); 23 | } 24 | }; 25 | 26 | export const assertValid = (result: Validity, message?: string): void => { 27 | assertEquals( 28 | result === undefined || (result as OptionalValidity).noContext, 29 | true, 30 | message || "Result should be valid", 31 | ); 32 | }; 33 | 34 | export const fakeUtils: ValidationUtils = { 35 | getValue: (key: string): any => undefined, 36 | hasValue: (key: string): boolean => false, 37 | }; 38 | -------------------------------------------------------------------------------- /tests/validate.test.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | InputData, 3 | ValidationRules, 4 | ValidationMessages, 5 | ValidationErrors, 6 | } from "../src/interfaces.ts"; 7 | import { validate, validateData, validateValue } from "../src/validate.ts"; 8 | import { assertEquals } from "./deps.ts"; 9 | import { 10 | required, 11 | isInt, 12 | isNumber, 13 | isString, 14 | validateArray, 15 | validateObject, 16 | nullable, 17 | requiredIf, 18 | requiredWhen, 19 | requiredUnless, 20 | } from "../src/rules.ts"; 21 | import { invalid } from "../src/utils.ts"; 22 | import { fakeUtils } from "./utils.ts"; 23 | 24 | const advancedExample = (): { 25 | input: InputData; 26 | rules: ValidationRules; 27 | messages: ValidationMessages; 28 | } => { 29 | return { 30 | input: { 31 | invalidOnRequired: null, 32 | invalidOnNumber: "2", 33 | multipleRuleFails: "10", 34 | arrayFailAtRequired: null, 35 | arrayFailAtIndex1And3: [0, "1", 2, 3.5], 36 | objectFailAtX: { x: null, y: 12 }, 37 | objectFailAtY: { x: 10, y: "12" }, 38 | optionalArrayPasses: null, 39 | optionalObjectPasses: null, 40 | nestedObjectFailAtFooBarBaz: { 41 | foo: { 42 | a: 10, 43 | b: "thing", 44 | c: { c1: 1, c2: 2 }, 45 | bar: { 46 | d: 11, 47 | baz: null, 48 | }, 49 | }, 50 | }, 51 | }, 52 | rules: { 53 | invalidOnRequired: [required, isNumber], 54 | invalidOnNumber: [required, isNumber], 55 | multipleRuleFails: [required, isNumber, isInt], 56 | arrayFailAtRequired: validateArray(true, [isNumber, isInt]), 57 | arrayFailAtIndex1And3: validateArray(true, [isNumber, isInt]), 58 | objectFailAtX: validateObject(true, { 59 | x: [required, isNumber], 60 | y: [required, isNumber], 61 | }), 62 | objectFailAtY: validateObject(true, { 63 | x: [required, isNumber], 64 | y: [required, isNumber], 65 | }), 66 | optionalArrayPasses: validateArray(false, [isNumber]), 67 | optionalObjectPasses: validateObject(false, { 68 | x: [required, isNumber], 69 | y: [required, isNumber], 70 | }), 71 | nestedObjectFailAtFooBarBaz: validateObject(true, { 72 | foo: validateObject(true, { 73 | bar: validateObject(true, { 74 | baz: required, 75 | }), 76 | }), 77 | }), 78 | }, 79 | messages: { 80 | default: ":value is invalid", 81 | }, 82 | }; 83 | }; 84 | 85 | Deno.test( 86 | "validateValue() on empty value with non-required rules should be passes", 87 | async () => { 88 | const errs = await validateValue( 89 | null, 90 | [isString, isNumber, isInt], 91 | fakeUtils, 92 | ); 93 | assertEquals(errs.length, 0); 94 | }, 95 | ); 96 | 97 | Deno.test( 98 | "validateValue() on null value with nullable rules should be passes", 99 | async () => { 100 | const errs = await validateValue( 101 | null, 102 | [required, nullable, isString, isNumber, isInt], 103 | fakeUtils, 104 | ); 105 | assertEquals(errs.length, 0); 106 | }, 107 | ); 108 | 109 | Deno.test( 110 | "validateValue() on empty value with required rules should be error", 111 | async () => { 112 | const errs = await validateValue( 113 | null, 114 | [required, isString, isNumber, isInt], 115 | fakeUtils, 116 | ); 117 | assertEquals(errs.length, 1); 118 | }, 119 | ); 120 | 121 | Deno.test( 122 | "validateValue() on implicit rule should returns only 1 error message", 123 | async () => { 124 | const errs = await validateValue( 125 | null, 126 | [required, isNumber, isInt], 127 | fakeUtils, 128 | ); 129 | assertEquals(errs.length, 1); 130 | assertEquals(errs[0].rule, "required"); 131 | }, 132 | ); 133 | 134 | Deno.test( 135 | "validateValue() on non-implicit rule should returns all error message", 136 | async () => { 137 | const errs = await validateValue( 138 | "text", 139 | [isString, isNumber, isInt], 140 | fakeUtils, 141 | ); 142 | assertEquals(errs.length, 2); 143 | assertEquals(errs[0].rule, "isNumber"); 144 | assertEquals(errs[1].rule, "isInt"); 145 | }, 146 | ); 147 | 148 | Deno.test( 149 | "validateData() advanced example should return as expected", 150 | async () => { 151 | const { input, rules } = advancedExample(); 152 | const errors = await validateData(input, rules); 153 | const expected = { 154 | invalidOnRequired: [invalid("required", { value: null }, true)], 155 | invalidOnNumber: [invalid("isNumber", { value: "2" }, false)], 156 | multipleRuleFails: [ 157 | invalid("isNumber", { value: "10" }, false), 158 | invalid("isInt", { value: "10" }, false), 159 | ], 160 | arrayFailAtRequired: [invalid("required", { value: null }, true)], 161 | arrayFailAtIndex1And3: [ 162 | invalid( 163 | "validateArray", 164 | { 165 | value: [0, "1", 2, 3.5], 166 | errors: { 167 | "1": [ 168 | invalid("isNumber", { value: "1" }, false), 169 | invalid("isInt", { value: "1" }, false), 170 | ], 171 | "3": [invalid("isInt", { value: 3.5 }, false)], 172 | }, 173 | }, 174 | true, 175 | ), 176 | ], 177 | objectFailAtX: [ 178 | invalid( 179 | "validateObject", 180 | { 181 | value: { x: null, y: 12 }, 182 | errors: { 183 | x: [invalid("required", { value: null }, true)], 184 | }, 185 | }, 186 | true, 187 | ), 188 | ], 189 | objectFailAtY: [ 190 | invalid( 191 | "validateObject", 192 | { 193 | value: { x: 10, y: "12" }, 194 | errors: { 195 | y: [invalid("isNumber", { value: "12" }, false)], 196 | }, 197 | }, 198 | true, 199 | ), 200 | ], 201 | nestedObjectFailAtFooBarBaz: [ 202 | invalid( 203 | "validateObject", 204 | { 205 | value: { 206 | foo: { 207 | a: 10, 208 | b: "thing", 209 | c: { c1: 1, c2: 2 }, 210 | bar: { 211 | d: 11, 212 | baz: null, 213 | }, 214 | }, 215 | }, 216 | errors: { 217 | foo: [ 218 | invalid( 219 | "validateObject", 220 | { 221 | value: { 222 | a: 10, 223 | b: "thing", 224 | c: { c1: 1, c2: 2 }, 225 | bar: { 226 | d: 11, 227 | baz: null, 228 | }, 229 | }, 230 | errors: { 231 | bar: [ 232 | invalid( 233 | "validateObject", 234 | { 235 | value: { 236 | d: 11, 237 | baz: null, 238 | }, 239 | errors: { 240 | baz: [invalid("required", { value: null }, true)], 241 | }, 242 | }, 243 | true, 244 | ), 245 | ], 246 | }, 247 | }, 248 | true, 249 | ), 250 | ], 251 | }, 252 | }, 253 | true, 254 | ), 255 | ], 256 | }; 257 | 258 | // Deno.writeTextFileSync("_errors.json", JSON.stringify(errors, null, 4)); 259 | assertEquals(errors, expected); 260 | }, 261 | ); 262 | 263 | Deno.test("validate() advanced example should return as expected", async () => { 264 | const { input, rules, messages } = advancedExample(); 265 | const [passes, errors] = await validate(input, rules, { messages }); 266 | assertEquals(passes, false); 267 | 268 | const expected: ValidationErrors = { 269 | invalidOnRequired: { required: "null is invalid" }, 270 | invalidOnNumber: { isNumber: "2 is invalid" }, 271 | multipleRuleFails: { 272 | isNumber: "10 is invalid", 273 | isInt: "10 is invalid", 274 | }, 275 | arrayFailAtRequired: { 276 | required: "null is invalid", 277 | }, 278 | arrayFailAtIndex1And3: { 279 | validateArray: { 280 | "1": { 281 | isNumber: "1 is invalid", 282 | isInt: "1 is invalid", 283 | }, 284 | "3": { 285 | isInt: "3.5 is invalid", 286 | }, 287 | }, 288 | }, 289 | objectFailAtX: { 290 | validateObject: { 291 | x: { 292 | required: "null is invalid", 293 | }, 294 | }, 295 | }, 296 | objectFailAtY: { 297 | validateObject: { 298 | y: { 299 | isNumber: "12 is invalid", 300 | }, 301 | }, 302 | }, 303 | nestedObjectFailAtFooBarBaz: { 304 | validateObject: { 305 | foo: { 306 | validateObject: { 307 | bar: { 308 | validateObject: { 309 | baz: { 310 | required: "null is invalid", 311 | }, 312 | }, 313 | }, 314 | }, 315 | }, 316 | }, 317 | }, 318 | }; 319 | 320 | // Deno.writeTextFileSync("_errors.json", JSON.stringify(errors, null, 4)); 321 | assertEquals(errors, expected); 322 | }); 323 | 324 | Deno.test( 325 | "validate() with conditionally required fields should return expected results", 326 | async () => { 327 | const rules = { 328 | test1_1: [requiredIf("dep1", undefined), isString], 329 | test1_2: [requiredIf("dep1", undefined), isString], 330 | test1_3: [requiredIf("dep1", undefined), isString], 331 | test1_4: [requiredIf("dep1", "not-null"), isString], 332 | test1_5: [requiredIf("dep1", "not-null"), isString], 333 | test2_1: [requiredUnless("dep2", 1), isString], 334 | test2_2: [requiredUnless("dep2", 1), isString], 335 | test2_3: [requiredUnless("dep2", 1), isString], 336 | test2_4: [requiredUnless("dep2", 2), isString], 337 | test2_5: [requiredUnless("dep2", 2), isString], 338 | test3_1: [requiredWhen(() => true), isString], 339 | test3_2: [requiredWhen(() => true), isString], 340 | test3_3: [requiredWhen(() => true), isString], 341 | test3_4: [requiredWhen(() => false), isString], 342 | test3_5: [requiredWhen(() => false), isString], 343 | }; 344 | const input = { 345 | dep1: undefined, 346 | test1_1: undefined, 347 | test1_2: 1, 348 | test1_3: "t3", 349 | test1_4: 3, 350 | test1_5: undefined, 351 | dep2: 2, 352 | test2_1: undefined, 353 | test2_2: 1, 354 | test2_3: "t3", 355 | test2_4: 3, 356 | test2_5: undefined, 357 | test3_1: undefined, 358 | test3_2: 1, 359 | test3_3: "t3", 360 | test3_4: 3, 361 | test3_5: undefined, 362 | }; 363 | 364 | const messages = { 365 | default: ":value is invalid", 366 | }; 367 | 368 | const expected = { 369 | test1_1: { required: "undefined is invalid" }, 370 | test1_2: { isString: "1 is invalid" }, 371 | test1_4: { isString: "3 is invalid" }, 372 | test2_1: { required: "undefined is invalid" }, 373 | test2_2: { isString: "1 is invalid" }, 374 | test2_4: { isString: "3 is invalid" }, 375 | test3_1: { required: "undefined is invalid" }, 376 | test3_2: { isString: "1 is invalid" }, 377 | test3_4: { isString: "3 is invalid" }, 378 | }; 379 | 380 | const [passes, errors] = await validate(input, rules, { messages }); 381 | assertEquals(passes, false); 382 | 383 | assertEquals(errors, expected); 384 | }, 385 | ); 386 | -------------------------------------------------------------------------------- /validasaur.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 38 | 40 | 41 | 43 | image/svg+xml 44 | 46 | 47 | 48 | 49 | 50 | 55 | 58 | 60 | 63 | 66 | 71 | 75 | 79 | 83 | 87 | 91 | 95 | 99 | 103 | 107 | 111 | 112 | 118 | 121 | 129 | 137 | 144 | 151 | 153 | 158 | 162 | 163 | 165 | 170 | 174 | 175 | 181 | 187 | 194 | 199 | 200 | 201 | 202 | 206 | 210 | 211 | 212 | 213 | --------------------------------------------------------------------------------