├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .husky
├── .gitignore
└── pre-commit
├── .idea
├── .gitignore
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── dictionaries
│ └── project.xml
├── fluentvalidation.iml
├── inspectionProfiles
│ └── Project_Default.xml
├── jsLinters
│ └── eslint.xml
├── modules.xml
├── prettier.xml
├── runConfigurations
│ └── All_Tests.xml
└── vcs.xml
├── .prettierrc.json
├── .vscode
└── settings.json
├── License.txt
├── README.md
├── docs
├── api
│ ├── configuration
│ │ ├── unless.md
│ │ ├── when.md
│ │ └── withMessage.md
│ ├── core
│ │ ├── asyncValidator.md
│ │ ├── ruleFor.md
│ │ ├── ruleForEach.md
│ │ ├── ruleForEachTransformed.md
│ │ ├── ruleForTransformed.md
│ │ ├── validationErrors.md
│ │ └── validator.md
│ └── rules
│ │ ├── emailAddress.md
│ │ ├── equal.md
│ │ ├── exclusiveBetween.md
│ │ ├── greaterThan.md
│ │ ├── greaterThanOrEqualTo.md
│ │ ├── inclusiveBetween.md
│ │ ├── length.md
│ │ ├── lessThan.md
│ │ ├── lessThanOrEqualTo.md
│ │ ├── matches.md
│ │ ├── maxLength.md
│ │ ├── minLength.md
│ │ ├── must.md
│ │ ├── mustAsync.md
│ │ ├── notEmpty.md
│ │ ├── notEqual.md
│ │ ├── notNull.md
│ │ ├── notUndefined.md
│ │ ├── null.md
│ │ ├── precisionScale.md
│ │ ├── setAsyncValidator.md
│ │ ├── setValidator.md
│ │ └── undefined.md
├── guides
│ ├── ambientContext.md
│ ├── arrayProperties.md
│ ├── customRules.md
│ ├── formik.md
│ ├── objectProperties.md
│ └── reactHookForm.md
├── overview.md
└── tutorial.md
├── eslint.config.mjs
├── jest.config.ts
├── logo.png
├── package-lock.json
├── package.json
├── src
├── AsyncValidator.ts
├── IAsyncValidator.ts
├── IValidator.ts
├── SyncValidator.ts
├── ValidationErrors.ts
├── ValueValidationResult.ts
├── ValueValidator.ts
├── index.ts
├── numberHelpers.ts
├── rules
│ ├── AsyncRule.ts
│ ├── AsyncValidatorRule.ts
│ ├── CoreRule.ts
│ ├── EmailAddressRule.ts
│ ├── EqualRule.ts
│ ├── ExclusiveBetweenRule.ts
│ ├── GreaterThanOrEqualToRule.ts
│ ├── GreaterThanRule.ts
│ ├── InclusiveBetweenRule.ts
│ ├── LengthRule.ts
│ ├── LessThanOrEqualToRule.ts
│ ├── LessThanRule.ts
│ ├── MatchesRule.ts
│ ├── MaxLengthRule.ts
│ ├── MinLengthRule.ts
│ ├── MustAsyncRule.ts
│ ├── MustRule.ts
│ ├── NotEmptyRule.ts
│ ├── NotEqualRule.ts
│ ├── NotNullRule.ts
│ ├── NotUndefinedRule.ts
│ ├── NullRule.ts
│ ├── PrecisionScaleRule.ts
│ ├── Rule.ts
│ ├── UndefinedRule.ts
│ └── ValidatorRule.ts
├── types
│ ├── AppliesTo.ts
│ ├── ArrayType.ts
│ ├── Constrain.ts
│ ├── FlatType.ts
│ ├── IfNotNeverThen.ts
│ ├── IfType.ts
│ ├── Message.ts
│ ├── Optional.ts
│ ├── Predicate.ts
│ └── TransformedValue.ts
└── valueValidator
│ ├── ArrayValueValidatorBuilder.ts
│ ├── AsyncArrayValueValidatorBuilder.ts
│ ├── AsyncRuleValidators.ts
│ ├── AsyncValueValidator.ts
│ ├── AsyncValueValidatorBuilder.ts
│ ├── CoreValueValidatorBuilder.ts
│ ├── RuleValidators.ts
│ ├── ValueTransformer.ts
│ ├── ValueValidator.ts
│ ├── ValueValidatorBuilder.ts
│ └── ValueValidatorBuilderTypes.ts
├── test
├── baseValidatorsAsync.test.ts
├── baseValidatorsSync.test.ts
├── examplesAsync.test.ts
├── examplesSync.test.ts
├── index.test.ts
├── numberHelpers.test.ts
├── numberValidatorsAsync.test.ts
├── numberValidatorsSync.test.ts
├── ruleForEach.test.ts
├── ruleForEachTransformed.test.ts
├── ruleForTransformed.test.ts
├── stringValidators.test.ts
├── stringValidatorsAsync.test.ts
├── testHelpers.ts
├── unless.test.ts
├── when.test.ts
└── withMessage.test.ts
├── tsconfig.json
├── tsconfig.test.json
└── website
├── docusaurus.config.js
├── package-lock.json
├── package.json
├── sidebars.json
├── src
├── css
│ └── customTheme.css
├── pages
│ ├── help.js
│ └── index.js
└── theme
│ └── Root.js
└── static
├── css
└── code-block-buttons.css
├── img
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── apple-touch-icon.png
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
├── logo-outlined.svg
├── logo-text.svg
├── logo.svg
└── site.webmanifest
└── js
└── code-block-buttons.js
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: ['*']
6 | pull_request:
7 | branches: ['*']
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | node-version: [22.x]
16 |
17 | steps:
18 | - uses: actions/checkout@v4
19 | - name: Use Node.js ${{ matrix.node-version }}
20 | uses: actions/setup-node@v4
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 | cache: 'npm'
24 | - run: npm ci
25 | - run: npm run typecheck
26 | - run: npm run prettier:check
27 | - run: npm run lint:check
28 | - run: npm test
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | .rts2_cache_cjs
5 | .rts2_cache_esm
6 | .rts2_cache_umd
7 | dist
8 |
9 | website/translated_docs
10 | website/build
11 | website/node_modules
12 | website/i18n
13 | website/.docusaurus
14 |
15 | coverage/
16 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | npx --no-install pretty-quick
2 | npx pretty-quick --staged
3 |
4 | npm run lint
5 |
6 | npm run typecheck
7 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /../../../../../:\workspace\my\fluentvalidation\.idea/dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/dictionaries/project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | tseslint
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/fluentvalidation.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/jsLinters/eslint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/prettier.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/All_Tests.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "printWidth": 100,
4 | "tabWidth": 2,
5 | "useTabs": false,
6 | "trailingComma": "all",
7 | "singleQuote": true
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.tabSize": 2,
3 | "typescript.tsdk": "node_modules\\typescript\\lib"
4 | }
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # fluentvalidation-ts
2 |
3 | [](https://github.com/AlexJPotter/fluentvalidation-ts/actions/workflows/ci.yml)
4 | 
5 | 
6 | [](https://unpkg.com/fluentvalidation-ts@latest/dist/index.js)
7 |
8 | [](https://www.npmjs.com/package/fluentvalidation-ts)
9 | 
10 | 
11 | [](https://github.com/AlexJPotter/fluentvalidation-ts/issues)
12 |
13 | ## Strong, simple, extensible validation.
14 |
15 | Visit [https://fluentvalidation-ts.alexpotter.dev](https://fluentvalidation-ts.alexpotter.dev) to get started.
16 |
17 | ## Overview
18 |
19 | Front-end validation is a must-have for any project that involves forms, but the requirements vary hugely. You might have a simple sign-up form with a few text fields, or a complex configuration page with collections and deeply nested fields.
20 |
21 | There are plenty of libraries out there which help you to solve the problem of front-end validation, but all the ones I've tried have felt lacking in one aspect or another - whether that's TypeScript support, their capacity to handle complex requirements, or the ability to define your own reusable validation logic.
22 |
23 | So I wrote **fluentvalidation-ts**, a tiny library that is:
24 |
25 | - Designed for **TypeScript**
26 | - Simple yet powerful
27 | - Fully extensible
28 |
29 | Whatever your validation needs, **fluentvalidation-ts** can handle them.
30 |
31 | ## Docs
32 |
33 | Full documentation, including a tutorial and a number of useful guides, is available on the [documentation website](https://fluentvalidation-ts.alexpotter.dev).
34 |
35 | - [Overview](https://fluentvalidation-ts.alexpotter.dev/docs/overview)
36 | - [Tutorial](https://fluentvalidation-ts.alexpotter.dev/docs/tutorial)
37 | - [Guides](https://fluentvalidation-ts.alexpotter.dev/docs/guides/customrules)
38 | - [Core API Reference](https://fluentvalidation-ts.alexpotter.dev/docs/api/core/validator)
39 | - [Validation Rules API Reference](https://fluentvalidation-ts.alexpotter.dev/docs/api/rules/emailaddress)
40 | - [Releases](https://github.com/AlexJPotter/fluentvalidation-ts/releases)
41 |
42 | ### Requirements
43 |
44 | This library has been written in, and for, **TypeScript**. You can still use **fluentvalidation-ts** without TypeScript, but the primary benefit of having strongly-typed validation rules is lost.
45 |
46 | If using TypeScript (strongly recommended), you must be on TypeScript version **`2.9`** or later.
47 |
48 | ### Installation
49 |
50 | Using NPM:
51 |
52 | ```
53 | npm i --save fluentvalidation-ts
54 | ```
55 |
56 | Using Yarn:
57 |
58 | ```
59 | yarn add fluentvalidation-ts
60 | ```
61 |
62 | > [!TIP]
63 | > There's no need to install types separately - **fluentvalidation-ts** has been written with first-class support for TypeScript!
64 |
65 | ### Example Usage
66 |
67 | ```typescript
68 | import { Validator } from 'fluentvalidation-ts';
69 |
70 | type Person = {
71 | name: string;
72 | age: number;
73 | };
74 |
75 | class PersonValidator extends Validator {
76 | constructor() {
77 | super();
78 |
79 | this.ruleFor('name') // This is type-safe! (Argument is of type 'name' | 'age')
80 | .notEmpty()
81 | .withMessage('Please enter your name');
82 |
83 | this.ruleFor('age').greaterThanOrEqualTo(0).withMessage('Age cannot be negative');
84 | }
85 | }
86 |
87 | const validator = new PersonValidator();
88 |
89 | validator.validate({ name: '', age: 25 });
90 | // { name: 'Please enter your name' }
91 |
92 | validator.validate({ name: 'Alex', age: -1 });
93 | // { age: 'Age cannot be negative' }
94 |
95 | validator.validate({ name: '', age: -1 });
96 | // { name: 'Please enter your name', age: 'Age cannot be negative' }
97 | ```
98 |
99 | ### Test Coverage
100 |
101 | **fluentvalidation-ts** has 100% test coverage via unit tests written with [Jest](https://jestjs.io/).
102 |
103 | > [!NOTE]
104 | > Some branches are incorrectly reported as uncovered due to the following issue: [https://github.com/gotwarlost/istanbul/issues/690](https://github.com/gotwarlost/istanbul/issues/690).
105 |
106 | ### Issues
107 |
108 | Please report issues via [GitHub](https://github.com/AlexJPotter/fluentvalidation-ts/issues).
109 |
110 | ### License
111 |
112 | **fluentvalidation-ts** is provided under the terms of an [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) license.
113 |
114 | ### Development
115 |
116 | Clone the repo and run `npm install`, then run `npm run watch` in the root of the project to start the TypeScript compiler in watch mode. You can run the tests with `npm test`.
117 |
118 | ### About the Author
119 |
120 | Alex Potter is a full-stack Software Engineer, currently working as a Technical Lead at [Ghyston](https://www.ghyston.com), an award-winning software development company based in Bristol.
121 |
--------------------------------------------------------------------------------
/docs/api/configuration/unless.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: unless
3 | title: '.unless'
4 | ---
5 |
6 | The `.unless` option is used to control when a rule or chain of rules should **not** execute.
7 |
8 | By default, the `.unless` option will apply to all rules in the chain so far, but you can pass a second parameter to specify that it should only apply to the rule immediately preceding it.
9 |
10 | :::note
11 |
12 | In the case that there are multiple `.when` and/or `.unless` conditions in the rule chain, each condition applies only to the rules defined **between it and the previous condition**.
13 |
14 | :::
15 |
16 | ## Examples
17 |
18 | ### Apply to all rules in the chain so far
19 |
20 | In this example we apply an `.unless` condition to an entire rule chain.
21 |
22 | In particular, we validate that the delivery note has been entered and is no more than 1,000 characters long unless it has been specified that a delivery note is not required.
23 |
24 | ```typescript
25 | import { Validator } from 'fluentvalidation-ts';
26 |
27 | type FormModel = {
28 | doesNotRequireDeliveryNote: boolean;
29 | deliveryNote: string | null;
30 | };
31 |
32 | class FormValidator extends Validator {
33 | constructor() {
34 | super();
35 |
36 | this.ruleFor('deliveryNote')
37 | .notNull()
38 | .notEmpty()
39 | .maxLength(1000)
40 | // highlight-next-line
41 | .unless((formModel) => formModel.doesNotRequireDeliveryNote);
42 | }
43 | }
44 |
45 | const formValidator = new FormValidator();
46 |
47 | formValidator.validate({
48 | doesNotRequireDeliveryNote: true,
49 | deliveryNote: null,
50 | });
51 | // ✔ {}
52 |
53 | formValidator.validate({
54 | doesNotRequireDeliveryNote: false,
55 | deliveryNote: null,
56 | });
57 | // ❌ { deliveryNote: 'Value cannot be null' }
58 | ```
59 |
60 | ### Multiple calls within the same chain
61 |
62 | In this example we apply multiple `.unless` conditions within the same rule chain.
63 |
64 | In particular, we validate that the account balance is non-negative unless overdrafts are allowed, and also validate that the account balance is more than 100 unless the account is not subject to minimum balance requirements.
65 |
66 | ```typescript
67 | import { Validator } from 'fluentvalidation-ts';
68 |
69 | type FormModel = {
70 | accountBalance: number;
71 | allowOverdrafts: boolean;
72 | subjectToMinimumBalance: boolean;
73 | };
74 |
75 | class FormValidator extends Validator {
76 | constructor() {
77 | super();
78 |
79 | this.ruleFor('accountBalance')
80 | .greaterThanOrEqualTo(0)
81 | // highlight-next-line
82 | .unless((formModel) => formModel.allowOverdrafts)
83 | .greaterThanOrEqualTo(100)
84 | // highlight-next-line
85 | .unless((formModel) => !formModel.subjectToMinimumBalance);
86 | }
87 | }
88 |
89 | const formValidator = new FormValidator();
90 |
91 | formValidator.validate({
92 | accountBalance: -50,
93 | allowOverdrafts: true,
94 | subjectToMinimumBalance: false,
95 | });
96 | // ✔ {}
97 |
98 | formValidator.validate({
99 | accountBalance: -50,
100 | allowOverdrafts: false,
101 | subjectToMinimumBalance: false,
102 | });
103 | // ❌ { accountBalance: 'Value must be greater than or equal to 0' }
104 |
105 | formValidator.validate({
106 | accountBalance: 50,
107 | allowOverdrafts: false,
108 | subjectToMinimumBalance: true,
109 | });
110 | // ❌ { accountBalance: 'Value must be greater than or equal to 100' }
111 | ```
112 |
113 | ### Apply to a specific rule in the chain
114 |
115 | In this example we apply an `.unless` condition to a specific rule in the chain.
116 |
117 | In particular, we validate that an age has been entered, and also validate that it is at least 18 unless no alcoholic drink has been chosen.
118 |
119 | ```typescript
120 | import { Validator } from 'fluentvalidation-ts';
121 |
122 | type FormModel = {
123 | age: number | null;
124 | alcoholicDrink: string | null;
125 | };
126 |
127 | class FormValidator extends Validator {
128 | constructor() {
129 | super();
130 |
131 | this.ruleFor('age')
132 | .notNull()
133 | .greaterThanOrEqualTo(18)
134 | // highlight-start
135 | .unless((formModel) => formModel.alcoholicDrink == null, 'AppliesToCurrentValidator');
136 | // highlight-end
137 | }
138 | }
139 |
140 | const formValidator = new FormValidator();
141 |
142 | formValidator.validate({
143 | age: 17,
144 | alcoholicDrink: null,
145 | });
146 | // ✔ {}
147 |
148 | formValidator.validate({
149 | age: 17,
150 | alcoholicDrink: 'Beer',
151 | });
152 | // ❌ { age: 'Value must be greater than or equal to 18' }
153 |
154 | formValidator.validate({
155 | age: null,
156 | alcoholicDrink: null,
157 | });
158 | // ❌ { age: 'Value cannot be null' }
159 | ```
160 |
161 | ## Reference
162 |
163 | ### `.unless(condition: (model: TModel) => boolean, appliesTo?: 'AppliesToAllValidators' | 'AppliesToCurrentValidator')`
164 |
165 | A configuration option which controls when a particular rule or chain of rules should not execute.
166 |
167 | ### `condition`
168 |
169 | This is a function which accepts the value of the base model and returns a `boolean` indicating whether the rule or chain of rules preceding it should not execute.
170 |
171 | A return value of `true` indicates that the rule or chain of rules **should not** execute.
172 |
173 | Conversely, a return value of `false` indicates that the rule or chain of rules **should** execute.
174 |
175 | ### `TModel`
176 |
177 | Matches the type of the base model.
178 |
179 | ### `appliesTo`
180 |
181 | This is an optional parameter which can be used to control which rules in the current rule chain the condition applies to.
182 |
183 | A value of `'AppliesToAllValidators'` means that the `.unless` condition applies to all rules in the current rule chain so far. If there are other calls to `.when` or `.unless` in the chain, only the rules defined since the most recent condition will have the condition applied to them.
184 |
185 | A value of `'AppliesToCurrentValidator'` specifies that the `.unless` condition only controls the execution of the rule immediately preceding it in the current rule chain.
186 |
187 | By default, the `appliesTo` parameter is set to `'AppliesToAllValidators'`.
188 |
--------------------------------------------------------------------------------
/docs/api/configuration/when.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: when
3 | title: '.when'
4 | ---
5 |
6 | The `.when` option is used to control when a rule or chain of rules should execute.
7 |
8 | By default, the `.when` option will apply to all rules in the chain so far, but you can pass a second parameter to specify that it should only apply to the rule immediately preceding it.
9 |
10 | :::note
11 |
12 | In the case that there are multiple `.when` and/or `.unless` conditions in the rule chain, each condition applies only to the rules defined **between it and the previous condition**.
13 |
14 | :::
15 |
16 | ## Examples
17 |
18 | ### Apply to all rules in the chain so far
19 |
20 | In this example we apply a `.when` condition to an entire rule chain.
21 |
22 | In particular, we validate that the delivery note has been entered and is no more than 1,000 characters long when it has been specified that a delivery note is required.
23 |
24 | ```typescript
25 | import { Validator } from 'fluentvalidation-ts';
26 |
27 | type FormModel = {
28 | requiresDeliveryNote: boolean;
29 | deliveryNote: string | null;
30 | };
31 |
32 | class FormValidator extends Validator {
33 | constructor() {
34 | super();
35 |
36 | this.ruleFor('deliveryNote')
37 | .notNull()
38 | .notEmpty()
39 | .maxLength(1000)
40 | // highlight-next-line
41 | .when((formModel) => formModel.requiresDeliveryNote);
42 | }
43 | }
44 |
45 | const formValidator = new FormValidator();
46 |
47 | formValidator.validate({
48 | requiresDeliveryNote: false,
49 | deliveryNote: null,
50 | });
51 | // ✔ {}
52 |
53 | formValidator.validate({
54 | requiresDeliveryNote: true,
55 | deliveryNote: null,
56 | });
57 | // ❌ { deliveryNote: 'Value cannot be null' }
58 | ```
59 |
60 | ### Multiple calls within the same chain
61 |
62 | In this example we apply multiple `.when` conditions within the same rule chain.
63 |
64 | In particular, we validate that Sunday delivery rates are only applied when the delivery day is a Sunday.
65 |
66 | ```typescript
67 | import { Validator } from 'fluentvalidation-ts';
68 |
69 | type FormModel = {
70 | deliveryDay: string;
71 | deliveryRate: number;
72 | };
73 |
74 | class FormValidator extends Validator {
75 | constructor() {
76 | super();
77 |
78 | this.ruleFor('deliveryRate')
79 | .equal(4.99)
80 | .withMessage('Sunday rates must apply if delivery day is Sunday')
81 | // highlight-next-line
82 | .when((formModel) => formModel.deliveryDay === 'Sunday')
83 | .equal(2.99)
84 | .withMessage('Standard rates must apply if delivery day is Monday to Saturday')
85 | // highlight-next-line
86 | .when((formModel) => formModel.deliveryDay !== 'Sunday');
87 | }
88 | }
89 |
90 | const formValidator = new FormValidator();
91 |
92 | formValidator.validate({ deliveryDay: 'Sunday', deliveryRate: 4.99 });
93 | // ✔ {}
94 |
95 | formValidator.validate({ deliveryDay: 'Sunday', deliveryRate: 2.99 });
96 | // ❌ { deliveryRate: 'Sunday rates must apply if delivery day is Sunday' }
97 |
98 | formValidator.validate({ deliveryDay: 'Monday', deliveryRate: 2.99 });
99 | // ✔ {}
100 |
101 | formValidator.validate({ deliveryDay: 'Monday', deliveryRate: 4.99 });
102 | // ❌ { deliveryRate: 'Standard rates must apply if delivery day is Monday to Saturday' }
103 | ```
104 |
105 | ### Apply to a specific rule in the chain
106 |
107 | In this example we apply a `.when` condition to a specific rule in the chain.
108 |
109 | In particular, we validate that an age has been entered, and also validate that it is at least 18 when an alcoholic drink has been chosen.
110 |
111 | ```typescript
112 | import { Validator } from 'fluentvalidation-ts';
113 |
114 | type FormModel = {
115 | age: number | null;
116 | alcoholicDrink: string | null;
117 | };
118 |
119 | class FormValidator extends Validator {
120 | constructor() {
121 | super();
122 |
123 | this.ruleFor('age')
124 | .notNull()
125 | .greaterThanOrEqualTo(18)
126 | // highlight-start
127 | .when((formModel) => formModel.alcoholicDrink != null, 'AppliesToCurrentValidator');
128 | // highlight-end
129 | }
130 | }
131 |
132 | const formValidator = new FormValidator();
133 |
134 | formValidator.validate({
135 | age: 17,
136 | alcoholicDrink: null,
137 | });
138 | // ✔ {}
139 |
140 | formValidator.validate({
141 | age: 17,
142 | alcoholicDrink: 'Beer',
143 | });
144 | // ❌ { age: 'Value must be greater than or equal to 18' }
145 |
146 | formValidator.validate({
147 | age: null,
148 | alcoholicDrink: null,
149 | });
150 | // ❌ { age: 'Value cannot be null' }
151 | ```
152 |
153 | ## Reference
154 |
155 | ### `.when(condition: (model: TModel) => boolean, appliesTo?: 'AppliesToAllValidators' | 'AppliesToCurrentValidator')`
156 |
157 | A configuration option which controls when a particular rule or chain of rules should execute.
158 |
159 | ### `condition`
160 |
161 | This is a function which accepts the value of the base model and returns a `boolean` indicating whether the rule or chain of rules preceding it should execute.
162 |
163 | A return value of `true` indicates that the rule or chain of rules **should** execute.
164 |
165 | Conversely, a return value of `false` indicates that the rule or chain of rules **should not** execute.
166 |
167 | ### `TModel`
168 |
169 | Matches the type of the base model.
170 |
171 | ### `appliesTo`
172 |
173 | This is an optional parameter which can be used to control which rules in the current rule chain the condition applies to.
174 |
175 | A value of `'AppliesToAllValidators'` means that the `.when` condition applies to all rules in the current rule chain so far. If there are other calls to `.when` or `.unless` in the chain, only the rules defined since the most recent condition will have the condition applied to them.
176 |
177 | A value of `'AppliesToCurrentValidator'` specifies that the `.when` condition only controls the execution of the rule immediately preceding it in the current rule chain.
178 |
179 | By default, the `appliesTo` parameter is set to `'AppliesToAllValidators'`.
180 |
--------------------------------------------------------------------------------
/docs/api/configuration/withMessage.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: withMessage
3 | title: '.withMessage'
4 | ---
5 |
6 | The `.withMessage` option is used to specify a custom error message that should be used when a given validation rule fails.
7 |
8 | All validation rules have a default error message associated with them, but sometimes you may wish to override these defaults and specify your own user-friendly error message.
9 |
10 | Note that `.withMessage` only applies to the rule immediately preceding it in the rule chain, not to all rules in the chain so far.
11 |
12 | ## Example
13 |
14 | ```typescript
15 | import { Validator } from 'fluentvalidation-ts';
16 |
17 | type FormModel = {
18 | name: string;
19 | };
20 |
21 | class FormValidator extends Validator {
22 | constructor() {
23 | super();
24 |
25 | this.ruleFor('name')
26 | .notEmpty()
27 | // highlight-next-line
28 | .withMessage('Please enter your name')
29 | .maxLength(1000)
30 | // highlight-next-line
31 | .withMessage('Please enter no more than 1,000 characters');
32 | }
33 | }
34 |
35 | const formValidator = new FormValidator();
36 |
37 | formValidator.validate({ name: 'Alex' });
38 | // ✔ {}
39 |
40 | formValidator.validate({ name: '' });
41 | // ❌ { name: 'Please enter your name' }
42 | ```
43 |
44 | ## Reference
45 |
46 | ### `.withMessage(customMessage: string)`
47 |
48 | A configuration option which takes a custom error message and uses that message in place of the default error message if the given validation rule fails.
49 |
--------------------------------------------------------------------------------
/docs/api/core/asyncValidator.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: asyncValidator
3 | title: AsyncValidator
4 | ---
5 |
6 | The `AsyncValidator` generic class is an extension of [`Validator`](api/core/validator.md) that has additional async rules available (most notably [`.mustAsync`](api/rules/mustAsync.md) and [`.setAsyncValidator`](api/rules/setAsyncValidator.md)).
7 |
8 | ```typescript
9 | import { AsyncValidator } from 'fluentvalidation-ts';
10 | ```
11 |
12 | Defining an async validator for a model of type `TModel` works exactly the same as defining a standard validator - all you have to do is define a class which extends `AsyncValidator` (as opposed to `Validator`) and specify some rules in the constructor using the [`.ruleFor`](api/core/ruleFor.md) and [`.ruleForEach`](api/core/ruleForEach.md) methods.
13 |
14 | ```typescript
15 | type FormModel = { username: string };
16 |
17 | class FormValidator extends AsyncValidator {
18 | constructor() {
19 | super();
20 |
21 | this.ruleFor('username').mustAsync(async (username) =>
22 | await api.usernameIsAvailable(username);
23 | )
24 | .withMessage('This username is already taken');
25 | }
26 | }
27 | ```
28 |
29 | To actually validate an instance of your model, simply create an instance of your validator and pass your model to the `.validateAsync` method. As the name suggests this method is **asynchronous**, so be sure to `await` the result or use Promise callback methods (i.e. `.then` and `.catch`).
30 |
31 | Note that the synchronous `.validate` method is **not available** on an instance of `AsyncValidator`, you must always use the `.validateAsync` method.
32 |
33 | ```typescript
34 | const formValidator = new FormValidator();
35 |
36 | const validResult = await formValidator.validateAsync({
37 | username: 'ajp_dev123',
38 | });
39 | // ✔ {}
40 |
41 | const invalidResult = await formValidator.validateAsync({
42 | username: 'ajp_dev',
43 | });
44 | // ❌ { username: 'This username is already taken' }
45 | ```
46 |
47 | A call to `.validateAsync` returns a `Promise` that resolves to an object of type [`ValidationErrors`](api/core/validationErrors.md), which describes the validity of the given value.
48 |
--------------------------------------------------------------------------------
/docs/api/core/ruleFor.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: ruleFor
3 | title: '.ruleFor'
4 | ---
5 |
6 | The `.ruleFor` method on the `Validator` class is used to build up rule chains for properties on your model.
7 |
8 | To get started, simply call `this.ruleFor` in the constructor of your validator and pass in the name of a property on your model (note that this is strongly typed, you'll get a compilation error if you pass the name of a property that doesn't exist on the model).
9 |
10 | The result of this call is a rule chain builder that exposes all the relevant built-in validation rules for the property you specified.
11 |
12 | ```typescript
13 | import { Validator } from 'fluentvalidation-ts';
14 |
15 | type FormModel = {
16 | name: string;
17 | isEmployed: boolean;
18 | jobTitle: string | null;
19 | };
20 |
21 | class FormValidator extends Validator {
22 | constructor() {
23 | super();
24 |
25 | // Returns a rule chain builder for the 'name' property
26 | this.ruleFor('name');
27 | }
28 | }
29 | ```
30 |
31 | To add a validation rule to the target property, simply call the relevant method on the rule chain builder (passing in any parameters as necessary). The result of such a call is again the rule chain builder, so you can specify multiple rules in a single call to `.ruleFor`.
32 |
33 | ```typescript
34 | // The result of adding a rule is again the rule chain builder,
35 | // so you can add multiple rules in a single call
36 | this.ruleFor('name').notEmpty().maxLength(100);
37 | ```
38 |
39 | After adding a rule to the chain you also gain access to a number of configuration methods which allow you to do things like specify what error should be used if the validation rule fails, and conditions under which the rules should/shouldn't run.
40 |
41 | ```typescript
42 | this.ruleFor('name').notEmpty().maxLength(100);
43 |
44 | // You can specify a custom error message for each rule in the chain,
45 | // and provide a condition to determine when the rules should run
46 | this.ruleFor('jobTitle')
47 | .notEmpty()
48 | .withMessage('Please enter a Job Title')
49 | .maxLength(100)
50 | .withMessage('Please enter no more than 100 characters')
51 | // highlight-next-line
52 | .when((formModel) => formModel.isEmployed);
53 |
54 | // You can also provide a condition to determine when certain rules
55 | // should not run
56 | this.ruleFor('jobTitle')
57 | .equal('')
58 | .withMessage('You cannot enter a Job Title if you are not employed')
59 | // highlight-next-line
60 | .unless((formModel) => formModel.isEmployed);
61 | ```
62 |
63 | As the above example illustrates, you can make several calls to `.ruleFor` for the same property. It doesn't matter how many rule chains you define for a particular property, and you don't have to define any at all if you don't need to.
64 |
--------------------------------------------------------------------------------
/docs/api/core/ruleForEach.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: ruleForEach
3 | title: '.ruleForEach'
4 | ---
5 |
6 | The `.ruleForEach` method on the `Validator` class is much like the [`.ruleFor`](api/core/ruleFor.md) method, except that is used to build up rule chains for **array** properties on your model.
7 |
8 | You can use `.ruleForEach` to specify a rule chain that should apply to **each element** of a particular array property. Aside from this, the `.ruleForEach` method works almost exactly the same as the [`.ruleFor`](api/core/ruleFor.md) method, with all the chaining and configuration available in exactly the same way.
9 |
10 | ```typescript
11 | import { Validator } from 'fluentvalidation-ts';
12 |
13 | type FormModel = { scores: Array };
14 |
15 | class FormValidator extends Validator {
16 | constructor() {
17 | super();
18 |
19 | this.ruleForEach('scores')
20 | .greaterThan(0)
21 | .withMessage('Please enter a positive score')
22 | .lessThanOrEqualTo(5)
23 | .withMessage('Please enter a score no greater than 5');
24 | }
25 | }
26 | ```
27 |
--------------------------------------------------------------------------------
/docs/api/core/ruleForEachTransformed.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: ruleForEachTransformed
3 | title: '.ruleForEachTransformed'
4 | ---
5 |
6 | The `.ruleForEachTransformed` method on the `Validator` class is identical to the [`.ruleForEach`](api/core/ruleForEach.md) method, except that it allows you to transform each item of the given array property on your model via a transformation function prior to building up the rule chain for it.
7 |
8 | The available validation rules will be based on the type of the **transformed** items, rather than the original type of the items.
9 |
10 | To get started, simply call `this.ruleForTransformed` in the constructor of your validator and pass in the name of an array property on your model, along with a transformation function.
11 |
12 | The result of this call is a rule chain builder, exactly the same as that returned by [`.ruleForEach`](api/core/ruleForEach.md), except that it exposes all the relevant built-in validation rules for the type of the **transformed** item values.
13 |
14 | ```typescript
15 | import { Validator } from 'fluentvalidation-ts';
16 |
17 | type FormModel = {
18 | scores: Array;
19 | };
20 |
21 | class FormValidator extends Validator {
22 | constructor() {
23 | super();
24 |
25 | this.ruleForEachTransformed('scores', (s) => Number(s))
26 | .must((numberScore) => !isNaN(numberScore))
27 | .greaterThan(0)
28 | .lessThanOrEqualTo(100);
29 | }
30 | }
31 | ```
32 |
33 | ## Limitations
34 |
35 | The same limitations that apply to the [`.ruleForTransformed`](api/core/ruleForTransformed.md) method apply also to the `.ruleForEachTransformed` method.
36 |
--------------------------------------------------------------------------------
/docs/api/core/ruleForTransformed.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: ruleForTransformed
3 | title: '.ruleForTransformed'
4 | ---
5 |
6 | The `.ruleForTransformed` method on the `Validator` class is identical to the [`.ruleFor`](api/core/ruleFor.md) method, except that it allows you to transform the given property on your model via a transformation function prior to building up the rule chain for it.
7 |
8 | The available validation rules will be based on the type of the **transformed** value, rather than the original type of the property.
9 |
10 | To get started, simply call `this.ruleForTransformed` in the constructor of your validator and pass in the name of a property on your model, along with a transformation function.
11 |
12 | The result of this call is a rule chain builder, exactly the same as that returned by [`.ruleFor`](api/core/ruleFor.md), except that it exposes all the relevant built-in validation rules for the type of the **transformed** property value.
13 |
14 | ```typescript
15 | import { Validator } from 'fluentvalidation-ts';
16 |
17 | type FormModel = {
18 | quantity: string;
19 | };
20 |
21 | class FormValidator extends Validator {
22 | constructor() {
23 | super();
24 |
25 | this.ruleForTransformed('quantity', (q) => Number(q))
26 | .must((numberQuantity) => !isNaN(numberQuantity))
27 | .greaterThan(0)
28 | .lessThanOrEqualTo(100);
29 | }
30 | }
31 | ```
32 |
33 | ## Limitations
34 |
35 | Note that in order to preserve the shape of the [errors object](api/core/validationErrors.md) returned by the `.validate` and `.validateAsync` methods, the transformation function passed to `.ruleForTransformed` cannot map flat types into complex types.
36 |
37 | For example, a `string` property cannot be transformed into an `Array`. This is because the errors object could then contain an array of errors at the path of the `string` property, while the expected type at this path is a "flat" error (i.e. `string | null | undefined`).
38 |
39 | For the same reasons, complex types cannot be mapped to other complex types that look different. For example, if an `object` property is mapped to another `object` with different properties, then the errors object could contain nested errors at the path of the property with unexpected keys (i.e. keys not present on the original type of the property).
40 |
41 | It is possible to map complex types to flat types, or complex types to other complex types with some/all of the same properties. This is because the shape of the errors object is preserved in these cases.
42 |
--------------------------------------------------------------------------------
/docs/api/core/validator.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: validator
3 | title: Validator
4 | ---
5 |
6 | ## Validator<TModel>
7 |
8 | The `Validator` generic class is the core component of the **fluentvalidation-ts** API.
9 |
10 | ```typescript
11 | import { Validator } from 'fluentvalidation-ts';
12 | ```
13 |
14 | To define a validator for a model of type `TModel` all you have to do is define a class which extends `Validator` and specify some rules in the constructor using the `.ruleFor` and `.ruleForEach` methods.
15 |
16 | ```typescript
17 | type FormModel = { name: string };
18 |
19 | class FormValidator extends Validator {
20 | constructor() {
21 | super();
22 |
23 | this.ruleFor('name').notEmpty().withMessage('Please enter your name');
24 | }
25 | }
26 | ```
27 |
28 | ## .validate
29 |
30 | To actually validate an instance of your model, simply create an instance of your validator and pass your model to the `.validate` method.
31 |
32 | ```typescript
33 | const formValidator = new FormValidator();
34 |
35 | const validResult = formValidator.validate({ name: 'Alex' });
36 | // ✔ {}
37 |
38 | const invalidResult = formValidator.validate({ name: '' });
39 | // ❌ { name: 'Please enter your name' }
40 | ```
41 |
42 | A call to `.validate` returns an object of type `ValidationErrors`, which describes the validity of the given value.
43 |
--------------------------------------------------------------------------------
/docs/api/rules/emailAddress.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: emailAddress
3 | title: '.emailAddress'
4 | ---
5 |
6 | The `.emailAddress` rule is used to ensure that the value of a given `string` property is a valid email address.
7 |
8 | ## Example
9 |
10 | ```typescript
11 | import { Validator } from 'fluentvalidation-ts';
12 |
13 | type FormModel = {
14 | contactEmail: string;
15 | };
16 |
17 | class FormValidator extends Validator {
18 | constructor() {
19 | super();
20 |
21 | this.ruleFor('contactEmail').emailAddress();
22 | }
23 | }
24 |
25 | const formValidator = new FormValidator();
26 |
27 | formValidator.validate({ contactEmail: 'foo@example.com' });
28 | // ✔ {}
29 |
30 | formValidator.validate({ contactEmail: 'foo' });
31 | // ❌ { contactEmail: 'Not a valid email address' }
32 | ```
33 |
34 | ## Reference
35 |
36 | ### `.emailAddress()`
37 |
38 | A string validation rule which ensures that the given property is a valid email address.
39 |
40 | ## Example Message
41 |
42 | > Not a valid email address
43 |
--------------------------------------------------------------------------------
/docs/api/rules/equal.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: equal
3 | title: '.equal'
4 | ---
5 |
6 | The `.equal` rule is used to ensure that the value of a given property is equal to a given value.
7 |
8 | Note that this rule uses **strict** equality (i.e. the `===` operator) and may not work as intended for object or array values.
9 |
10 | ## Example
11 |
12 | ```typescript
13 | import { Validator } from 'fluentvalidation-ts';
14 |
15 | type FormModel = {
16 | acceptsTermsAndConditions: boolean;
17 | };
18 |
19 | class FormValidator extends Validator {
20 | constructor() {
21 | super();
22 |
23 | this.ruleFor('acceptsTermsAndConditions').equal(true);
24 | }
25 | }
26 |
27 | const formValidator = new FormValidator();
28 |
29 | formValidator.validate({ acceptsTermsAndConditions: true });
30 | // ✔ {}
31 |
32 | formValidator.validate({ acceptsTermsAndConditions: false });
33 | // ❌ { acceptsTermsAndConditions: `Must equal 'true'` }
34 | ```
35 |
36 | ## Reference
37 |
38 | ### `.equal(comparisonValue: TValue)`
39 |
40 | A base validation rule which takes in a value and ensures that the given property is equal to that value.
41 |
42 | ### `TValue`
43 |
44 | Matches the type of the property that the rule is applied to.
45 |
46 | ## Example Message
47 |
48 | > Must equal '`[comparisonValue]`'
49 |
--------------------------------------------------------------------------------
/docs/api/rules/exclusiveBetween.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: exclusiveBetween
3 | title: '.exclusiveBetween'
4 | ---
5 |
6 | The `.exclusiveBetween` rule is used to ensure that the value of a given `number` property is exclusively between the given bounds (i.e. greater than the lower bound and less than the upper bound).
7 |
8 | ## Example
9 |
10 | ```typescript
11 | import { Validator } from 'fluentvalidation-ts';
12 |
13 | type FormModel = {
14 | score: number;
15 | };
16 |
17 | class FormValidator extends Validator {
18 | constructor() {
19 | super();
20 |
21 | this.ruleFor('score').exclusiveBetween(0, 10);
22 | }
23 | }
24 |
25 | const formValidator = new FormValidator();
26 |
27 | formValidator.validate({ score: 5 });
28 | // ✔ {}
29 |
30 | formValidator.validate({ score: 0 });
31 | // ❌ { score: 'Value must be between 0 and 10 (exclusive)' }
32 | ```
33 |
34 | ## Reference
35 |
36 | ### `.exclusiveBetween(lowerBound: number, upperBound: number)`
37 |
38 | A number validation rule which takes in a lower bound and upper bound and ensures that the given property is exclusively between them (i.e. greater than the lower bound and less than the upper bound).
39 |
40 | ## Example Message
41 |
42 | > Value must be between `[lowerBound]` and `[upperBound]` (exclusive)
43 |
--------------------------------------------------------------------------------
/docs/api/rules/greaterThan.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: greaterThan
3 | title: '.greaterThan'
4 | ---
5 |
6 | The `.greaterThan` rule is used to ensure that the value of a given `number` property is strictly greater than a given value.
7 |
8 | ## Example
9 |
10 | ```typescript
11 | import { Validator } from 'fluentvalidation-ts';
12 |
13 | type FormModel = {
14 | quantity: number;
15 | };
16 |
17 | class FormValidator extends Validator {
18 | constructor() {
19 | super();
20 |
21 | this.ruleFor('quantity').greaterThan(0);
22 | }
23 | }
24 |
25 | const formValidator = new FormValidator();
26 |
27 | formValidator.validate({ quantity: 2 });
28 | // ✔ {}
29 |
30 | formValidator.validate({ quantity: 0 });
31 | // ❌ { quantity: 'Value must be greater than 0' }
32 | ```
33 |
34 | ## Reference
35 |
36 | ### `.greaterThan(threshold: number)`
37 |
38 | A number validation rule which takes in a threshold and ensures that the given property is strictly greater than it.
39 |
40 | ## Example Message
41 |
42 | > Value must be greater than `[threshold]`
43 |
--------------------------------------------------------------------------------
/docs/api/rules/greaterThanOrEqualTo.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: greaterThanOrEqualTo
3 | title: '.greaterThanOrEqualTo'
4 | ---
5 |
6 | The `.greaterThanOrEqualTo` rule is used to ensure that the value of a given `number` property is greater than or equal to a given value.
7 |
8 | ## Example
9 |
10 | ```typescript
11 | import { Validator } from 'fluentvalidation-ts';
12 |
13 | type FormModel = {
14 | age: number;
15 | };
16 |
17 | class FormValidator extends Validator {
18 | constructor() {
19 | super();
20 |
21 | this.ruleFor('age').greaterThanOrEqualTo(18);
22 | }
23 | }
24 |
25 | const formValidator = new FormValidator();
26 |
27 | formValidator.validate({ age: 18 });
28 | // ✔ {}
29 |
30 | formValidator.validate({ age: 16 });
31 | // ❌ { age: 'Value must be greater than or equal to 18' }
32 | ```
33 |
34 | ## Reference
35 |
36 | ### `.greaterThanOrEqualTo(threshold: number)`
37 |
38 | A number validation rule which takes in a threshold and ensures that the given property is greater than or equal to it.
39 |
40 | ## Example Message
41 |
42 | > Value must be greater than or equal to `[threshold]`
43 |
--------------------------------------------------------------------------------
/docs/api/rules/inclusiveBetween.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: inclusiveBetween
3 | title: '.inclusiveBetween'
4 | ---
5 |
6 | The `.inclusiveBetween` rule is used to ensure that the value of a given `number` property is inclusively between the given bounds (i.e. greater than or equal to the lower bound and less than or equal to the upper bound).
7 |
8 | ## Example
9 |
10 | ```typescript
11 | import { Validator } from 'fluentvalidation-ts';
12 |
13 | type FormModel = {
14 | percentageComplete: number;
15 | };
16 |
17 | class FormValidator extends Validator {
18 | constructor() {
19 | super();
20 |
21 | this.ruleFor('percentageComplete').inclusiveBetween(0, 100);
22 | }
23 | }
24 |
25 | const formValidator = new FormValidator();
26 |
27 | formValidator.validate({ percentageComplete: 50 });
28 | // ✔ {}
29 |
30 | formValidator.validate({ percentageComplete: 110 });
31 | // ❌ { percentageComplete: 'Value must be between 0 and 100 (inclusive)' }
32 | ```
33 |
34 | ## Reference
35 |
36 | ### `.inclusiveBetween(lowerBound: number, upperBound: number)`
37 |
38 | A number validation rule which takes in a lower bound and upper bound and ensures that the given property is inclusively between them (i.e. greater than or equal to the lower bound and less than or equal to the upper bound).
39 |
40 | ## Example Message
41 |
42 | > Value must be between `[lowerBound]` and `[upperBound]` (inclusive)
43 |
--------------------------------------------------------------------------------
/docs/api/rules/length.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: length
3 | title: '.length'
4 | ---
5 |
6 | The `.length` rule is used to ensure that the length of a given `string` property is inclusively between the given bounds (i.e. greater than or equal to the lower bound and less than or equal to the upper bound).
7 |
8 | ## Example
9 |
10 | ```typescript
11 | import { Validator } from 'fluentvalidation-ts';
12 |
13 | type FormModel = {
14 | voucherCode: string;
15 | };
16 |
17 | class FormValidator extends Validator {
18 | constructor() {
19 | super();
20 |
21 | this.ruleFor('voucherCode').length(5, 10);
22 | }
23 | }
24 |
25 | const formValidator = new FormValidator();
26 |
27 | formValidator.validate({ voucherCode: 'ABC44' });
28 | // ✔ {}
29 |
30 | formValidator.validate({ voucherCode: 'ZZ' });
31 | // ❌ { voucherCode: 'Value must be between 5 and 10 characters long' }
32 | ```
33 |
34 | ## Reference
35 |
36 | ### `.length(lowerBound: number, upperBound: number)`
37 |
38 | A string validation rule which takes in a lower bound and upper bound and ensures that the length of the given property is inclusively between them (i.e. greater than or equal to the lower bound and less than or equal to the upper bound).
39 |
40 | ## Example Message
41 |
42 | > Value must be between `[lowerBound]` and `[upperBound]` characters long
43 |
--------------------------------------------------------------------------------
/docs/api/rules/lessThan.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: lessThan
3 | title: '.lessThan'
4 | ---
5 |
6 | The `.lessThan` rule is used to ensure that the value of a given `number` property is strictly less than a given value.
7 |
8 | ## Example
9 |
10 | ```typescript
11 | import { Validator } from 'fluentvalidation-ts';
12 |
13 | type FormModel = {
14 | bagWeightInKilograms: number;
15 | };
16 |
17 | class FormValidator extends Validator {
18 | constructor() {
19 | super();
20 |
21 | this.ruleFor('bagWeightInKilograms').lessThan(20);
22 | }
23 | }
24 |
25 | const formValidator = new FormValidator();
26 |
27 | formValidator.validate({ bagWeightInKilograms: 18.5 });
28 | // ✔ {}
29 |
30 | formValidator.validate({ bagWeightInKilograms: 22.8 });
31 | // ❌ { bagWeightInKilograms: 'Value must be less than 20' }
32 | ```
33 |
34 | ## Reference
35 |
36 | ### `.lessThan(threshold: number)`
37 |
38 | A number validation rule which takes in a threshold and ensures that the given property is strictly less than it.
39 |
40 | ## Example Message
41 |
42 | > Value must be less than `[threshold]`
43 |
--------------------------------------------------------------------------------
/docs/api/rules/lessThanOrEqualTo.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: lessThanOrEqualTo
3 | title: '.lessThanOrEqualTo'
4 | ---
5 |
6 | The `.lessThanOrEqualTo` rule is used to ensure that the value of a given `number` property is less than or equal to a given value.
7 |
8 | ## Example
9 |
10 | ```typescript
11 | import { Validator } from 'fluentvalidation-ts';
12 |
13 | type FormModel = {
14 | passengers: number;
15 | };
16 |
17 | class FormValidator extends Validator {
18 | constructor() {
19 | super();
20 |
21 | this.ruleFor('passengers').lessThanOrEqualTo(4);
22 | }
23 | }
24 |
25 | const formValidator = new FormValidator();
26 |
27 | formValidator.validate({ passengers: 4 });
28 | // ✔ {}
29 |
30 | formValidator.validate({ passengers: 6 });
31 | // ❌ { passengers: 'Value must be less than or equal to 4' }
32 | ```
33 |
34 | ## Reference
35 |
36 | ### `.lessThanOrEqualTo(threshold: number)`
37 |
38 | A number validation rule which takes in a threshold and ensures that the given property is less than or equal to it.
39 |
40 | ## Example Message
41 |
42 | > Value must be less than or equal to `[threshold]`
43 |
--------------------------------------------------------------------------------
/docs/api/rules/matches.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: matches
3 | title: '.matches'
4 | ---
5 |
6 | The `.matches` rule is used to ensure that the value of a given `string` property matches the given [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp).
7 |
8 | ## Example
9 |
10 | ```typescript
11 | import { Validator } from 'fluentvalidation-ts';
12 |
13 | type FormModel = {
14 | price: string;
15 | };
16 |
17 | class FormValidator extends Validator {
18 | constructor() {
19 | super();
20 |
21 | this.ruleFor('price').matches(new RegExp('^([0-9])+.([0-9]){2}$'));
22 | }
23 | }
24 |
25 | const formValidator = new FormValidator();
26 |
27 | formValidator.validate({ price: '249.99' });
28 | // ✔ {}
29 |
30 | formValidator.validate({ price: '15' });
31 | // ❌ { price: 'Value does not match the required pattern' }
32 | ```
33 |
34 | ## Reference
35 |
36 | ### `.matches(pattern: RegExp)`
37 |
38 | A string validation rule which takes in a [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) and ensures that the given property matches it.
39 |
40 | ## Example Message
41 |
42 | > Value does not match the required pattern
43 |
--------------------------------------------------------------------------------
/docs/api/rules/maxLength.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: maxLength
3 | title: '.maxLength'
4 | ---
5 |
6 | The `.maxLength` rule is used to ensure that the length of a given `string` property is less than or equal to a given value.
7 |
8 | ## Example
9 |
10 | ```typescript
11 | import { Validator } from 'fluentvalidation-ts';
12 |
13 | type FormModel = {
14 | username: string;
15 | };
16 |
17 | class FormValidator extends Validator {
18 | constructor() {
19 | super();
20 |
21 | this.ruleFor('username').maxLength(20);
22 | }
23 | }
24 |
25 | const formValidator = new FormValidator();
26 |
27 | formValidator.validate({ username: 'AlexPotter' });
28 | // ✔ {}
29 |
30 | formValidator.validate({ username: 'ThisUsernameIsFarTooLong' });
31 | // ❌ { username: 'Value must be no more than 20 characters long' }
32 | ```
33 |
34 | ## Reference
35 |
36 | ### `.maxLength(upperBound: number)`
37 |
38 | A string validation rule which takes in an upper bound and ensures that the length of the given property is less than or equal to it.
39 |
40 | ## Example Message
41 |
42 | > Value must be no more than `[upperBound]` characters long
43 |
--------------------------------------------------------------------------------
/docs/api/rules/minLength.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: minLength
3 | title: '.minLength'
4 | ---
5 |
6 | The `.minLength` rule is used to ensure that the length of a given `string` property is greater than or equal to a given value.
7 |
8 | ## Example
9 |
10 | ```typescript
11 | import { Validator } from 'fluentvalidation-ts';
12 |
13 | type FormModel = {
14 | password: string;
15 | };
16 |
17 | class FormValidator extends Validator {
18 | constructor() {
19 | super();
20 |
21 | this.ruleFor('password').minLength(6);
22 | }
23 | }
24 |
25 | const formValidator = new FormValidator();
26 |
27 | formValidator.validate({ password: 'supersecret' });
28 | // ✔ {}
29 |
30 | formValidator.validate({ password: 'foo' });
31 | // ❌ { password: 'Value must be at least 6 characters long' }
32 | ```
33 |
34 | ## Reference
35 |
36 | ### `.minLength(lowerBound: number)`
37 |
38 | A string validation rule which takes in a lower bound and ensures that the length of the given property is greater than or equal to it.
39 |
40 | ## Example Message
41 |
42 | > Value must be at least `[lowerBound]` characters long
43 |
--------------------------------------------------------------------------------
/docs/api/rules/mustAsync.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: mustAsync
3 | title: '.mustAsync'
4 | ---
5 |
6 | The `.mustAsync` rule is one of the special async rules that become available when you extend from [`AsyncValidator`](api/core/asyncValidator.md) as opposed to just [`Validator`](api/core/validator.md).
7 |
8 | This rule works exactly the same as the [`.must`](api/rules/must.md) rule, except that it takes an async predicate function. This allows you to do things like define custom validation rules which perform API requests (e.g. checking if a username is already taken).
9 |
10 | All the various overloads for the [`.must`](api/rules/must.md) rule are also available for the `.mustAsync` rule - the only difference is that your predicate function must be async (i.e. have a return type of `Promise` instead of `boolean`).
11 |
12 | ## Examples
13 |
14 | The documentation page for the [`.must`](api/rules/must.md) rule includes a full list of examples demonstrating the different overloads that are available.
15 |
16 | These are all relevant to the `.mustAsync` rule too, just replace `Validator` with `AsyncValidator`, `.must` with `.mustAsync`, and synchronous predicate functions with asynchronous ones.
17 |
18 | ### Predicate dependent on value
19 |
20 | In this example we specify an async predicate on its own, which is dependent only on the value of the property we're validating.
21 |
22 | ```typescript
23 | import { AsyncValidator } from 'fluentvalidation-ts';
24 |
25 | type FormModel = {
26 | username: string;
27 | };
28 |
29 | class FormValidator extends AsyncValidator {
30 | constructor() {
31 | super();
32 |
33 | // highlight-start
34 | this.ruleFor('username').mustAsync(
35 | async (username) => await api.usernameIsAvailable(username)
36 | );
37 | // highlight-end
38 | }
39 | }
40 |
41 | const formValidator = new FormValidator();
42 |
43 | await formValidator.validateAsync({ username: 'ajp_dev123' });
44 | // ✔ {}
45 |
46 | await formValidator.validateAsync({ username: 'ajp_dev' });
47 | // ❌ { username: 'Value is not valid' }
48 | ```
49 |
50 | ## Reference
51 |
52 | The `.mustAsync` rule is one of the more complex built-in rules. You may wish to refer to the examples on the documentation page for the [`.must`](api/rules/must.md) rule to help you understand the different variations of this rule.
53 |
54 | ### `.mustAsync(predicate: SimpleAsyncPredicate)`
55 |
56 | A validation rule which takes in a simple async predicate function and ensures that the given property is valid according to that predicate function.
57 |
58 | ### `.mustAsync(predicateAndMessage: SimpleAsyncPredicateWithMessage)`
59 |
60 | A validation rule which takes in a definition that specifies both an async predicate function and a message (or message generator), and ensures that the given property is valid according to the given predicate function (exposing the relevant message if validation fails).
61 |
62 | ### `.mustAsync(definitions: Array | SimpleAsyncPredicateWithMessage>)`
63 |
64 | A validation rule which takes in an array of async predicate functions and/or predicate function and message (or message generator) pairs, and ensures that the given property is valid according to each one (exposing a relevant message for the first failing predicate if validation fails).
65 |
66 | ### `SimpleAsyncPredicateWithMessage`
67 |
68 | Equivalent to `{ predicate: SimpleAsyncPredicate; message: string | MessageGenerator }`
69 |
70 | An object that specifies both an async predicate function and a message (or message generator). The predicate function is used to determine whether a given value is valid, and the message (either explicit or generated) is used in the validation errors object if validation fails.
71 |
72 | ### `SimpleAsyncPredicate`
73 |
74 | Equivalent to `(value: TValue, model: TModel) => Promise`.
75 |
76 | A simple predicate is an async function which accepts the value of the property being validated and the value of the model as a whole, and returns a `Promise` indicating whether the property is valid or not.
77 |
78 | A return value that resolves to `true` indicates that the property is valid ✔.
79 |
80 | Conversely, a return value that resolves to `false` indicates that the property is invalid ❌.
81 |
82 | ### `MessageGenerator`
83 |
84 | Equivalent to `(value: TValue, model: TModel) => string`.
85 |
86 | A function which accepts both the value being validated and the model as a whole, and returns an appropriate error message.
87 |
88 | ### `TValue`
89 |
90 | Matches the type of the property that the rule is applied to.
91 |
92 | ### `TModel`
93 |
94 | Matches the type of the base model.
95 |
96 | ## Example Message
97 |
98 | > Value is not valid
99 |
--------------------------------------------------------------------------------
/docs/api/rules/notEmpty.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: notEmpty
3 | title: '.notEmpty'
4 | ---
5 |
6 | The `.notEmpty` rule is used to ensure that the value of a given `string` property is not the empty string, or formed entirely of whitespace.
7 |
8 | ## Example
9 |
10 | ```typescript
11 | import { Validator } from 'fluentvalidation-ts';
12 |
13 | type FormModel = {
14 | name: string;
15 | };
16 |
17 | class FormValidator extends Validator {
18 | constructor() {
19 | super();
20 |
21 | this.ruleFor('name').notEmpty();
22 | }
23 | }
24 |
25 | const formValidator = new FormValidator();
26 |
27 | formValidator.validate({ name: 'Alex' });
28 | // ✔ {}
29 |
30 | formValidator.validate({ name: ' ' });
31 | // ❌ { name: 'Value cannot be empty' }
32 | ```
33 |
34 | ## Reference
35 |
36 | ### `.notEmpty()`
37 |
38 | A string validation rule which ensures that the given property is not the empty string, or formed entirely of whitespace.
39 |
40 | ## Example Message
41 |
42 | > Value cannot be empty
43 |
--------------------------------------------------------------------------------
/docs/api/rules/notEqual.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: notEqual
3 | title: '.notEqual'
4 | ---
5 |
6 | The `.notEqual` rule is used to ensure that the value of a given property is not equal to a given value.
7 |
8 | Note that this rule uses **strict** inequality (i.e. the `!==` operator) and may not work as intended for object or array values.
9 |
10 | ## Example
11 |
12 | ```typescript
13 | import { Validator } from 'fluentvalidation-ts';
14 |
15 | type FormModel = {
16 | acceptsTermsAndConditions: boolean;
17 | };
18 |
19 | class FormValidator extends Validator {
20 | constructor() {
21 | super();
22 |
23 | this.ruleFor('acceptsTermsAndConditions').notEqual(false);
24 | }
25 | }
26 |
27 | const formValidator = new FormValidator();
28 |
29 | formValidator.validate({ acceptsTermsAndConditions: true });
30 | // ✔ {}
31 |
32 | formValidator.validate({ acceptsTermsAndConditions: false });
33 | // ❌ { acceptsTermsAndConditions: `Value must not equal 'false'` }
34 | ```
35 |
36 | ## Reference
37 |
38 | ### `.notEqual(comparisonValue: TValue)`
39 |
40 | A base validation rule which takes in a value and ensures that the given property is not equal to that value.
41 |
42 | ### `TValue`
43 |
44 | Matches the type of the property that the rule is applied to.
45 |
46 | ## Example Message
47 |
48 | > Value must not equal '`[comparisonValue]`'
49 |
--------------------------------------------------------------------------------
/docs/api/rules/notNull.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: notNull
3 | title: '.notNull'
4 | ---
5 |
6 | The `.notNull` rule is used to ensure that the value of a given property is not `null` (including `undefined` by default, though this is configurable).
7 |
8 | :::tip
9 |
10 | If you only want to check for `undefined` values, you may use the [`.notUndefined`](./notUndefined.md) rule instead.
11 |
12 | :::
13 |
14 | ## Examples
15 |
16 | ### Default Usage
17 |
18 | If you don't specify any options, the rule will check that the given value is not `null` or `undefined`.
19 |
20 | In other words, the `includeUndefined` option is defaulted to `true` - this decision was made to avoid introducing a breaking change.
21 |
22 | In this setup, both `null` and `undefined` values will be considered invalid.
23 |
24 | ```typescript
25 | import { Validator } from 'fluentvalidation-ts';
26 |
27 | type FormModel = {
28 | customerId?: number | null;
29 | };
30 |
31 | class FormValidator extends Validator {
32 | constructor() {
33 | super();
34 |
35 | this.ruleFor('customerId').notNull();
36 | }
37 | }
38 |
39 | const formValidator = new FormValidator();
40 |
41 | formValidator.validate({ customerId: 100 });
42 | // ✔ {}
43 |
44 | formValidator.validate({ customerId: null });
45 | // ❌ { customerId: 'Value cannot be null' }
46 |
47 | formValidator.validate({ customerId: undefined });
48 | // ❌ { customerId: 'Value cannot be null' }
49 |
50 | formValidator.validate({});
51 | // ❌ { customerId: 'Value cannot be null' }
52 | ```
53 |
54 | ### Excluding `undefined`
55 |
56 | The behaviour of the `.notNull` rule can be made "strict" (in the sense that it only checks for `null` and not `undefined`) by passing the `includeUndefined` option as `false`.
57 |
58 | In this setup, `undefined` values will be allowed, and only `null` values will be considered invalid.
59 |
60 | ```typescript
61 | import { Validator } from 'fluentvalidation-ts';
62 |
63 | type FormModel = {
64 | customerId?: number | null;
65 | };
66 |
67 | class FormValidator extends Validator {
68 | constructor() {
69 | super();
70 |
71 | // highlight-next-line
72 | this.ruleFor('customerId').notNull({ includeUndefined: false });
73 | }
74 | }
75 |
76 | const formValidator = new FormValidator();
77 |
78 | formValidator.validate({ customerId: 100 });
79 | // ✔ {}
80 |
81 | formValidator.validate({ customerId: null });
82 | // ❌ { customerId: 'Value cannot be null' }
83 |
84 | // highlight-start
85 | formValidator.validate({ customerId: undefined });
86 | // ✔ {}
87 |
88 | formValidator.validate({});
89 | // ✔ {}
90 | // highlight-end
91 | ```
92 |
93 | ## Reference
94 |
95 | ### `.notNull(ruleOptions?: NotNullRuleOptions)`
96 |
97 | A validation rule which ensures that the given property is not `null` (or `undefined`, depending on the value of `ruleOptions`).
98 |
99 | The default value of `ruleOptions` is `{ includeUndefined: true }`, meaning that both `null` and `undefined` values will be considered invalid.
100 |
101 | ### `NotNullRuleOptions`
102 |
103 | Equivalent to `{ includeUndefined: boolean }`, where the `includeUndefined` property determines whether `undefined` values should be considered invalid.
104 |
105 | When `includeUndefined` is `true`, both `null` and `undefined` values will be considered invalid.
106 |
107 | When `includeUndefined` is `false`, only `null` values will be considered invalid, and `undefined` values will be allowed.
108 |
109 | ## Example Message
110 |
111 | > Value cannot be null
112 |
--------------------------------------------------------------------------------
/docs/api/rules/notUndefined.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: notUndefined
3 | title: '.notUndefined'
4 | ---
5 |
6 | The `.notUndefined` rule is used to ensure that the value of a given property is not `undefined`.
7 |
8 | :::note
9 |
10 | Note that this rule considers `null` values to be **valid**. If you need to disallow both `null` and `undefined` values (or just `null` values), you may use the [`.notNull`](./notNull.md) rule instead.
11 |
12 | :::
13 |
14 | ## Example
15 |
16 | ```typescript
17 | import { Validator } from 'fluentvalidation-ts';
18 |
19 | type FormModel = {
20 | customerId?: number | null;
21 | };
22 |
23 | class FormValidator extends Validator {
24 | constructor() {
25 | super();
26 |
27 | this.ruleFor('customerId').notUndefined();
28 | }
29 | }
30 |
31 | const formValidator = new FormValidator();
32 |
33 | formValidator.validate({ customerId: 100 });
34 | // ✔ {}
35 |
36 | formValidator.validate({ customerId: null });
37 | // ✔ {}
38 |
39 | formValidator.validate({});
40 | // ❌ { customerId: 'Value cannot be undefined' }
41 |
42 | formValidator.validate({ customerId: undefined });
43 | // ❌ { customerId: 'Value cannot be undefined' }
44 | ```
45 |
46 | ## Reference
47 |
48 | ### `.notUndefined()`
49 |
50 | A validation rule which ensures that the given property is not `undefined`.
51 |
52 | ## Example Message
53 |
54 | > Value cannot be undefined
55 |
--------------------------------------------------------------------------------
/docs/api/rules/null.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: nullRule
3 | title: '.null'
4 | ---
5 |
6 | The `.null` rule is used to ensure that the value of a given property is `null` (or `undefined` by default, though this is configurable).
7 |
8 | :::tip
9 |
10 | If you only want to check for `undefined` values, you may use the [`.undefined`](./undefined.md) rule instead.
11 |
12 | :::
13 |
14 | ## Examples
15 |
16 | ### Default Usage
17 |
18 | If you don't specify any options, the rule will check that the given value is `null` or `undefined`.
19 |
20 | In other words, the `includeUndefined` option is defaulted to `true` - this decision was made to avoid introducing a breaking change.
21 |
22 | In this setup, both `null` and `undefined` values will be considered valid.
23 |
24 | ```typescript
25 | import { Validator } from 'fluentvalidation-ts';
26 |
27 | type FormModel = {
28 | apiError?: string | null;
29 | };
30 |
31 | class FormValidator extends Validator {
32 | constructor() {
33 | super();
34 |
35 | this.ruleFor('apiError').null();
36 | }
37 | }
38 |
39 | const formValidator = new FormValidator();
40 |
41 | formValidator.validate({ apiError: null });
42 | // ✔ {}
43 |
44 | formValidator.validate({ apiError: 'Failed to fetch data from the API' });
45 | // ❌ { apiError: 'Value must be null' }
46 |
47 | formValidator.validate({ apiError: undefined });
48 | // ✔ {}
49 |
50 | formValidator.validate({});
51 | // ✔ {}
52 | ```
53 |
54 | ### Excluding `undefined`
55 |
56 | The behaviour of the `.null` rule can be made "strict" (in the sense that it only checks for `null` and not `undefined`) by passing the `includeUndefined` option as `false`.
57 |
58 | In this setup, `undefined` values will be considered invalid, and only `null` values will be allowed.
59 |
60 | ```typescript
61 | import { Validator } from 'fluentvalidation-ts';
62 |
63 | type FormModel = {
64 | apiError?: string | null;
65 | };
66 |
67 | class FormValidator extends Validator {
68 | constructor() {
69 | super();
70 |
71 | this.ruleFor('apiError').null({ includeUndefined: false });
72 | }
73 | }
74 |
75 | const formValidator = new FormValidator();
76 |
77 | formValidator.validate({ apiError: null });
78 | // ✔ {}
79 |
80 | formValidator.validate({ apiError: 'Failed to fetch data from the API' });
81 | // ❌ { apiError: 'Value must be null' }
82 |
83 | // highlight-start
84 | formValidator.validate({ apiError: undefined });
85 | // ❌ { apiError: 'Value must be null' }
86 |
87 | formValidator.validate({});
88 | // ❌ { apiError: 'Value must be null' }
89 | // highlight-end
90 | ```
91 |
92 | ## Reference
93 |
94 | ### `.null(ruleOptions?: NullRuleOptions)`
95 |
96 | A validation rule which ensures that the given property is `null` (or `undefined`, depending on the value of `ruleOptions`).
97 |
98 | The default value of `ruleOptions` is `{ includeUndefined: true }`, meaning that both `null` and `undefined` values will be considered valid.
99 |
100 | ### `NullRuleOptions`
101 |
102 | Equivalent to `{ includeUndefined: boolean }`, where the `includeUndefined` property determines whether `undefined` values should be considered valid.
103 |
104 | When `includeUndefined` is `true`, both `null` and `undefined` values will be considered valid.
105 |
106 | When `includeUndefined` is `false`, only `null` values will be considered valid, and `undefined` values will be considered invalid.
107 |
108 | ## Example Message
109 |
110 | > Value must be null
111 |
--------------------------------------------------------------------------------
/docs/api/rules/precisionScale.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: precisionScale
3 | title: '.precisionScale'
4 | ---
5 |
6 | The `.precisionScale` rule is used to ensure that the value of a given `number` property is permissible for the specified **precision** and **scale**.
7 |
8 | These terms are defined as follows:
9 |
10 | - **Precision** is the number of digits in a number.
11 | - **Scale** is the number of digits to the right of the decimal point in a number.
12 |
13 | :::warning
14 |
15 | Prior to `v5.0.0` the `.precisionScale` rule was called `.scalePrecision` and the parameter naming was incorrect!
16 |
17 | :::
18 |
19 | ## Example
20 |
21 | ```typescript
22 | import { Validator } from 'fluentvalidation-ts';
23 |
24 | type FormModel = {
25 | price: number;
26 | };
27 |
28 | class FormValidator extends Validator {
29 | constructor() {
30 | super();
31 |
32 | this.ruleFor('price').precisionScale(4, 2);
33 | }
34 | }
35 |
36 | const formValidator = new FormValidator();
37 |
38 | formValidator.validate({ price: 10.01 });
39 | // ✔ {}
40 |
41 | formValidator.validate({ price: 0.001 }); // Too many digits after the decimal point
42 | // ❌ { price: 'Value must be no more than 4 digits in total, with allowance for 2 decimals' }
43 |
44 | formValidator.validate({ price: 100.1 }); // Too many digits (when accounting for reserved digits after the decimal point)
45 | // ❌ { price: 'Value must be no more than 4 digits in total, with allowance for 2 decimals' }
46 | ```
47 |
48 | ## Reference
49 |
50 | ### `.precisionScale(precision: number, scale: number)`
51 |
52 | A number validation rule which takes in an allowed precision and scale, and ensures that the value of the given property is permissible.
53 |
54 | :::danger
55 |
56 | Due to rounding issues with floating point numbers in JavaScript, this rule may not function as expected for large precisions/scales.
57 |
58 | :::
59 |
60 | ### `precision`
61 |
62 | This is the total number of digits that the value may have (taking into account the number of digits "reserved" for after the decimal point).
63 |
64 | The maximum number of significant digits allowed before the decimal point (i.e. the integer part) can be calculated as `(precision - scale)`.
65 |
66 | ### `scale`
67 |
68 | This is the maximum number of digits after the decimal point that the value may have.
69 |
70 | :::note
71 |
72 | When `precision` and `scale` are equal, the "leading zero" to the left of the decimal point is **not** counted as a digit (e.g. a value of `0.01` would be viewed as `.01`).
73 |
74 | :::
75 |
76 | ## Example Message
77 |
78 | > Value must not be more than `[precision]` digits in total, with allowance for `[scale]` decimals
79 |
--------------------------------------------------------------------------------
/docs/api/rules/setAsyncValidator.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: setAsyncValidator
3 | title: '.setAsyncValidator'
4 | ---
5 |
6 | The `.setAsyncValidator` rule is one of the special async rules that become available when you extend from [`AsyncValidator`](api/core/asyncValidator.md) as opposed to just [`Validator`](api/core/validator.md).
7 |
8 | This rule works exactly the same as the [`.setValidator`](api/rules/setValidator.md) rule, except that you must pass an instance of [`AsyncValidator`](api/core/asyncValidator.md) as opposed to an instance of [`Validator`](api/core/validator.md).
9 |
10 | As with the [`.setValidator`](api/rules/setValidator.md) rule, the async validator to use is specified by way of a producer function, which takes in the value of the base model and returns an appropriate validator.
11 |
12 | ## Examples
13 |
14 | The documentation page for the [`.setValidator`](api/rules/setValidator.md) rule includes a full list of examples demonstrating the different overloads that are available.
15 |
16 | These are all relevant to the `.setAsyncValidator` rule too, just replace `Validator` with `AsyncValidator` and `.setValidator` with `.setAsyncValidator`.
17 |
18 | ### Nested validator does not depend on the base model
19 |
20 | In this example the nested validator has no dependency on the base model, so we can simply define an instance of the nested validator ahead of time and return that from the validator producer function.
21 |
22 | ```typescript
23 | import { AsyncValidator } from 'fluentvalidation-ts';
24 |
25 | type ContactDetails = {
26 | name: string;
27 | emailAddress: string;
28 | };
29 |
30 | // highlight-start
31 | class ContactDetailsValidator extends AsyncValidator {
32 | constructor() {
33 | super();
34 |
35 | this.ruleFor('name').notEmpty();
36 |
37 | this.ruleFor('emailAddress')
38 | .emailAddress()
39 | .mustAsync(
40 | async (emailAddress) => await api.emailAddressNotInUse(emailAddress)
41 | )
42 | .withMessage('This email address is already in use');
43 | }
44 | }
45 |
46 | const contactDetailsValidator = new ContactDetailsValidator();
47 | // highlight-end
48 |
49 | type FormModel = {
50 | contactDetails: ContactDetails;
51 | };
52 |
53 | class FormValidator extends AsyncValidator {
54 | constructor() {
55 | super();
56 |
57 | // highlight-start
58 | this.ruleFor('contactDetails').setAsyncValidator(
59 | () => contactDetailsValidator
60 | );
61 | // highlight-end
62 | }
63 | }
64 |
65 | const formValidator = new FormValidator();
66 |
67 | await formValidator.validateAsync({
68 | contactDetails: { name: 'Alex', emailAddress: 'alex123@example.com' },
69 | });
70 | // ✔ {}
71 |
72 | await formValidator.validateAsync({
73 | contactDetails: { name: 'Alex', emailAddress: 'alex@example.com' },
74 | });
75 | // ❌ { contactDetails: { emailAddress: 'This email address is already in use' } }
76 | ```
77 |
78 | ## Reference
79 |
80 | ### `.setAsyncValidator(asyncValidatorProducer: (model: TModel) => AsyncValidator)`
81 |
82 | A validation rule which takes in a validator producer function and ensures that the given property is valid according to the async validator produced by that function.
83 |
84 | ### `TModel`
85 |
86 | Matches the type of the base model.
87 |
88 | ### `TValue`
89 |
90 | Matches the type of the property that the rule is applied to.
91 |
92 | ### `AsyncValidator`
93 |
94 | The [`AsyncValidator`](api/core/asyncValidator.md) generic class provided by **fluentvalidation-ts**.
95 |
--------------------------------------------------------------------------------
/docs/api/rules/setValidator.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: setValidator
3 | title: '.setValidator'
4 | ---
5 |
6 | The `.setValidator` rule is used to ensure that the value of a given `object` property is valid according to a given [`Validator`](api/core/validator.md).
7 |
8 | The validator to use is specified by way of a producer function, which takes in the value of the base model and returns an appropriate validator.
9 |
10 | This approach enables the nested validator to depend on the base model, and makes recursive validation possible.
11 |
12 | ## Examples
13 |
14 | ### Nested validator does not depend on the base model
15 |
16 | In this example the nested validator has no dependency on the base model, so we can simply define an instance of the nested validator ahead of time and return that from the validator producer function.
17 |
18 | ```typescript
19 | import { Validator } from 'fluentvalidation-ts';
20 |
21 | // highlight-start
22 | type ContactDetails = {
23 | name: string;
24 | emailAddress: string;
25 | };
26 |
27 | class ContactDetailsValidator extends Validator {
28 | constructor() {
29 | super();
30 |
31 | this.ruleFor('name').notEmpty();
32 |
33 | this.ruleFor('emailAddress').emailAddress();
34 | }
35 | }
36 |
37 | const contactDetailsValidator = new ContactDetailsValidator();
38 | // highlight-end
39 |
40 | type FormModel = {
41 | // highlight-next-line
42 | contactDetails: ContactDetails;
43 | };
44 |
45 | class FormValidator extends Validator {
46 | constructor() {
47 | super();
48 |
49 | // highlight-next-line
50 | this.ruleFor('contactDetails').setValidator(() => contactDetailsValidator);
51 | }
52 | }
53 |
54 | const formValidator = new FormValidator();
55 |
56 | formValidator.validate({
57 | contactDetails: { name: 'Alex', emailAddress: 'alex@example.com' },
58 | });
59 | // ✔ {}
60 |
61 | formValidator.validate({
62 | contactDetails: { name: '', emailAddress: 'alex@example.com' },
63 | });
64 | // ❌ { contactDetails: { name: 'Value cannot be empty' } }
65 | ```
66 |
67 | ### Nested validator depends on the base model
68 |
69 | In this example the nested validator has a constructor argument which changes its behaviour.
70 |
71 | In particular, we only require an email address to be given if the user has indicated that they wish to sign up to the mailing list.
72 |
73 | ```typescript
74 | import { Validator } from 'fluentvalidation-ts';
75 |
76 | type ContactDetails = {
77 | name: string;
78 | emailAddress: string | null;
79 | };
80 |
81 | class ContactDetailsValidator extends Validator {
82 | // highlight-next-line
83 | constructor(emailAddressIsRequired: boolean) {
84 | super();
85 |
86 | this.ruleFor('name').notEmpty();
87 |
88 | this.ruleFor('emailAddress')
89 | .notNull()
90 | // highlight-next-line
91 | .when(() => emailAddressIsRequired);
92 |
93 | this.ruleFor('emailAddress').emailAddress();
94 | }
95 | }
96 |
97 | type FormModel = {
98 | signUpToMailingList: boolean;
99 | contactDetails: ContactDetails;
100 | };
101 |
102 | class FormValidator extends Validator {
103 | constructor() {
104 | super();
105 |
106 | this.ruleFor('contactDetails').setValidator(
107 | // highlight-next-line
108 | (formModel) => new ContactDetailsValidator(formModel.signUpToMailingList)
109 | );
110 | }
111 | }
112 |
113 | const formValidator = new FormValidator();
114 |
115 | formValidator.validate({
116 | signUpToMailingList: false,
117 | contactDetails: { name: 'Alex', emailAddress: null },
118 | });
119 | // ✔ {}
120 |
121 | formValidator.validate({
122 | signUpToMailingList: true,
123 | contactDetails: { name: 'Alex', emailAddress: null },
124 | });
125 | // ❌ { contactDetails: { emailAddress: 'Value cannot be null' } }
126 | ```
127 |
128 | ### Recursive validators
129 |
130 | In this example we deal with validating a recursive (self-referencing) model.
131 |
132 | In particular, an employee might have a line manager, who is also an employee. This line manager might themselves have a line manager, and so on.
133 |
134 | ```typescript
135 | import { Validator } from 'fluentvalidation-ts';
136 |
137 | type Employee = {
138 | name: string;
139 | lineManager: Employee | null;
140 | };
141 |
142 | class EmployeeValidator extends Validator {
143 | constructor() {
144 | super();
145 |
146 | this.ruleFor('name').notEmpty();
147 |
148 | // highlight-next-line
149 | this.ruleFor('lineManager').setValidator(() => new EmployeeValidator());
150 | }
151 | }
152 |
153 | const validator = new EmployeeValidator();
154 |
155 | validator.validate({
156 | name: 'Bob',
157 | lineManager: {
158 | name: 'Alice',
159 | lineManager: null,
160 | },
161 | });
162 | // ✔ {}
163 |
164 | validator.validate({
165 | name: 'Alex',
166 | lineManager: {
167 | name: '',
168 | lineManager: null,
169 | },
170 | });
171 | // ❌ { lineManager: { name: 'Value cannot be empty' } }
172 | ```
173 |
174 | ## Reference
175 |
176 | ### `.setValidator(validatorProducer: (model: TModel) => Validator)`
177 |
178 | A validation rule which takes in a validator producer function and ensures that the given property is valid according to the validator produced by that function.
179 |
180 | ### `TModel`
181 |
182 | Matches the type of the base model.
183 |
184 | ### `TValue`
185 |
186 | Matches the type of the property that the rule is applied to.
187 |
188 | ### `Validator`
189 |
190 | The [`Validator`](api/core/validator.md) generic class provided by **fluentvalidation-ts**.
191 |
--------------------------------------------------------------------------------
/docs/api/rules/undefined.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: undefinedRule
3 | title: '.undefined'
4 | ---
5 |
6 | The `.undefined` rule is used to ensure that the value of a given property is `undefined`.
7 |
8 | :::note
9 |
10 | Note that this rule considers `null` values to be **invalid**. If you need to allow for both `null` and `undefined` values (or just `null` values), you may use the [`.null`](./null.md) rule instead.
11 |
12 | :::
13 |
14 | ## Example
15 |
16 | ```typescript
17 | import { Validator } from 'fluentvalidation-ts';
18 |
19 | type FormModel = {
20 | customerId?: number | null;
21 | };
22 |
23 | class FormValidator extends Validator {
24 | constructor() {
25 | super();
26 |
27 | this.ruleFor('customerId').undefined();
28 | }
29 | }
30 |
31 | const formValidator = new FormValidator();
32 |
33 | formValidator.validate({});
34 | // ✔ {}
35 |
36 | formValidator.validate({ customerId: undefined });
37 | // ✔ {}
38 |
39 | formValidator.validate({ customerId: 100 });
40 | // ❌ { customerId: 'Value must be undefined' }
41 |
42 | formValidator.validate({ customerId: null });
43 | // ❌ { customerId: 'Value must be undefined' }
44 | ```
45 |
46 | ## Reference
47 |
48 | ### `.undefined()`
49 |
50 | A validation rule which ensures that the given property is `undefined`.
51 |
52 | ## Example Message
53 |
54 | > Value must be undefined
55 |
--------------------------------------------------------------------------------
/docs/guides/ambientContext.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: ambientContext
3 | title: Ambient Context
4 | ---
5 |
6 | Sometimes your validation logic will need to depend on external, or "ambient", context that isn't part of your form model. With **fluentvalidation-ts** validators are just classes, so you can make use of constructor arguments to inject dependencies.
7 |
8 | ## The Gist
9 |
10 | You can inject external dependencies into your validators using constructor arguments:
11 |
12 | ```typescript
13 | type FormModel = {
14 | age: number;
15 | };
16 |
17 | class FormValidator extends Validator {
18 | // highlight-next-line
19 | constructor(country: string) {
20 | super();
21 |
22 | this.ruleFor('age')
23 | // highlight-next-line
24 | .greaterThanOrEqualTo(country === 'US' ? 21 : 18);
25 | }
26 | }
27 | ```
28 |
29 | This approach means that you need to instantiate a new instance of your validator every time the ambient context changes, so there is potentially a performance cost involved.
30 |
31 | Usage of the example validator from above might look something like this:
32 |
33 | ```typescript
34 | const ukValidator = new FormValidator('UK');
35 | const usValidator = new FormValidator('US');
36 |
37 | const pubGoer = { age: 20 };
38 |
39 | const ukResult = ukValidator.validate(pubGoer); // {}
40 | const usResult = usValidator.validate(pubGoer); // { age: 'Value must be greater than or equal to 21' }
41 | ```
42 |
--------------------------------------------------------------------------------
/docs/guides/arrayProperties.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: arrayProperties
3 | title: Array Properties
4 | ---
5 |
6 | Validating array properties is made easy with the [`.ruleForEach`](api/core/ruleForEach.md) method.
7 |
8 | The `.ruleForEach` method works almost exactly the same as the [`.ruleFor`](api/core/ruleFor.md) method, so it's worth reading up on that first if you haven't already.
9 |
10 | ## The Gist
11 |
12 | You can validate an array property using the `.ruleFor` method:
13 |
14 | ```typescript
15 | this.ruleFor('scores').must(
16 | (scores) => scores.filter((score) => score < 0 || score > 100).length === 0
17 | );
18 | ```
19 |
20 | Alternatively, you can use the `.ruleForEach` method:
21 |
22 | ```typescript
23 | this.ruleForEach('scores').greaterThanOrEqualTo(0).lessThanOrEqualTo(100);
24 | ```
25 |
--------------------------------------------------------------------------------
/docs/guides/customRules.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: customRules
3 | title: Custom Rules
4 | ---
5 |
6 | One of the main features of **fluentvalidation-ts** is that it is fully extensible, allowing you define your own custom validation logic and inject it via the [`.must`](api/rules/must.md) rule.
7 |
8 | The [documentation page](api/rules/must.md) for the `.must` rule contains several [examples](api/rules/must.md#examples) that demonstrate the different ways in which you can define and consume custom rules, as well as a full [API reference](api/rules/must.md#reference) which outlines everything in detail.
9 |
10 | ## The Gist
11 |
12 | Custom validation logic is defined by way of a **predicate** function, which takes a value and returns a boolean (true/false) value indicating whether or not the value is valid.
13 |
14 | You can pass custom validation logic directly into the `.must` rule with a predicate:
15 |
16 | ```typescript
17 | this.ruleFor('numberOfSocks').must((numberOfSocks) => numberOfSocks % 2 === 0);
18 | ```
19 |
20 | If you want to reuse the logic, you could pull it out into a named function:
21 |
22 | ```typescript
23 | const beEven = (value: number) => value % 2 === 0;
24 | ```
25 |
26 | Then you can just pass the named function into `.must`, like so:
27 |
28 | ```typescript
29 | this.ruleFor('numberOfSocks').must(beEven);
30 | ```
31 |
32 | The predicate function can also depend on the value of the model as well as the value of the property:
33 |
34 | ```typescript
35 | this.ruleFor('numberOfSocks').must(
36 | // highlight-next-line
37 | (numberOfSocks, model) => numberOfSocks === 2 * model.numberOfPants
38 | );
39 | ```
40 |
41 | You can define groups of rules by forming arrays:
42 |
43 | ```typescript
44 | const beEven = (value: number) => value % 2 === 0;
45 | const bePositive = (value: number) => value > 0;
46 |
47 | // highlight-next-line
48 | const beEvenAndPositive = [beEven, bePositive];
49 | ```
50 |
51 | These arrays can be passed directly to the `.must` rule:
52 |
53 | ```typescript
54 | this.ruleFor('numberOfSocks').must(beEvenAndPositive);
55 | ```
56 |
57 | You can also attach a custom message to your rule, alongside the predicate:
58 |
59 | ```typescript
60 | const beEven = {
61 | predicate: (value: number) => value % 2 === 0,
62 | // highlight-next-line
63 | message: 'Please enter an even number',
64 | };
65 | ```
66 |
67 | As before, you just pass this into the `.must` rule directly:
68 |
69 | ```typescript
70 | this.ruleFor('numberOfSocks').must(beEven);
71 | ```
72 |
73 | Again, you can use arrays to compose rules together:
74 |
75 | ```typescript
76 | const beEven = {
77 | predicate: (value: number) => value % 2 === 0,
78 | message: 'Please enter an even number',
79 | };
80 |
81 | const bePositive = {
82 | predicate: (value: number) => value > 0,
83 | message: 'Please enter a positive number',
84 | };
85 |
86 | // highlight-next-line
87 | const beEvenAndPositive = [beEven, bePositive];
88 | ```
89 |
90 | You can even compose groups of rules together by spreading or concatenating the arrays:
91 |
92 | ```typescript
93 | const newRuleGroup = [...ruleGroup, ...otherRuleGroup];
94 | ```
95 |
--------------------------------------------------------------------------------
/docs/guides/formik.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: formik
3 | title: Formik
4 | ---
5 |
6 | When I first wrote **fluentvalidation-ts**, I had seamless integration with [Formik](https://formik.org/) in mind.
7 |
8 | The [`ValidationErrors`](/docs/api/core/ValidationErrors) object returned by the [`.validate`](/docs/api/core/validator#validate) function has been designed to "just work" with Formik, so you can start using the two together with minimal effort.
9 |
10 | If you're not familiar with Formik, it's a fantastic library for writing forms in [React](https://react.dev/).
11 |
12 | ## Usage
13 |
14 | To use **fluentvalidation-ts** with Formik, simply define a `Validator` for your form model, instantiate an instance of your validator, then pass the validator's [`.validate`](https://formik.org/docs/guides/validation#validate) method to Formik's `validate` prop:
15 |
16 | ```tsx
17 | import { Formik } from 'formik';
18 | import { Validator } from 'fluentvalidation-ts';
19 |
20 | type FormModel = { username: string };
21 |
22 | // highlight-start
23 | class MyFormValidator extends Validator {
24 | constructor() {
25 | super();
26 | this.ruleFor('username').notEmpty().withMessage('Please enter your username');
27 | }
28 | }
29 |
30 | const formValidator = new MyFormValidator();
31 | // highlight-end
32 |
33 | export const MyForm = () => (
34 |
35 | // highlight-next-line
36 | validate={formValidator.validate}
37 | ...
38 | >
39 | ...
40 |
41 | );
42 | ```
43 |
--------------------------------------------------------------------------------
/docs/guides/objectProperties.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: objectProperties
3 | title: Object Properties
4 | ---
5 |
6 | Object properties can be validated by way of the [`.setValidator`](api/rules/setValidator.md) rule.
7 |
8 | The [documentation page](api/rules/setValidator.md) for the `.setValidator` rule contains several [examples](api/rules/setValidator.md#examples) that demonstrate the different ways in which you can use it, as well as a full [API reference](api/rules/setValidator.md#reference) which outlines everything in detail.
9 |
10 | ## The Gist
11 |
12 | You can validate an object property using the built-in rules:
13 |
14 | ```typescript
15 | this.ruleFor('pet')
16 | .notNull()
17 | .must((pet) => pet.age >= 0)
18 | .must((pet) => pet.name !== '');
19 | ```
20 |
21 | Alternatively, you can define a validator for the type of the object property:
22 |
23 | ```typescript
24 | class PetValidator extends Validator {
25 | constructor() {
26 | super();
27 | this.ruleFor('age').greaterThanOrEqualTo(0);
28 | this.ruleFor('name').notEmpty();
29 | }
30 | }
31 |
32 | const petValidator = new PetValidator();
33 | ```
34 |
35 | This can then be passed in with the `.setValidator` rule:
36 |
37 | ```typescript
38 | this.ruleFor('pet')
39 | .notNull()
40 | // highlight-next-line
41 | .setValidator(() => petValidator);
42 | ```
43 |
--------------------------------------------------------------------------------
/docs/guides/reactHookForm.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: reactHookForm
3 | title: React Hook Form
4 | ---
5 |
6 | While **fluentvalidation-ts** was originally developed with Formik integration in mind, [React Hook Form](https://react-hook-form.com/) has become increasingly popular in the React community. Thankfully, wonderful members of the community have contributed a [fluentvalidation-ts resolver](https://github.com/react-hook-form/resolvers?tab=readme-ov-file#fluentvalidation-ts) for React Hook Form, allowing you to integrate it seamlessly with no effort required on your part!
7 |
--------------------------------------------------------------------------------
/docs/overview.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: overview
3 | title: Overview
4 | ---
5 |
6 | Front-end validation is a must-have for any project that involves forms, but the requirements vary hugely. You might have a simple sign-up form with a few text fields, or a complex configuration page with collections and deeply nested fields.
7 |
8 | There are plenty of libraries out there which help you to solve the problem of front-end validation, but all the ones I've tried have felt lacking in one aspect or another - whether that's TypeScript support, their capacity to handle complex requirements, the ability to define your own reusable validation logic, or just the expressiveness of the API.
9 |
10 | So I wrote **fluentvalidation-ts**, a tiny library that is:
11 |
12 | - Designed for TypeScript
13 | - Simple yet powerful
14 | - Fully extensible
15 |
16 | Whatever your validation needs, **fluentvalidation-ts** can handle them.
17 |
18 | ## Compatibility
19 |
20 | **fluentvalidation-ts** is completely framework-agnostic, so you can use it with any front-end framework or library. It has no dependencies, and is designed to be as lightweight as possible. Having said that, it has primarily been designed to integrate seamlessly with popular form libraries for React - see the guides on [Formik](/docs/guides/formik) and [React Hook Form](/docs/guides/reactHookForm) for more information.
21 |
22 | ## Influences
23 |
24 | If you've ever worked on a .NET API, you might have heard of a library called [FluentValidation](https://fluentvalidation.net/). It has a really nice API for building up validation rules, and that made me wonder whether I could achieve something similar in TypeScript. While **fluentvalidation-ts** is not a direct port, it will still feel very familiar to anyone who's used FluentValidation before.
25 |
26 | ## Installation
27 |
28 | You can install **fluentvalidation-ts** with NPM/Yarn, or include it directly via a `
50 | ```
51 |
52 | Or, to target a specific version (e.g. `4.0.0`), add the following:
53 |
54 | ```html
55 |
56 | ```
57 |
58 | Once you've done this, all you need is the `Validator` class which can be accessed via:
59 |
60 | ```js
61 | window['fluentvalidation'].Validator;
62 | ```
63 |
64 | ## The Gist
65 |
66 | To use **fluentvalidation-ts** simply import the `Validator` generic class, and define your own class which extends it using the appropriate generic type argument. Build up the rules for your various properties in the constructor of your derived class, then create an instance of your class to get hold of a validator. Finally, pass an instance of your model into the `.validate` function of your validator to obtain a validation errors object.
67 |
68 | ```typescript
69 | import { Validator } from 'fluentvalidation-ts';
70 |
71 | type FormModel = {
72 | name: string;
73 | age: number;
74 | };
75 |
76 | class FormValidator extends Validator {
77 | constructor() {
78 | super();
79 |
80 | this.ruleFor('name').notEmpty().withMessage('Please enter your name');
81 |
82 | this.ruleFor('age')
83 | .greaterThanOrEqualTo(0)
84 | .withMessage('Please enter a non-negative number');
85 | }
86 | }
87 |
88 | const formValidator = new FormValidator();
89 |
90 | const valid: FormModel = {
91 | name: 'Alex',
92 | age: 26,
93 | };
94 | formValidator.validate(valid);
95 | // {}
96 |
97 | const invalid: FormModel = {
98 | name: '',
99 | age: -1,
100 | };
101 | formValidator.validate(invalid);
102 | // { name: 'Please enter your name', age: 'Please enter a non-negative number' }
103 | ```
104 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js';
2 | import globals from 'globals';
3 | import { defineConfig } from 'eslint/config';
4 | import importPlugin from 'eslint-plugin-import';
5 | import tseslint from 'typescript-eslint';
6 |
7 | const ignores = ['website/', 'coverage/', 'dist/'];
8 |
9 | export default defineConfig([
10 | {
11 | ignores,
12 | },
13 | {
14 | plugins: { js },
15 | extends: ['js/recommended'],
16 | },
17 | {
18 | languageOptions: {
19 | globals: globals.browser,
20 | },
21 | },
22 | tseslint.configs.recommended,
23 | {
24 | extends: [
25 | importPlugin.flatConfigs.recommended,
26 | importPlugin.flatConfigs.typescript,
27 | ],
28 | rules: {
29 | '@typescript-eslint/no-explicit-any': 'error',
30 | 'import/order': 'error',
31 | 'import/no-unresolved': 'off',
32 | },
33 | },
34 | {
35 | files: ['test/**/*.ts'],
36 | rules: {
37 | '@typescript-eslint/no-explicit-any': 'off',
38 | '@typescript-eslint/no-unused-vars': 'off',
39 | '@typescript-eslint/ban-ts-comment': 'off',
40 | },
41 | },
42 | ]);
43 |
--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'jest';
2 |
3 | const config: Config = {
4 | collectCoverage: true,
5 | coverageDirectory: 'coverage',
6 | preset: 'ts-jest',
7 | transform: {
8 | '^.+\\.ts$': [
9 | 'ts-jest',
10 | {
11 | tsconfig: 'tsconfig.test.json',
12 | },
13 | ],
14 | },
15 | moduleNameMapper: {
16 | '^@/(.*)$': '/src/$1',
17 | },
18 | };
19 |
20 | export default config;
21 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexJPotter/fluentvalidation-ts/5a9f064835ea910aed9ab04bf3c0e89b6c04553c/logo.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fluentvalidation-ts",
3 | "version": "5.0.0",
4 | "description": "A TypeScript-first library for building strongly-typed validation rules",
5 | "keywords": [
6 | "fluent",
7 | "validation",
8 | "validator",
9 | "typescript",
10 | "form",
11 | "formik",
12 | "react-hook-form",
13 | "fluentvalidation"
14 | ],
15 | "homepage": "https://github.com/AlexJPotter/fluentvalidation-ts",
16 | "main": "dist/index.js",
17 | "author": "Alex Potter ",
18 | "license": "Apache-2.0",
19 | "private": false,
20 | "repository": {
21 | "type": "git",
22 | "url": "git+https://github.com/AlexJPotter/fluentvalidation-ts.git"
23 | },
24 | "bugs": {
25 | "url": "https://github.com/AlexJPotter/fluentvalidation-ts/issues"
26 | },
27 | "umd:main": "dist/index.global.js",
28 | "module": "dist/index.mjs",
29 | "typings": "dist/index.d.ts",
30 | "files": [
31 | "dist"
32 | ],
33 | "scripts": {
34 | "watch": "tsc --noEmit --watch",
35 | "build": "cross-env NODE_ENV=production tsup src/index.ts --dts --minify --treeshake --format cjs,esm,iife --global-name fluentvalidation --sourcemap",
36 | "test": "jest",
37 | "test:watch": "jest --watch",
38 | "lint": "eslint --fix",
39 | "lint:check": "eslint",
40 | "prettier": "prettier --write \"{src,test}/**/*.ts\"",
41 | "prettier:check": "prettier --check \"{src,test}/**/*.ts\"",
42 | "typecheck": "tsc --noEmit && tsc --noEmit --project tsconfig.test.json",
43 | "prepare": "husky"
44 | },
45 | "devDependencies": {
46 | "@eslint/js": "^9.26.0",
47 | "@swc/core": "^1.3.56",
48 | "@types/jest": "^29.5.14",
49 | "@typescript-eslint/parser": "^8.32.0",
50 | "cross-env": "^7.0.3",
51 | "eslint": "^9.26.0",
52 | "eslint-import-resolver-typescript": "^4.3.4",
53 | "eslint-plugin-import": "^2.31.0",
54 | "globals": "^16.1.0",
55 | "husky": "^9.1.7",
56 | "jest": "^29.7.0",
57 | "prettier": "^3.5.3",
58 | "pretty-quick": "^4.1.1",
59 | "ts-jest": "^29.3.2",
60 | "ts-node": "^10.9.2",
61 | "tslib": "^2.3.0",
62 | "tsup": "^8.4.0",
63 | "typescript": "^5.8.3",
64 | "typescript-eslint": "^8.32.0"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/AsyncValidator.ts:
--------------------------------------------------------------------------------
1 | import { AsyncArrayValueValidatorBuilder } from '@/valueValidator/AsyncArrayValueValidatorBuilder';
2 | import { AsyncValueValidatorBuilder } from '@/valueValidator/AsyncValueValidatorBuilder';
3 | import { ValidationErrors } from '@/ValidationErrors';
4 | import { AsyncRuleValidators } from '@/valueValidator/AsyncRuleValidators';
5 | import { hasError } from '@/valueValidator/ValueValidator';
6 | import { Constrain } from '@/types/Constrain';
7 | import { ValueValidationResult } from '@/ValueValidationResult';
8 | import { ValueTransformer } from '@/valueValidator/ValueTransformer';
9 | import { ArrayType } from '@/types/ArrayType';
10 | import { TransformedValue } from '@/types/TransformedValue';
11 | import { AsyncValueValidator } from '@/valueValidator/AsyncValueValidator';
12 | import { Optional } from '@/types/Optional';
13 | import { IfNotNeverThen } from '@/types/IfNotNeverThen';
14 |
15 | interface IAsyncValueValidatorBuilder {
16 | build: () => AsyncValueValidator;
17 | }
18 |
19 | export class AsyncValidator {
20 | private asyncValueValidatorBuildersByPropertyName: {
21 | [propertyName in keyof TModel]?: Array>;
22 | } = {};
23 |
24 | protected _validateAsync: (value: TModel) => Promise> = async () => {
25 | return Promise.resolve({});
26 | };
27 |
28 | public validateAsync = (value: TModel): Promise> =>
29 | this._validateAsync(value);
30 |
31 | private rebuildValidateAsync = () => {
32 | this._validateAsync = async (value: TModel): Promise> => {
33 | const errors: ValidationErrors = {};
34 |
35 | for (const propertyName of Object.keys(this.asyncValueValidatorBuildersByPropertyName)) {
36 | const asyncValueValidatorBuilders =
37 | this.asyncValueValidatorBuildersByPropertyName[propertyName as keyof TModel];
38 |
39 | for (const asyncValueValidatorBuilder of asyncValueValidatorBuilders!) {
40 | const asyncValueValidator = asyncValueValidatorBuilder.build();
41 |
42 | const result = (await asyncValueValidator(
43 | value[propertyName as keyof TModel],
44 | value,
45 | )) as ValueValidationResult;
46 |
47 | if (hasError(result)) {
48 | errors[propertyName as keyof TModel] = result;
49 | }
50 | }
51 | }
52 |
53 | return errors;
54 | };
55 | };
56 |
57 | protected ruleFor = (
58 | propertyName: TPropertyName,
59 | ): AsyncRuleValidators => {
60 | const asyncValueValidatorBuilder = new AsyncValueValidatorBuilder<
61 | TModel,
62 | TModel[TPropertyName],
63 | TransformedValue
64 | >(this.rebuildValidateAsync, (value) => value);
65 |
66 | this.asyncValueValidatorBuildersByPropertyName[propertyName] =
67 | this.asyncValueValidatorBuildersByPropertyName[propertyName] || [];
68 |
69 | this.asyncValueValidatorBuildersByPropertyName[propertyName]!.push(
70 | asyncValueValidatorBuilder as IAsyncValueValidatorBuilder,
71 | );
72 |
73 | return asyncValueValidatorBuilder.getAllRules() as AsyncRuleValidators;
74 | };
75 |
76 | protected ruleForTransformed = <
77 | TPropertyName extends keyof TModel,
78 | TValue extends TModel[TPropertyName],
79 | TTransformedValue extends TransformedValue,
80 | >(
81 | propertyName: TPropertyName,
82 | transformValue: (
83 | value: TValue,
84 | ) => TTransformedValue extends object
85 | ? Constrain
86 | : TTransformedValue,
87 | ): AsyncRuleValidators => {
88 | const asyncValueValidatorBuilder = new AsyncValueValidatorBuilder<
89 | TModel,
90 | TValue,
91 | TTransformedValue
92 | >(this.rebuildValidateAsync, transformValue as ValueTransformer);
93 |
94 | this.asyncValueValidatorBuildersByPropertyName[propertyName] =
95 | this.asyncValueValidatorBuildersByPropertyName[propertyName] || [];
96 |
97 | this.asyncValueValidatorBuildersByPropertyName[propertyName]!.push(
98 | asyncValueValidatorBuilder as IAsyncValueValidatorBuilder,
99 | );
100 |
101 | return asyncValueValidatorBuilder.getAllRules() as AsyncRuleValidators<
102 | TModel,
103 | TTransformedValue
104 | >;
105 | };
106 |
107 | protected ruleForEach = <
108 | TPropertyName extends keyof TModel,
109 | TEachValue extends TModel[TPropertyName] extends Optional>
110 | ? TEachValueInferred
111 | : never,
112 | TValue extends TModel[TPropertyName] & ArrayType,
113 | >(
114 | propertyName: IfNotNeverThen,
115 | ): IfNotNeverThen> => {
116 | const asyncArrayValueValidatorBuilder = new AsyncArrayValueValidatorBuilder<
117 | TModel,
118 | TValue,
119 | TEachValue,
120 | TEachValue
121 | >(this.rebuildValidateAsync, (value) => value);
122 |
123 | if (this.asyncValueValidatorBuildersByPropertyName[propertyName] == null) {
124 | this.asyncValueValidatorBuildersByPropertyName[propertyName] = [];
125 | }
126 |
127 | this.asyncValueValidatorBuildersByPropertyName[propertyName]!.push(
128 | asyncArrayValueValidatorBuilder as IAsyncValueValidatorBuilder,
129 | );
130 |
131 | return asyncArrayValueValidatorBuilder.getAllRules() as IfNotNeverThen<
132 | TEachValue,
133 | AsyncRuleValidators
134 | >;
135 | };
136 |
137 | protected ruleForEachTransformed = <
138 | TPropertyName extends keyof TModel,
139 | TEachValue extends TModel[TPropertyName] extends Optional>
140 | ? TEachValueInferred
141 | : never,
142 | TValue extends TModel[TPropertyName] & ArrayType,
143 | TEachTransformedValue extends TransformedValue,
144 | >(
145 | propertyName: IfNotNeverThen,
146 | transformValue: (
147 | value: TEachValue,
148 | ) => TEachTransformedValue extends object
149 | ? Constrain
150 | : TEachTransformedValue,
151 | ): IfNotNeverThen> => {
152 | const asyncArrayValueValidatorBuilder = new AsyncArrayValueValidatorBuilder<
153 | TModel,
154 | TValue,
155 | TEachValue,
156 | TEachTransformedValue
157 | >(this.rebuildValidateAsync, transformValue);
158 |
159 | if (this.asyncValueValidatorBuildersByPropertyName[propertyName] == null) {
160 | this.asyncValueValidatorBuildersByPropertyName[propertyName] = [];
161 | }
162 |
163 | this.asyncValueValidatorBuildersByPropertyName[propertyName]!.push(
164 | asyncArrayValueValidatorBuilder as IAsyncValueValidatorBuilder,
165 | );
166 |
167 | return asyncArrayValueValidatorBuilder.getAllRules() as IfNotNeverThen<
168 | TEachValue,
169 | AsyncRuleValidators
170 | >;
171 | };
172 | }
173 |
--------------------------------------------------------------------------------
/src/IAsyncValidator.ts:
--------------------------------------------------------------------------------
1 | import { ValidationErrors } from './ValidationErrors';
2 |
3 | export interface IAsyncValidator {
4 | validateAsync: (model: TModel) => Promise>;
5 | }
6 |
--------------------------------------------------------------------------------
/src/IValidator.ts:
--------------------------------------------------------------------------------
1 | import { ValidationErrors } from './ValidationErrors';
2 |
3 | export interface IValidator {
4 | validate: (model: TModel) => ValidationErrors;
5 | }
6 |
--------------------------------------------------------------------------------
/src/SyncValidator.ts:
--------------------------------------------------------------------------------
1 | import { ValidationErrors } from '@/ValidationErrors';
2 | import { ArrayValueValidatorBuilder } from '@/valueValidator/ArrayValueValidatorBuilder';
3 | import { RuleValidators } from '@/valueValidator/RuleValidators';
4 | import { hasError } from '@/valueValidator/ValueValidator';
5 | import { ValueValidatorBuilder } from '@/valueValidator/ValueValidatorBuilder';
6 | import { Constrain } from '@/types/Constrain';
7 | import { ArrayType } from '@/types/ArrayType';
8 | import { TransformedValue } from '@/types/TransformedValue';
9 | import { ValueValidator } from '@/ValueValidator';
10 | import { Optional } from '@/types/Optional';
11 | import { IfNotNeverThen } from '@/types/IfNotNeverThen';
12 |
13 | interface IValueValidatorBuilder {
14 | build: () => ValueValidator;
15 | }
16 |
17 | export class SyncValidator {
18 | private valueValidatorBuildersByPropertyName: {
19 | [propertyName in keyof TModel]?: Array>;
20 | } = {};
21 |
22 | protected _validate: (value: TModel) => ValidationErrors = () => {
23 | return {};
24 | };
25 |
26 | public validate = (value: TModel): ValidationErrors => this._validate(value);
27 |
28 | private rebuildValidate = () => {
29 | this._validate = (value: TModel): ValidationErrors => {
30 | const errors: ValidationErrors = {};
31 |
32 | for (const propertyName of Object.keys(this.valueValidatorBuildersByPropertyName)) {
33 | const valueValidatorBuilders =
34 | this.valueValidatorBuildersByPropertyName[propertyName as keyof TModel];
35 |
36 | for (const valueValidatorBuilder of valueValidatorBuilders!) {
37 | const valueValidator = valueValidatorBuilder.build();
38 |
39 | const result = valueValidator(value[propertyName as keyof TModel], value);
40 |
41 | if (hasError(result)) {
42 | errors[propertyName as keyof TModel] = result;
43 | }
44 | }
45 | }
46 |
47 | return errors;
48 | };
49 | };
50 |
51 | protected ruleFor = (
52 | propertyName: TPropertyName,
53 | ): RuleValidators => {
54 | const valueValidatorBuilder = new ValueValidatorBuilder(
55 | this.rebuildValidate,
56 | (value) => value,
57 | );
58 |
59 | this.valueValidatorBuildersByPropertyName[propertyName] =
60 | this.valueValidatorBuildersByPropertyName[propertyName] || [];
61 |
62 | this.valueValidatorBuildersByPropertyName[propertyName]!.push(
63 | valueValidatorBuilder as IValueValidatorBuilder,
64 | );
65 |
66 | return valueValidatorBuilder.getAllRules();
67 | };
68 |
69 | protected ruleForTransformed = <
70 | TPropertyName extends keyof TModel,
71 | TValue extends TModel[TPropertyName],
72 | TTransformedValue extends TransformedValue,
73 | >(
74 | propertyName: TPropertyName,
75 | transformValue: (
76 | value: TValue,
77 | ) => TTransformedValue extends object
78 | ? Constrain
79 | : TTransformedValue,
80 | ): RuleValidators => {
81 | const valueValidatorBuilder = new ValueValidatorBuilder(
82 | this.rebuildValidate,
83 | transformValue,
84 | );
85 |
86 | this.valueValidatorBuildersByPropertyName[propertyName] =
87 | this.valueValidatorBuildersByPropertyName[propertyName] || [];
88 |
89 | this.valueValidatorBuildersByPropertyName[propertyName]!.push(
90 | valueValidatorBuilder as IValueValidatorBuilder,
91 | );
92 |
93 | return valueValidatorBuilder.getAllRules();
94 | };
95 |
96 | protected ruleForEach = <
97 | TPropertyName extends keyof TModel,
98 | TEachValue extends TModel[TPropertyName] extends Optional>
99 | ? TEachValueInferred
100 | : never,
101 | TValue extends TModel[TPropertyName] & ArrayType,
102 | >(
103 | propertyName: IfNotNeverThen,
104 | ): IfNotNeverThen> => {
105 | const arrayValueValidatorBuilder = new ArrayValueValidatorBuilder<
106 | TModel,
107 | TValue,
108 | TEachValue,
109 | TEachValue
110 | >(this.rebuildValidate, (value) => value);
111 |
112 | if (this.valueValidatorBuildersByPropertyName[propertyName] == null) {
113 | this.valueValidatorBuildersByPropertyName[propertyName] = [];
114 | }
115 |
116 | this.valueValidatorBuildersByPropertyName[propertyName]!.push(
117 | arrayValueValidatorBuilder as IValueValidatorBuilder,
118 | );
119 |
120 | return arrayValueValidatorBuilder.getAllRules() as IfNotNeverThen<
121 | TEachValue,
122 | RuleValidators
123 | >;
124 | };
125 |
126 | protected ruleForEachTransformed = <
127 | TPropertyName extends keyof TModel,
128 | TEachValue extends TModel[TPropertyName] extends Optional>
129 | ? TEachValueInferred
130 | : never,
131 | TValue extends TModel[TPropertyName] & ArrayType,
132 | TEachTransformedValue extends TransformedValue,
133 | >(
134 | propertyName: IfNotNeverThen,
135 | transformValue: (
136 | value: TEachValue,
137 | ) => TEachTransformedValue extends object
138 | ? Constrain
139 | : TEachTransformedValue,
140 | ): IfNotNeverThen> => {
141 | const arrayValueValidatorBuilder = new ArrayValueValidatorBuilder<
142 | TModel,
143 | TValue,
144 | TEachValue,
145 | TEachTransformedValue
146 | >(this.rebuildValidate, transformValue);
147 |
148 | if (this.valueValidatorBuildersByPropertyName[propertyName] == null) {
149 | this.valueValidatorBuildersByPropertyName[propertyName] = [];
150 | }
151 |
152 | this.valueValidatorBuildersByPropertyName[propertyName]!.push(
153 | arrayValueValidatorBuilder as IValueValidatorBuilder,
154 | );
155 |
156 | return arrayValueValidatorBuilder.getAllRules() as IfNotNeverThen<
157 | TEachValue,
158 | RuleValidators
159 | >;
160 | };
161 | }
162 |
--------------------------------------------------------------------------------
/src/ValidationErrors.ts:
--------------------------------------------------------------------------------
1 | import { ValueValidationResult } from './ValueValidationResult';
2 |
3 | export type ValidationErrors = {
4 | [propertyName in keyof TModel]?: ValueValidationResult;
5 | };
6 |
--------------------------------------------------------------------------------
/src/ValueValidationResult.ts:
--------------------------------------------------------------------------------
1 | import { ArrayType } from '@/types/ArrayType';
2 |
3 | export type ValueValidationResult =
4 | | (TValue extends ArrayType
5 | ? Array> | string | null
6 | : TValue extends object
7 | ?
8 | | {
9 | [propertyName in keyof TValue]?: ValueValidationResult;
10 | }
11 | | string
12 | | null
13 | : string | null)
14 | | string
15 | | null;
16 |
--------------------------------------------------------------------------------
/src/ValueValidator.ts:
--------------------------------------------------------------------------------
1 | import { ValueValidationResult } from './ValueValidationResult';
2 |
3 | export type ValueValidator = (
4 | value: TValue,
5 | model: TModel,
6 | ) => ValueValidationResult;
7 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { SyncValidator as Validator } from './SyncValidator';
2 | export { AsyncValidator } from './AsyncValidator';
3 | export { ValidationErrors } from '@/ValidationErrors';
4 | export { ValueValidationResult } from '@/ValueValidationResult';
5 | export { RuleValidators } from '@/valueValidator/RuleValidators';
6 | export { AsyncRuleValidators } from '@/valueValidator/AsyncRuleValidators';
7 |
--------------------------------------------------------------------------------
/src/numberHelpers.ts:
--------------------------------------------------------------------------------
1 | export const formatNumber = (value: number) =>
2 | value.toLocaleString(undefined, { maximumFractionDigits: 20 });
3 |
--------------------------------------------------------------------------------
/src/rules/AsyncRule.ts:
--------------------------------------------------------------------------------
1 | import { CoreRule } from './CoreRule';
2 | import { AsyncValueValidator } from '@/valueValidator/AsyncValueValidator';
3 | import { ValueValidationResult } from '@/ValueValidationResult';
4 |
5 | export class AsyncRule extends CoreRule {
6 | private readonly asyncValueValidator: AsyncValueValidator;
7 |
8 | constructor(asyncValueValidator: AsyncValueValidator) {
9 | super();
10 | this.asyncValueValidator = asyncValueValidator;
11 | }
12 |
13 | public validateAsync = async (
14 | value: TValue,
15 | model: TModel,
16 | ): Promise> => {
17 | if (this.whenCondition != null && !this.whenCondition(model)) {
18 | return null;
19 | }
20 |
21 | if (this.unlessCondition != null && this.unlessCondition(model)) {
22 | return null;
23 | }
24 |
25 | const errorOrNull = await this.asyncValueValidator(value, model);
26 | return errorOrNull != null ? this.customErrorMessage || errorOrNull : null;
27 | };
28 | }
29 |
--------------------------------------------------------------------------------
/src/rules/AsyncValidatorRule.ts:
--------------------------------------------------------------------------------
1 | import { AsyncRule } from './AsyncRule';
2 | import { ValueValidationResult } from '@/ValueValidationResult';
3 | import { IAsyncValidator } from '@/IAsyncValidator';
4 |
5 | export class AsyncValidatorRule extends AsyncRule {
6 | constructor(validatorProducer: (model: TModel) => IAsyncValidator) {
7 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
8 | super(async (value: TValue, model: TModel) =>
9 | value == null
10 | ? Promise.resolve(null)
11 | : ((await validatorProducer(model).validateAsync(value)) as ValueValidationResult),
12 | );
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/rules/CoreRule.ts:
--------------------------------------------------------------------------------
1 | export class CoreRule {
2 | protected customErrorMessage?: string;
3 | protected whenCondition?: (model: TModel) => boolean;
4 | protected unlessCondition?: (model: TModel) => boolean;
5 |
6 | public setCustomErrorMessage = (customErrorMessage: string): void => {
7 | this.customErrorMessage = customErrorMessage;
8 | };
9 |
10 | public setWhenCondition = (condition: (model: TModel) => boolean) => {
11 | this.whenCondition = condition;
12 | };
13 |
14 | public setUnlessCondition = (condition: (model: TModel) => boolean) => {
15 | this.unlessCondition = condition;
16 | };
17 | }
18 |
--------------------------------------------------------------------------------
/src/rules/EmailAddressRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 |
3 | const emailAddressPattern = /^[a-zA-Z0-9.!#$%&’"*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$/;
4 |
5 | export class EmailAddressRule extends Rule {
6 | constructor() {
7 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
8 | super((value: TValue) => {
9 | if (value == null) {
10 | return null;
11 | }
12 | if (typeof value !== 'string') {
13 | throw new TypeError('A non-string value was passed to the emailAddress rule');
14 | }
15 | return emailAddressPattern.test(value) ? null : 'Not a valid email address';
16 | });
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/rules/EqualRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 |
3 | export class EqualRule extends Rule {
4 | constructor(requiredValue: TValue) {
5 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
6 | super((value: TValue) => (value === requiredValue ? null : `Must equal '${requiredValue}'`));
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/rules/ExclusiveBetweenRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 | import { formatNumber } from '@/numberHelpers';
3 |
4 | export class ExclusiveBetweenRule extends Rule {
5 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
6 | constructor(lowerBound: number, upperBound: number) {
7 | super((value: TValue) => {
8 | if (value == null) {
9 | return null;
10 | }
11 | if (typeof value !== 'number') {
12 | throw new TypeError('A non-number value was passed to the exclusiveBetween rule');
13 | }
14 | return value > lowerBound && value < upperBound
15 | ? null
16 | : `Value must be between ${formatNumber(lowerBound)} and ${formatNumber(upperBound)} (exclusive)`;
17 | });
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/rules/GreaterThanOrEqualToRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 | import { formatNumber } from '@/numberHelpers';
3 |
4 | export class GreaterThanOrEqualToRule extends Rule {
5 | constructor(threshold: number) {
6 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
7 | super((value: TValue) => {
8 | if (value == null) {
9 | return null;
10 | }
11 | if (typeof value !== 'number') {
12 | throw new TypeError('A non-number value was passed to the greaterThanOrEqualTo rule');
13 | }
14 | return value >= threshold
15 | ? null
16 | : `Value must be greater than or equal to ${formatNumber(threshold)}`;
17 | });
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/rules/GreaterThanRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 | import { formatNumber } from '@/numberHelpers';
3 |
4 | export class GreaterThanRule extends Rule {
5 | constructor(threshold: number) {
6 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
7 | super((value: TValue) => {
8 | if (value == null) {
9 | return null;
10 | }
11 | if (typeof value !== 'number') {
12 | throw new TypeError('A non-number value was passed to the greaterThan rule');
13 | }
14 | return value > threshold ? null : `Value must be greater than ${formatNumber(threshold)}`;
15 | });
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/rules/InclusiveBetweenRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 | import { formatNumber } from '@/numberHelpers';
3 |
4 | export class InclusiveBetweenRule extends Rule {
5 | constructor(lowerBound: number, upperBound: number) {
6 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
7 | super((value: TValue) => {
8 | if (value == null) {
9 | return null;
10 | }
11 | if (typeof value !== 'number') {
12 | throw new TypeError('A non-number value was passed to the inclusiveBetween rule');
13 | }
14 | return value >= lowerBound && value <= upperBound
15 | ? null
16 | : `Value must be between ${formatNumber(lowerBound)} and ${formatNumber(upperBound)} (inclusive)`;
17 | });
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/rules/LengthRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 | import { formatNumber } from '@/numberHelpers';
3 |
4 | export class LengthRule extends Rule {
5 | constructor(minLength: number, maxLength: number) {
6 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
7 | super((value: TValue) => {
8 | if (value == null) {
9 | return null;
10 | }
11 | if (typeof value !== 'string') {
12 | throw new TypeError('A non-string value was passed to the length rule');
13 | }
14 | return value.length >= minLength && value.length <= maxLength
15 | ? null
16 | : `Value must be between ${formatNumber(minLength)} and ${formatNumber(maxLength)} characters long`;
17 | });
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/rules/LessThanOrEqualToRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 | import { formatNumber } from '@/numberHelpers';
3 |
4 | export class LessThanOrEqualToRule extends Rule {
5 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
6 | constructor(threshold: number) {
7 | super((value: TValue) => {
8 | if (value == null) {
9 | return null;
10 | }
11 | if (typeof value !== 'number') {
12 | throw new TypeError('A non-number value was passed to the lessThanOrEqualTo rule');
13 | }
14 | return value <= threshold
15 | ? null
16 | : `Value must be less than or equal to ${formatNumber(threshold)}`;
17 | });
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/rules/LessThanRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 | import { formatNumber } from '@/numberHelpers';
3 |
4 | export class LessThanRule extends Rule {
5 | constructor(threshold: number) {
6 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
7 | super((value: TValue) => {
8 | if (value == null) {
9 | return null;
10 | }
11 | if (typeof value !== 'number') {
12 | throw new TypeError('A non-number value was passed to the lessThan rule');
13 | }
14 | return value < threshold ? null : `Value must be less than ${formatNumber(threshold)}`;
15 | });
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/rules/MatchesRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 |
3 | export class MatchesRule extends Rule {
4 | constructor(pattern: RegExp) {
5 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
6 | super((value: TValue) => {
7 | if (value == null) {
8 | return null;
9 | }
10 | if (typeof value !== 'string') {
11 | throw new TypeError('A non-string value was passed to the matches rule');
12 | }
13 | return pattern.test(value) ? null : 'Value does not match the required pattern';
14 | });
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/rules/MaxLengthRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 | import { formatNumber } from '@/numberHelpers';
3 |
4 | export class MaxLengthRule extends Rule {
5 | constructor(maxLength: number) {
6 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
7 | super((value: TValue) => {
8 | if (value == null) {
9 | return null;
10 | }
11 | if (typeof value !== 'string') {
12 | throw new TypeError('A non-string value was passed to the maxLength rule');
13 | }
14 | return value.length <= maxLength
15 | ? null
16 | : `Value must be no more than ${formatNumber(maxLength)} characters long`;
17 | });
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/rules/MinLengthRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 | import { formatNumber } from '@/numberHelpers';
3 |
4 | export class MinLengthRule extends Rule {
5 | constructor(minLength: number) {
6 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
7 | super((value: TValue) => {
8 | if (value == null) {
9 | return null;
10 | }
11 | if (typeof value !== 'string') {
12 | throw new TypeError('A non-string value was passed to the minLength rule');
13 | }
14 | return value.length >= minLength
15 | ? null
16 | : `Value must be at least ${formatNumber(minLength)} characters long`;
17 | });
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/rules/MustAsyncRule.ts:
--------------------------------------------------------------------------------
1 | import { AsyncRule } from './AsyncRule';
2 | import { AsyncPredicate } from '@/types/Predicate';
3 |
4 | export class MustAsyncRule extends AsyncRule {
5 | constructor(definition: AsyncPredicate) {
6 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
7 | super(async (value: TValue, model: TModel) => {
8 | if (Array.isArray(definition)) {
9 | for (const eachDefinition of definition) {
10 | if (typeof eachDefinition === 'function') {
11 | const isValid = await eachDefinition(value, model);
12 | if (!isValid) {
13 | return 'Value is not valid';
14 | }
15 | } else {
16 | const isValid = await eachDefinition.predicate(value, model);
17 | if (!isValid) {
18 | return typeof eachDefinition.message === 'function'
19 | ? eachDefinition.message(value, model)
20 | : eachDefinition.message;
21 | }
22 | }
23 | }
24 | return null;
25 | }
26 |
27 | if (typeof definition === 'function') {
28 | return (await definition(value, model)) ? null : 'Value is not valid';
29 | }
30 |
31 | const { predicate, message } = definition;
32 |
33 | return (await predicate(value, model))
34 | ? null
35 | : typeof message === 'function'
36 | ? message(value, model)
37 | : message;
38 | });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/rules/MustRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 | import { Predicate } from '@/types/Predicate';
3 |
4 | export class MustRule extends Rule {
5 | constructor(definition: Predicate) {
6 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
7 | super((value: TValue, model: TModel) => {
8 | if (Array.isArray(definition)) {
9 | for (const eachDefinition of definition) {
10 | if (typeof eachDefinition === 'function') {
11 | const isValid = eachDefinition(value, model);
12 | if (!isValid) {
13 | return 'Value is not valid';
14 | }
15 | } else {
16 | const isValid = eachDefinition.predicate(value, model);
17 | if (!isValid) {
18 | return typeof eachDefinition.message === 'function'
19 | ? eachDefinition.message(value, model)
20 | : eachDefinition.message;
21 | }
22 | }
23 | }
24 | return null;
25 | }
26 |
27 | if (typeof definition === 'function') {
28 | return definition(value, model) ? null : 'Value is not valid';
29 | }
30 |
31 | const { predicate, message } = definition;
32 |
33 | return predicate(value, model)
34 | ? null
35 | : typeof message === 'function'
36 | ? message(value, model)
37 | : message;
38 | });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/rules/NotEmptyRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 |
3 | export class NotEmptyRule extends Rule {
4 | constructor() {
5 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
6 | super((value: TValue) => {
7 | if (typeof value !== 'string') {
8 | if (value == null) {
9 | return null;
10 | }
11 | throw new TypeError('A non-string value was passed to the notEmpty rule');
12 | }
13 | return value.trim().length > 0 ? null : 'Value cannot be empty';
14 | });
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/rules/NotEqualRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 |
3 | export class NotEqualRule extends Rule {
4 | constructor(forbiddenValue: TValue) {
5 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
6 | super((value: TValue) =>
7 | value !== forbiddenValue ? null : `Must not equal '${forbiddenValue}'`,
8 | );
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/rules/NotNullRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 |
3 | export type NotNullRuleOptions = {
4 | includeUndefined?: boolean;
5 | };
6 |
7 | export class NotNullRule extends Rule {
8 | constructor({ includeUndefined }: NotNullRuleOptions) {
9 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
10 | super((value: TValue) => {
11 | if (includeUndefined) {
12 | return value == null ? 'Value cannot be null' : null;
13 | }
14 |
15 | return value === null ? 'Value cannot be null' : null;
16 | });
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/rules/NotUndefinedRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 |
3 | export class NotUndefinedRule extends Rule {
4 | constructor() {
5 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
6 | super((value: TValue) => (value === undefined ? 'Value cannot be undefined' : null));
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/rules/NullRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 |
3 | export type NullRuleOptions = {
4 | includeUndefined: boolean;
5 | };
6 |
7 | export class NullRule extends Rule {
8 | constructor({ includeUndefined }: NullRuleOptions) {
9 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
10 | super((value: TValue) => {
11 | if (includeUndefined) {
12 | return value == null ? null : 'Value must be null';
13 | }
14 |
15 | return value === null ? null : 'Value must be null';
16 | });
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/rules/PrecisionScaleRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 |
3 | export class PrecisionScaleRule extends Rule {
4 | constructor(precision: number, scale: number) {
5 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
6 | super((value: TValue) => {
7 | if (value == null || value === 0) {
8 | return null;
9 | }
10 | if (typeof value !== 'number') {
11 | throw new TypeError('A non-number value was passed to the precisionScale rule');
12 | }
13 |
14 | const regex = precisionScaleRegex({ precision, scale });
15 |
16 | if (!regex.test(value.toString())) {
17 | return `Value must not be more than ${precision} digits in total, with allowance for ${scale} decimals`;
18 | }
19 |
20 | return null;
21 | });
22 | }
23 | }
24 |
25 | const precisionScaleRegex = ({ precision, scale }: { precision: number; scale: number }) => {
26 | const integerDigits = precision - scale;
27 |
28 | return integerDigits === 0
29 | ? new RegExp(`^-?0?\\.\\d{0,${scale}}$`) // The leading 0 to the left of the decimal point does not count towards precision
30 | : new RegExp(`^-?\\d{1,${integerDigits}}(\\.\\d{1,${scale}})?$`);
31 | };
32 |
--------------------------------------------------------------------------------
/src/rules/Rule.ts:
--------------------------------------------------------------------------------
1 | import { CoreRule } from './CoreRule';
2 | import { ValueValidationResult } from '@/ValueValidationResult';
3 | import { ValueValidator } from '@/ValueValidator';
4 |
5 | export class Rule extends CoreRule {
6 | private readonly valueValidator: ValueValidator;
7 |
8 | constructor(valueValidator: ValueValidator) {
9 | super();
10 | this.valueValidator = valueValidator;
11 | }
12 |
13 | public validate = (value: TValue, model: TModel): ValueValidationResult => {
14 | if (this.whenCondition != null && !this.whenCondition(model)) {
15 | return null;
16 | }
17 |
18 | if (this.unlessCondition != null && this.unlessCondition(model)) {
19 | return null;
20 | }
21 |
22 | const errorOrNull = this.valueValidator(value, model);
23 | return errorOrNull != null ? this.customErrorMessage || errorOrNull : null;
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/src/rules/UndefinedRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 |
3 | export class UndefinedRule extends Rule {
4 | constructor() {
5 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
6 | super((value: TValue) => (value === undefined ? null : 'Value must be undefined'));
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/rules/ValidatorRule.ts:
--------------------------------------------------------------------------------
1 | import { Rule } from './Rule';
2 | import { IValidator } from '@/IValidator';
3 | import { ValueValidationResult } from '@/ValueValidationResult';
4 |
5 | export class ValidatorRule extends Rule {
6 | constructor(validatorProducer: (model: TModel) => IValidator) {
7 | // istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
8 | super((value: TValue, model: TModel) =>
9 | value == null
10 | ? null
11 | : (validatorProducer(model).validate(value) as ValueValidationResult),
12 | );
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/types/AppliesTo.ts:
--------------------------------------------------------------------------------
1 | export type AppliesTo = 'AppliesToAllValidators' | 'AppliesToCurrentValidator';
2 |
--------------------------------------------------------------------------------
/src/types/ArrayType.ts:
--------------------------------------------------------------------------------
1 | export type ArrayType =
2 | | Array
3 | | ReadonlyArray
4 | | Readonly>;
5 |
--------------------------------------------------------------------------------
/src/types/Constrain.ts:
--------------------------------------------------------------------------------
1 | // Credit: https://github.com/microsoft/TypeScript/issues/9252#issuecomment-472881853
2 | // Slightly hacky workaround for the fact that a generic type parameter
3 | // does not limit values of T to having **only** the properties of U, but rather requires
4 | // that they have **at least** the properties specified in U. This is currently used for
5 | // getting the right typing on the `.ruleForTransformed` methods, so that an object property
6 | // can't be mapped to an object property of a different type (in particular, one that has
7 | // more properties, since these won't be present in the `ValidationErrors` object).
8 | /**
9 | * Constrain
10 | * @desc Constrains type `T` to only those properties that exist in type `U`
11 | */
12 | export type Constrain = {
13 | [key in keyof T]: key extends keyof U ? T[key] : never;
14 | };
15 |
--------------------------------------------------------------------------------
/src/types/FlatType.ts:
--------------------------------------------------------------------------------
1 | export type FlatType = string | number | boolean | symbol;
2 |
--------------------------------------------------------------------------------
/src/types/IfNotNeverThen.ts:
--------------------------------------------------------------------------------
1 | export type IfNotNeverThen = TValue extends never ? never : TOut;
2 |
--------------------------------------------------------------------------------
/src/types/IfType.ts:
--------------------------------------------------------------------------------
1 | export type IfString = TValue extends string ? TOut : never;
2 | export type IfNumber = TValue extends number ? TOut : never;
3 | export type IfObject = TValue extends object ? TOut : never;
4 |
--------------------------------------------------------------------------------
/src/types/Message.ts:
--------------------------------------------------------------------------------
1 | type MessageGenerator = (
2 | value: TTransformedValue,
3 | model: TModel,
4 | ) => string;
5 |
6 | export type Message =
7 | | string
8 | | MessageGenerator;
9 |
--------------------------------------------------------------------------------
/src/types/Optional.ts:
--------------------------------------------------------------------------------
1 | export type Optional = T | null | undefined;
2 |
--------------------------------------------------------------------------------
/src/types/Predicate.ts:
--------------------------------------------------------------------------------
1 | import { Message } from '@/types/Message';
2 |
3 | export type SimplePredicate = (
4 | value: TTransformedValue,
5 | model: TModel,
6 | ) => boolean;
7 |
8 | export type SimpleAsyncPredicate = (
9 | value: TTransformedValue,
10 | model: TModel,
11 | ) => Promise;
12 |
13 | export type SimplePredicateWithMessage = {
14 | predicate: SimplePredicate;
15 | message: Message;
16 | };
17 |
18 | export type SimpleAsyncPredicateWithMessage = {
19 | predicate: SimpleAsyncPredicate;
20 | message: Message;
21 | };
22 |
23 | export type Predicate =
24 | | SimplePredicate
25 | | SimplePredicateWithMessage
26 | | Array<
27 | | SimplePredicate
28 | | SimplePredicateWithMessage
29 | >;
30 |
31 | export type AsyncPredicate =
32 | | SimpleAsyncPredicate
33 | | SimpleAsyncPredicateWithMessage
34 | | Array<
35 | | SimpleAsyncPredicate
36 | | SimpleAsyncPredicateWithMessage
37 | >;
38 |
--------------------------------------------------------------------------------
/src/types/TransformedValue.ts:
--------------------------------------------------------------------------------
1 | import { FlatType } from '@/types/FlatType';
2 |
3 | // We restrict the type to flat types, otherwise it would be possible to map a flat type
4 | // to a complex type and force an object/array property in the validation errors, when only
5 | // a flat error (`string | null`) is expected. `TValue` is also obviously accepted, since
6 | // the errors object will have the same shape in that case.
7 | export type TransformedValue = TValue | FlatType | null | undefined;
8 |
9 | // TODO: Should we allow Partial here given that a subset of the
10 | // properties being in the errors object is a valid use case?
11 |
--------------------------------------------------------------------------------
/src/valueValidator/ArrayValueValidatorBuilder.ts:
--------------------------------------------------------------------------------
1 | import { ValueTransformer } from './ValueTransformer';
2 | import { hasError } from './ValueValidator';
3 | import { ValueValidatorBuilder } from './ValueValidatorBuilder';
4 | import { ValueValidator } from '@/ValueValidator';
5 | import { ValueValidationResult } from '@/ValueValidationResult';
6 | import { ArrayType } from '@/types/ArrayType';
7 |
8 | export class ArrayValueValidatorBuilder<
9 | TModel,
10 | TValue extends ArrayType,
11 | TEachValue,
12 | TEachTransformedValue,
13 | > {
14 | private eachValueValidatorBuilder: ValueValidatorBuilder<
15 | TModel,
16 | TEachValue,
17 | TEachTransformedValue
18 | >;
19 |
20 | constructor(
21 | rebuildValidate: () => void,
22 | transformValue: ValueTransformer,
23 | ) {
24 | this.eachValueValidatorBuilder = new ValueValidatorBuilder<
25 | TModel,
26 | TEachValue,
27 | TEachTransformedValue
28 | >(rebuildValidate, transformValue);
29 | }
30 |
31 | public build = (): ValueValidator => {
32 | return (value: TValue, model: TModel) => {
33 | if (value == null) {
34 | return null;
35 | }
36 |
37 | const valueValidator = this.eachValueValidatorBuilder.build();
38 |
39 | const errors = value.map((element) => {
40 | const errorOrNull = valueValidator(element, model);
41 | return hasError(errorOrNull) ? errorOrNull : null;
42 | }) as ValueValidationResult;
43 |
44 | return hasError(errors) ? errors : null;
45 | };
46 | };
47 |
48 | public getAllRules = () => this.eachValueValidatorBuilder.getAllRules();
49 | }
50 |
--------------------------------------------------------------------------------
/src/valueValidator/AsyncArrayValueValidatorBuilder.ts:
--------------------------------------------------------------------------------
1 | import { AsyncValueValidator } from './AsyncValueValidator';
2 | import { AsyncValueValidatorBuilder } from './AsyncValueValidatorBuilder';
3 | import { ValueTransformer } from './ValueTransformer';
4 | import { ValueValidationResult } from '@/ValueValidationResult';
5 | import { hasError } from '@/valueValidator/ValueValidator';
6 | import { ArrayType } from '@/types/ArrayType';
7 |
8 | export class AsyncArrayValueValidatorBuilder<
9 | TModel,
10 | TValue extends ArrayType