diff --git a/README-updated.md b/README-updated.md new file mode 100644 index 00000000..3c3468bb --- /dev/null +++ b/README-updated.md @@ -0,0 +1,569 @@ +# Form Builder Validators + +Form Builder Validators offers a set of validators for any `FormField` widget or widgets that extend the `FormField` class - _e.g._, `TextFormField`, `DropdownFormField`, _et cetera_. It provides standard ready-made validation rules and a way to compose new validation rules combining multiple rules, including custom ones. + +Also it includes the `l10n` / `i18n` of error text messages to multiple languages. + +[![Pub Version](https://img.shields.io/pub/v/form_builder_validators?logo=flutter&style=for-the-badge)](https://pub.dev/packages/form_builder_validators) +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/flutter-form-builder-ecosystem/form_builder_validators/base.yaml?branch=main&logo=github&style=for-the-badge)](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/actions/workflows/base.yaml) +[![Codecov](https://img.shields.io/codecov/c/github/flutter-form-builder-ecosystem/form_builder_validators?logo=codecov&style=for-the-badge)](https://codecov.io/gh/flutter-form-builder-ecosystem/form_builder_validators/) +[![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/flutter-form-builder-ecosystem/form_builder_validators?logo=codefactor&style=for-the-badge)](https://www.codefactor.io/repository/github/flutter-form-builder-ecosystem/form_builder_validators) + +--- + +## Call for Maintainers + +> We are looking for maintainers to contribute to the development and maintenance of Flutter Form Builder Ecosystem. Is very important to keep the project alive and growing, so we need your help to keep it up to date and with new features. You can contribute in many ways, we describe some of them in [Support](#support) section. + +## Contents + +- [Features](#features) +- [Validators](#validators) + - [Collection validators](#collection-validators) + - [Core validators](#core-validators) + - [Composition validators](#composition-validators) + - [Conditional validators](#conditional-validators) + - [Debug print validator](#debug-print-validator) + - [Equality validators](#equality-validators) + - [Required validators](#required-validators) + - [Transform validators](#transform-validators) + - [Datetime validators](#datetime-validators) + - [File validators](#file-validators) + - [Finance validators](#finance-validators) + - [Generic type validators](#generic-type-validators) + - [Miscellaneous validators](#miscellaneous-validators) + - [Network validators](#network-validators) + - [Numeric validators](#numeric-validators) + - [Path validators](#path-validators) + - [String validators](#string-validators) + - [Type validators](#type-validators) + - [User information validators](#user-information-validators) +- [Supported languages](#supported-languages) +- [Use](#use) + - [Setup](#setup) + - [Basic use](#basic-use) + - [Specific uses](#specific-uses) + - [Composing multiple validators](#composing-multiple-validators) + - [Modify the default error message in a specific language](#modify-the-default-error-message-in-a-specific-language) +- [Migrations](#migrations) + - [v7 to v8](#v7-to-v8) + - [v10 to 11](#v10-to-v11) +- [How this package works](#how-this-package-works) +- [Support](#support) + - [Contribute](#contribute) + - [Add new supported language](#add-new-supported-language) + - [Add new validator](#add-new-validator) + - [Questions and answers](#questions-and-answers) + - [Donations](#donations) +- [Roadmap](#roadmap) +- [Ecosystem](#ecosystem) +- [Thanks to](#thanks-to) + +## Features + +- Ready-made validation rules +- Compose multiple reusable validation rules +- Default error messages in multiple languages + +## Validators + +This package comes with several most common `FormFieldValidator`s such as required, numeric, email, +URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit card, etc., with default `errorText` messages. + +Generally, the validators are separated in three main groups: +1. Field Requirement Validators: makes a field optional or required. +2. Type Validators: checks the type of the field. +3. Other validators: make any other kind of check that is not related to null/emptiness or type validation. + +Generally, we build a validator composing those three types in the following way: +`((()))` + +For example: +- Make the field required, check if it is of type `num` or a `String` parsable to num and then check if +it is greater than 10. +`Validators.required(Validators.num(Validators.greaterThan(10)))` + +As easy as that! This validator is meant to be used with form fields like in the following example: +```dart +// Example from the folder `examples` +TextFormField( + decoration: const InputDecoration( + labelText: 'Min Value Field', + prefixIcon: Icon(Icons.exposure_neg_1), + ), + keyboardType: TextInputType.number, + validator: Validators.required(Validators.num(Validators.greaterThan(10))), + textInputAction: TextInputAction.next, + autovalidateMode: AutovalidateMode.always, +); +``` + + +### Collection validators + +- `Validators.equalLength(expectedLength)`: Checks if the field contains a collection (must be a `String`, `Iterable`, or `Map`) with length equals `expectedLength`. +- `Validators.minLength(min)`: Checks if the field contains a collection (must be a `String`, `Iterable`, or `Map`) with length greater than or equal to `min`. +- `Validators.maxLength(max)`: Checks if the field contains a collection (must be a `String`, `Iterable`, or `Map`) with length less than or equal to `max`. +- `Validators.betweenLength(min, max)`: Checks if the field contains a collection (must be a `String`, `Iterable`, or `Map`) with length between `min` and `max`, inclusive. + +### Core validators + +#### Composition validators + +- `Validators.and(validators)`: Validates the field by requiring it to pass all validators in the `validators` list. +- `Validators.or(validators)`: Validates the field by requiring it to pass at least one of the validators in the `validators` list. + +#### Conditional validators + +- `Validators.validateIf(condition, v)`: Validates the field with validator `v` only if `condition` is `true`. +- `Validators.skipIf(condition, v)`: Validates the field with validator `v` only if `condition` is `false`. + +#### Debug print validator +- `Validators.debugPrintValidator()`: Print, for debug purposes, the user input value. + +#### Equality validators + +- `Validators.equal(value)`: Checks if the field contains an input that is equal to `value` (==). +- `Validators.notEqual(value)`: Checks if the field contains an input that is not equal to `value` (!=). + +#### Field Requirement Validators + +- `Validators.required(next)`: Makes the field required by checking if it contains a non-null and non-empty value, passing it to the `next` validator as a not-nullable type. +- `Validators.optional(next)`: Makes the field optional by passing it to the `next` validator if it contains a non-null and non-empty value. If the field is null or empty, null is returned. +- `Validators.validateWithDefault(defaultValue, next)`: Validates the field with `next` validator. If the input is null, it uses the `defaultValue` instead. + +#### Transform validators + +- `Validators.transformAndValidate(transformFunction, next:next)`: Transforms an input from `IN` type to `OUT` type through the function `transformFunction` and pass it to the `next` validator. + +### Datetime validators + +- `Validators.after(reference)`: Checks if the field contains a `DateTime` that is after `reference`. +- `Validators.before(reference)`: Checks if the field contains a `DateTime` that is before `reference`. +- `Validators.betweenDateTime(minReference, maxReference)`: Checks if the field contains a `DateTime` that is after `minReference` and before `maxReference`. + +### File validators + +- `Validators.maxFileSize(max, base:base)`: Checks if the field contains a file size that is less than the `max` size with `base` 1000 or 1024. +- TODO [ ] `Validators.minFileSize(min, base:base)`: Checks if the field contains a file size that is less than the `max` size with `base` 1000 or 1024. + +### Finance validators + +- `Validators.bic()`: Checks if the field contains a valid BIC (Bank Identifier Code). +- `Validators.iban()` - Checks if the field contains a valid IBAN (International Bank Account Number). + +### Generic Type Validators +Validators that check a generic type user input. + +- `Validators.inList(values)`: Checks if the field contains a value that is in the list `values`. +- `Validators.notInList(values)`: Checks if the field DOES NOT contain a value that is in the list `values`. +- `Validators.isTrue()`: Checks if the field contains a boolean or a parsable `String` of the `true` value. +- `Validators.isFalse()`: Checks if the field contains a boolean or a parsable `String` of the `false` value. + +### Miscellaneous validators + +- TODO [ ] `FormBuilderValidators.colorCode()` - requires the field's value to be a valid color code. +- TODO [ ] `FormBuilderValidators.isbn()` - requires the field's to be a valid ISBN. + +### Network validators + +- `Validators.ip()`: Checks if the field contains a properly formatted `Internet Protocol` (IP) address. It may check for either `IPv4`, or `IPv6` or even for both. +- `Validators.url()`: Checks if the field contains a properly formatted `Uniform Resource Locators` (URL). +- `FormBuilderValidators.email()`: Checks if the field contains a valid email. +- TODO [ ] `FormBuilderValidators.macAddress()` - requires the field's to be a valid MAC address. + +### Numeric validators + +- `Validators.between(min, max)`: Checks if the field contains a number that is in the inclusive range [min, max]. +- `Validators.greaterThan(reference)`: Checks if the field contains a number that is greater than `reference`. +- `Validators.greaterThanOrEqualTo(reference)`: Checks if the field contains a number that is greater than or equal to `reference`. +- `Validators.lessThan(reference)`: Checks if the field contains a number that is less than `reference`. +- `Validators.lessThanOrEqualTo(reference)`: Checks if the field contains a number that is less than or equal to `reference`. + +### Path Validators +- `Validators.matchesAllowedExtensions(extensions)`: Checks if the field contains a `String` that is in the list `extensions`. + +### String validators + +TODO continue from here... +- `Validators.contains(substring)` - Checks if the field contains the `substring`. +- TODO [ ] `Validators.notContains(substring)` - Checks if the field does not contain the `substring`. +- TODO [ ] `FormBuilderValidators.endsWith()` - requires the substring to be the end of the field's value. +- TODO [ ] `FormBuilderValidators.notEndsWith()` - requires the substring not to be the end of the field's value. +- TODO [ ] `FormBuilderValidators.startsWith()` - requires the substring to be the start of the field's value. +- TODO [ ] `FormBuilderValidators.notStartsWith()` - requires the substring not to be the start of the field's value. +- TODO [ ] `FormBuilderValidators.lowercase()` - requires the field's value to be lowercase. +- TODO [ ] `FormBuilderValidators.uppercase()` - requires the field's value to be uppercase. +- `Validators.hasMinUppercaseChars(min:min)` - Checks if the field has a minimum number of uppercase chars. +- TODO [ ] `Validators.hasMaxUppercaseChars(max:max)` - Checks if the field has a maximum number of uppercase chars. +- `Validators.hasMinLowercaseChars(min:min)` - Checks if the field has a minimum number of lowercase chars. +- TODO [ ] `Validators.hasMaxLowercaseChars(max:max)` - Checks if the field has a maximum number of lowercase chars. +- `Validators.hasMinNumericChars(min:min)` - Checks if the field has a minimum number of numeric chars. +- TODO [ ] `Validators.hasMaxNumericChars(max:max)` - Checks if the field has a maximum number of numeric chars. +- `Validators.hasMinSpecialChars(min:min)` - Checks if the field has a minimum number of special chars. +- TODO [ ] `Validators.hasMaxSpecialChars(max:max)` - Checks if the field has a maximum number of special chars. +- `Validators.match(regExp)` - Checks if the field matches with the regular expression `regExp`. +- TODO [ ] `Validators.notMatch(regExp)` - Checks if the field does not match with the regular expression `regExp`. +- `Validators.uuid()` - Checks if the field is a valid Universally Unique Identifier (UUID). +- TODO [ ] `FormBuilderValidators.maxWordsCount()` - requires the word count of the field's value to be less than or equal to the provided maximum count. +- TODO [ ] `FormBuilderValidators.minWordsCount()` - requires the word count of the field's value to be greater than or equal to the provided minimum count. + +### Type Validators +- `Validators.string(next)`: Checks if the field contains a valid `String` and passes the input as `String` to the `next` validator. +- `Validators.int(next)`: Checks if the field contains a valid `int` or parsable `String` to `int` and passes the input as `int` to the `next` validator. +- `Validators.double(next)`: Checks if the field contains a valid `double` or parsable `String` to `double` and passes the input as `double` to the `next` validator. +- `Validators.num(next)`: Checks if the field contains a valid `num` or parsable `String` to `num` and passes the input as `num` to the `next` validator. +- `Validators.bool(next)`: Checks if the field contains a valid `bool` or parsable `String` to `bool` and passes the input as `bool` to the `next` validator. +- `Validators.dateTime(next)`: Checks if the field contains a valid `DateTime` or parsable `String` to `DateTime` and passes the input as `DateTime` to the `next` validator. + +### User Information validators + +- `Validators.email()`: Checks if the field contains a valid email. +- `Validators.password()`: Checks if the field contains a valid password. A password may require some +conditions to be met in order to be considered as valid. +- `Validators.phoneNumber()`: Checks if the field contains a valid phone number. + +## Supported languages + +Validators support default `errorText` messages in these languages: + +- Albanian (al) +- Arabic (ar) +- Bangla (bn) +- Bosnian (bs) +- Bulgarian (bg) +- Catalan (ca) +- Chinese Simplified (zh_Hans) +- Chinese Traditional (zh_Hant) +- Croatian (hr) +- Czech (cs) +- Danish (da) +- Dutch (nl) +- English (en) +- Estonian (et) +- Finnish (fi) +- Farsi/Persian (fa) +- French (fr) +- German (de) +- Greek (el) +- Hebrew (he) +- Hungarian (hu) +- Hindi (hi) +- Indonesian (id) +- Italian (it) +- Japanese (ja) +- Kurdish (ku) +- Korean (ko) +- Khmer (km) +- Lao (lo) +- Latvian (lv) +- Malay (ms) +- Mongolian (mn) +- Norwegian (no) +- Polish (pl) +- Portuguese (pt) +- Romanian (ro) +- Russian (ru) +- Slovak (sk) +- Slovenian (sl) +- Spanish (es) +- Swahili (sw) +- Swedish (se) +- Tamil(ta) +- Thai (th) +- Turkish (tr) +- Ukrainian (uk) +- Vietnamese (vi) + +And you can still add your custom error messages. + +## Use + +### Setup + +The default error message is in English. To allow for localization of default error messages within your app, add `FormBuilderLocalizations.delegate` in the list of your app's `localizationsDelegates`. + +For example, in your `MaterialApp`: +```Dart +return MaterialApp( + supportedLocales: [ + Locale('de'), + Locale('en'), + Locale('es'), + Locale('fr'), + Locale('it'), + ... + ], + localizationsDelegates: [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + FormBuilderLocalizations.delegate, // here + ], +``` + +### Basic use + +In the following example, we have a required field for the `Name` of the user: +```dart +TextFormField( + decoration: InputDecoration(labelText: 'Name'), + autovalidateMode: AutovalidateMode.always, + validator: Validators.required(), +), +``` + +See [pub.dev example tab](https://pub.dev/packages/form_builder_validators/example) or [github code](example/lib/main.dart) for more details + +### Specific uses + +#### Composing multiple validators with the logical `AND` semantics + +The `Validators` class comes with a handy static function named `and()`, which takes a list of `Validator` functions. Composing allows you to create once and reuse validation rules across multiple fields, widgets, or apps. + +On validation, each validator is run, and if any validator returns a non-null value (i.e., a String), validation fails, and the `errorText` for the field is set as the first returned string or as a composition of all failures. + +In the following example, we have a form field for the user's `Age`, which is **required**, must be **numeric** (with custom error message if not numeric given by *"La edad debe ser numérica."*), must be **less than 70**, and, finally, must be **non-negative** (with custom non-negative validator): +```Dart +TextFormField( + decoration: InputDecoration(labelText: 'Age'), + keyboardType: TextInputType.number, + autovalidateMode: AutovalidateMode.always, + validator: Validators.required( + Validators.and(>[ + Validators.num(Validators.lessThan(70), (_) => 'La edad debe ser numérica.'), + + /// Include your own custom `Validator` function, if you want. + /// Ensures positive values only. We could also have used `Validators.greaterThanOrEqualTo(0)` instead. + (String? val) { + if (val != null) { + final int? number = int.tryParse(val); + // todo bug here: if it is not int, it accepts negative + // numbers + if (number == null) return null; + if (number < 0) return 'We cannot have a negative age'; + } + return null; + } + ])) +), +``` + +#### Modify the default error message in a specific language + +see [override_form_builder_localizations_en](example/lib/override_form_builder_localizations_en.dart) for more detail. + +## Migrations +### v11 to v12 +- Deprecate `FormBuilderValidators` class with its static methods as validators. +- Instead, you should use `Validators` class. +- Check the file [migrations](./doc/migration.md) for detailed instructions. + +### v10 to v11 + +- All validators now first check for null or empty value and return an error if so. You can set `checkNullOrEmpty` to `false` if you want to avoid this behavior. +- `dateString()` changed to `date()` for constancy in api naming. Simply change the name to fix the code. +- The positional parameter for the validator [`match()`](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/93d6fb230c706a6415a3a85973fc37fabbd82588/lib/src/form_builder_validators.dart#L1433) is not a `String` pattern anymore, but a `RegExp` regex. + +### v7 to v8 + +Remove `context` as a parameter to validator functions. For example, `FormBuilderValidators.required(context)` becomes `FormBuilderValidators.required()` without `context` passed in. + +## How this package works +This package comes with several most common `Validator`s and `FormFieldValidator`s such as required, numeric, mail, +URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit card, etc., with default `errorText` messages. + +- But what is a `FormFieldValidator`? + It is a function that takes user input of any nullable type and returns either null (for valid input) or + an error message string (for invalid input). The input parameter can be `String?`, `int?`, `num?`, or any + other nullable type that represents user-provided data. The input must be nullable since form fields may + be empty, resulting in null values. + +For example, here's a `FormFieldValidator` that checks if a number is even: +```dart +String? isEven(int? userInput) { + return (userInput != null && userInput % 2 == 0) ? null : 'This field must be even'; +} +``` +The challenge with the previous approach is that we must handle null checks in every validator +implementation. This leads to: + 1. Tangled validator logic: Each validator must handle both validation rules and null checking,making the code harder to understand and maintain. + 2. Code duplication: When composing validators, the same null-checking logic must be repeated across multiple validators, violating the DRY principle. + 3. Poor separation of concerns: A validator named `isEven` should focus solely on checking if a number is even. It shouldn't need to handle null cases or type validation - those are separate responsibilities that deserve their own focused validators. + 4. Verbose implementations: The combination of null checks, type validation, and business logic in each validator results in unnecessarily lengthy code that's harder to test and maintain. + 5. Potential problems with null safety: imagine an unsafe version of isEven that simply uses the `!` operator to throw an error during runtime if the user input is null: +```dart +/// `userInput` may not be null. +String? isEven(int? userInput) { + return (userInput! % 2 == 0) ? null : 'This field must be even'; +} +``` + +This package introduces a more precise approach that separates null-value handling from the actual +validation logic. Instead of the previous isEven implementation, we can compose two focused validators: + +```dart +String? Function(int) isEven(){ + return (int userInput){ + return (userInput % 2 == 0) ? null:'This field must be even'; + }; +} + +String? Function(int?) required(String? Function(int) next){ + return (int? userInput) { + return (userInput != null) ? next(userInput):'This field is required'; + }; +} + +// Important: isEven() does not return a FormFieldValidator, but the composition required(isEven()), does. +final validator = required(isEven()); +``` + +By introducing this level of indirection, we achieve: + 1. Clean separation between null checks and validation logic + 2. More composable validators + 3. Specific error messages for missing vs invalid input + 4. Type-safe validator chains + +## Support + +### Contribute + +You have some ways to contribute to this package. + +- Beginner: Reporting bugs or requesting new features +- Intermediate: Answer questions, implement new features (from issues or not), and create pull requests +- Advanced: Join [organization](#ecosystem) like a member and help to code, manage issues, discuss new features, and other things + +See the [contribution file](https://github.com/flutter-form-builder-ecosystem/.github/blob/main/CONTRIBUTING.md) for more details + +#### Add new supported language + +We welcome efforts to internationalize/localize the package by translating the default validation `errorText` strings for built-in validation rules. + +1. Add ARB files + + Create one ARB file inside the `lib/l10n` folder for each locale you need to add support. Name the files in the following way: `intl_.arb`. For example: `intl_fr.arb` or `intl_fr_FR.arb`. + +2. Translate the error messages + + Copy and paste the contents of `intl_en.arb` into your newly created ARB file. Then translate the error messages by overwriting the default messages. + +3. Generate localization code + + To generate boilerplate code for localization, run the generate command inside the package directory where `pubspec.yaml` file is located: + + `flutter gen-l10n` + + The command will automatically create/update files inside the `lib/localization` directory, including your newly added locale support. The files in here are only necessary for local development and will not be committed to Github. + +4. Update README + + Remember to update README, adding the new language (and language code) under [Supported languages section](#supported-languages) in alphabetic order, so that everyone knows your new language is now supported! + +5. Submit PR + + Submit your PR and be of help to millions of developers all over the world! + +#### Add new validator + +1. Add a new validator to one of the folders in the `src/validators` folder. +2. Implement it as a function which returns `Validator` with `T` being the type of +the user input to be validated. +3. Add the @macro tag for documentation using the template name: `validator_`. +This will refer to the actual documentation, which will be on the `Validators` static method. +4. If your validator uses localized error message, you can use `FormBuilderLocalizations.current.` +Next we have the example of the numeric validator `greaterThan`. As we can see, it has its `@macro` docstring, it uses a +localized error message (`FormBuilderLocalizations.current.greaterThanErrorText(reference)`) and it returns +`Validator`: + ```dart + /// {@macro validator_greater_than} + Validator greaterThan(T reference, + {String Function(T input, T reference)? greaterThanMsg}) { + return (T input) { + return input > reference + ? null + : greaterThanMsg?.call(input, reference) ?? + FormBuilderLocalizations.current.greaterThanErrorText(reference); + }; + } + ``` +5. Add the validator as static method to `form_builder_validators.dart` in the `Validators` class. Do +not forget to add documentation to the new static method, using the `@template` element to give a name +to the docstring. Follow the pattern: `validator_`. +Here, an example of how to add the static method of the validator to the `Validators` class: + ```dart + final class Validators{ + //Other validators... + + /// {@template validator_greater_than} + /// Creates a validator function that checks if a numeric input exceeds `reference`. + /// + /// ## Type Parameters + /// - `T`: A numeric type that extends [num], allowing `int`, `double` or + /// `num` validations + /// + /// ## Parameters + /// - `reference` (`T`): The threshold value that the input must exceed + /// - `greaterThanMsg` (`String Function(T input, T reference)?`): Optional custom error + /// message generator that takes the input value and threshold as parameters + /// + /// ## Returns + /// Returns a [Validator] function that: + /// - Returns `null` if the input is greater than the threshold value `reference` + /// - Returns an error message string if validation fails, either from the custom + /// `greaterThanMsg` function or the default localized error text + /// + /// ## Examples + /// ```dart + /// // Basic usage with integers + /// final ageValidator = greaterThan(18); + /// + /// // Custom error message + /// final priceValidator = greaterThan( + /// 0.0, + /// greaterThanMsg: (_, ref) => 'Price must be greater than \$${ref.toStringAsFixed(2)}', + /// ); + /// ``` + /// + /// ## Caveats + /// - The validator uses strict greater than comparison (`>`) + /// {@endtemplate} + static Validator greaterThan(T reference, + {String Function(c.num input, c.num reference)? greaterThanMsg}) => + val.greaterThan(reference, greaterThanMsg: greaterThanMsg); + } + // OBS.: the core package is imported with prefix c to avoid name collision! + ``` +6. Implement tests +7. Add to [validators](#validators) with name and description +8. Add message error translated on all languages (yes, all languages). To accomplish this need: + a. Add property to all `intl_*.arb` files, in alphabetic order. + b. Translate message in all languages. + c. Run `flutter gen-l10n` command +9. Run dart `dart fix --apply` and `dart format .` +10. Submit PR + +### Questions and answers + +You can ask questions or search for answers on [Github discussion](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/discussions) or on [StackOverflow](https://stackoverflow.com/questions/tagged/flutter-form-builder) + +### Donations + +Donate or become a sponsor of Flutter Form Builder Ecosystem + +[![Become a Sponsor](https://opencollective.com/flutter-form-builder-ecosystem/tiers/sponsor.svg?avatarHeight=56)](https://opencollective.com/flutter-form-builder-ecosystem) + +## Roadmap + +- [API refactor to improve performance and behavior](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/pull/129) +- [Solve open issues](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/issues), [prioritizing bugs](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/labels/bug) + +## Ecosystem + +Take a look at [our fantastic ecosystem](https://github.com/flutter-form-builder-ecosystem) and all packages in there + +## Thanks to + +[All contributors](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/graphs/contributors) + + diff --git a/README.md b/README.md index 82a22a88..48999123 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Also included is the `l10n` / `i18n` of error text messages to multiple language --- -## Call for Maintainers +## Call for Maintainers > We are looking for maintainers to contribute to the development and maintenance of Flutter Form Builder Ecosystem. Is very important to keep the project alive and growing, so we need your help to keep it up to date and with new features. You can contribute in many ways, we describe some of them in [Support](#support) section. diff --git a/doc/migration.md b/doc/migration.md new file mode 100644 index 00000000..c1530e8b --- /dev/null +++ b/doc/migration.md @@ -0,0 +1,1051 @@ +## Migrations +### v11 to v12 +The next items of this section show how to convert from the old API functions to the closest equivalent using the new APIs. For each item, we try to show how the conversion is made from a validator with all its parameters being used, thus, if your case is simples, probably it will be enough to ignore the additional parameters in the example. +#### checkNullOrEmpty +Before specifying the equivalent to each validator, it is important to deal with the `checkNullOrEmpty` parameter. Every validator from the old API has this parameter, thus we are going to use this section to specify how to handle this situation for most of the cases and we will assume that this aspect is already handled for the following sections. + +The conditions are: +- `checkNullOrEmpty = true` +```dart +// Old API +FormBuilderValidators.someValidator(..., checkNullOrEmpty:true); + +// New API +Validators.required(Validators.someEquivalentValidator(...)); + +``` +- `checkNullOrEmpty = false` +```dart +// Old API +FormBuilderValidators.someValidator(..., checkNullOrEmpty:false); + +// New API +Validators.optional(Validators.someEquivalentValidator(...)); +``` + +#### Bool validators + +For the following group of validators (`hasLowercaseChars`, `hasNumericChars`, `hasSpecialChars`, and `hasUppercaseChars`), it is expected to receive a `String` as user input. Thus, if your form widget does not guarantee a `String` input (e.g. it may receive an `Object`), you must wrap the equivalent validator with the type validator for strings (`Validators.string`). +Apply the following logic to the next items: + +```dart +// Replace "Something" with the actual name of the validator +// Old API - No type checking needed +FormBuilderValidators.hasMinSomethingChars(...); + +// New API - With type checking when the input is not guaranteed to be a String +Validators.string(Validators.hasMinSomethingChars(...)); +``` + +- `FormBuilderValidators.hasLowercaseChars` +```dart +// Old API +FormBuilderValidators.hasLowercaseChars( + atLeast: 3, + regex: RegExp(r'[a-z]'), + errorText: 'Need at least 3 lowercase letters' +); + +// New API equivalent +Validators.hasMinLowercaseChars( + min: 3, + customLowercaseCounter: (input) => RegExp(r'[a-z]').allMatches(input).length, + hasMinLowercaseCharsMsg: (_, __) => 'Need at least 3 lowercase letters' +); +``` + +- `FormBuilderValidators.hasNumericChars` +```dart +// Old API +FormBuilderValidators.hasNumericChars( + atLeast: 2, + regex: RegExp(r'[0-9]'), + errorText: 'Need at least 2 numbers' +); + +// New API equivalent +Validators.hasMinNumericChars( + min: 2, + customNumericCounter: (input) => RegExp(r'[0-9]').allMatches(input).length, + hasMinNumericCharsMsg: (_, __) => 'Need at least 2 numbers' +); +``` + +- `FormBuilderValidators.hasSpecialChars` +```dart +// Old API +FormBuilderValidators.hasSpecialChars( + atLeast: 1, + regex: RegExp(r'[!@#$%^&*(),.?":{}|<>]'), + errorText: 'Need at least 1 special character' +); + +// New API equivalent +Validators.hasMinSpecialChars( + min: 1, + customSpecialCounter: (input) => RegExp(r'[!@#$%^&*(),.?":{}|<>]').allMatches(input).length, + hasMinSpecialCharsMsg: (_, __) => 'Need at least 1 special character' +); +``` + +- `FormBuilderValidators.hasUppercaseChars` +```dart +// Old API +FormBuilderValidators.hasUppercaseChars( + atLeast: 2, + regex: RegExp(r'[A-Z]'), + errorText: 'Need at least 2 uppercase letters' +); + +// New API equivalent +Validators.hasMinUppercaseChars( + min: 2, + customUppercaseCounter: (input) => RegExp(r'[A-Z]').allMatches(input).length, + hasMinUppercaseCharsMsg: (_, __) => 'Need at least 2 uppercase letters' +); +``` + +For the remaining `isFalse` and `isTrue`, it is not necessary to wrap them with `Validators.string` even if the form widget does not provide `String` input. + + +- `FormBuilderValidators.isFalse` +```dart +// Old API +FormBuilderValidators.isFalse( + errorText: 'Value must be false' +); + +// New API equivalent +Validators.isFalse( + isFalseMsg: (_) => 'Value must be false' +); +``` + +- `FormBuilderValidators.isTrue` +```dart +// Old API +FormBuilderValidators.isTrue( + errorText: 'Value must be true' +); + +// New API equivalent +Validators.isTrue( + isTrueMsg: (_) => 'Value must be true' +); +``` + +#### Collection validators +- `FormBuilderValidators.containsElement` +```dart +// Old API +FormBuilderValidators.containsElement( + [v1, v2, v3], + errorText: 'Value must be in the list' +); + +// New API equivalent +Validators.inList( + [v1, v2, v3], + inListMsg: (_, __) => 'Value must be in the list' +); +``` +- `FormBuilderValidators.equalLength` + - The parameter `allowEmpty` was removed and [additional logic](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/collection/equal_length_validator.dart#L40) must be provided to handle the case in which this parameter is true. + +```dart +// Old API +FormBuilderValidators.equalLength( + 8, + allowEmpty: false, + errorText: 'Must be exactly 8 characters' +); + +// New API equivalent +Validators.equalLength( + 8, + equalLengthMsg: (_, __) => 'Must be exactly 8 characters' +); + +// Note: The parameter `allowEmpty` was removed. +// To handle 'allowEmpty: true' case, use something like: +Validators.or([ + Validators.equalLength(0), + Validators.equalLength(8) +]); +``` + +- `FormBuilderValidators.maxLength` +```dart +// Old API +FormBuilderValidators.maxLength( + 50, + errorText: 'Must be no more than 50 characters' +); + +// New API equivalent +Validators.maxLength( + 50, + maxLengthMsg: (_, __) => 'Must be no more than 50 characters' +); +``` + +- `FormBuilderValidators.minLength` +```dart +// Old API +FormBuilderValidators.minLength( + 8, + errorText: 'Must be at least 8 characters' +); + +// New API equivalent +Validators.minLength( + 8, + minLengthMsg: (_, __) => 'Must be at least 8 characters' +); +``` + +- `FormBuilderValidators.range` (for collections) +```dart +// Old API (inclusive: true) +FormBuilderValidators.range( + 3, + 10, + inclusive: true, + errorText: 'Length must be between 3 and 10' +); + +// New API equivalent +Validators.betweenLength( + 3, + 10, + betweenLengthMsg: (_) => 'Length must be between 3 and 10' +); + +/******************************************************************************/ + +// Old API (inclusive: false) +FormBuilderValidators.range( + 3, + 10, + inclusive: false, + errorText: 'Length must be between 4 and 9' +); + +// New API equivalent +Validators.betweenLength( + 4, + 9, + betweenLengthMsg: (_) => 'Length must be between 4 and 9' +); +``` + +- `FormBuilderValidators.range` (for numeric values) +```dart +// Old API +FormBuilderValidators.range( + 1, + 100, + inclusive: true, + errorText: 'Value must be between 1 and 100' +); + +// New API equivalent +Validators.between( + 1, + 100, + minInclusive: true, + maxInclusive: true, + betweenMsg: (_, __, ___, ____, _____) => 'Value must be between 1 and 100' +); +``` +- `FormBuilderValidators.unique([v1, v2, v3], errorText:'some error')`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/collection/unique_validator.dart#L32), thus, a custom validator should be implemented. + - Example: + +```dart +Validator unique(List values, {String? errorText}){ + return (input){ + return values.where((element) => element == input).length > 1? errorText:null; + }; + +} +``` + +### Core validators + +- `FormBuilderValidators.aggregate` +```dart +// Old API +FormBuilderValidators.aggregate([ + validator1, + validator2, + validator3 +]); + +// New API equivalent +Validators.and( + [validator1, validator2, validator3], + separator: '\n', + printErrorAsSoonAsPossible: false +); +``` + +- `FormBuilderValidators.compose` +```dart +// Old API +FormBuilderValidators.compose([ + validator1, + validator2 +]); + +// New API equivalent +Validators.and([ + validator1, + validator2 +]); +``` + +- `FormBuilderValidators.conditional` +```dart +// Old API +FormBuilderValidators.conditional( + (value) => value != null, + validator +); + +// New API equivalent +Validators.validateIf( + (value) => value != null, + validator +); +``` + +- `FormBuilderValidators.defaultValue` +```dart +// Old API +FormBuilderValidators.defaultValue( + 'Default', + validator +); + +// New API equivalent +Validators.validateWithDefault( + 'Default', + validator +); +``` + +- `FormBuilderValidators.equal` +```dart +// Old API +FormBuilderValidators.equal( + expectedValue, + 'Must match the expected value' +); + +// New API equivalent +Validators.equal( + expectedValue, + equalMsg: (_, __) => 'Must match the expected value' +); +``` + +- `FormBuilderValidators.log` +```dart +// Old API +FormBuilderValidators.log( + logFunction, + 'Error message' +); + +// New API equivalent +Validators.debugPrintValidator( + (input) => logFunction?.call(input) ?? 'Error message' +); +``` + +- `FormBuilderValidators.notEqual` +```dart +// Old API +FormBuilderValidators.notEqual( + forbiddenValue, + 'Must not match the forbidden value' +); + +// New API equivalent +Validators.notEqual( + forbiddenValue, + notEqualMsg: (_, __) => 'Must not match the forbidden value' +); +``` + +- `FormBuilderValidators.or` +```dart +// Old API +FormBuilderValidators.or([ + validator1, + validator2 +]); + +// New API equivalent +Validators.or([ + validator1, + validator2 +]); +``` + +- `FormBuilderValidators.required` +```dart +// Old API +FormBuilderValidators.required( + 'This field is required' +); + +// New API equivalent +Validators.required( + null, + 'This field is required' +); +``` + +- `FormBuilderValidators.skipWhen` +```dart +// Old API +FormBuilderValidators.skipWhen( + (value) => value == null, + validator +); + +// New API equivalent +Validators.skipIf( + (value) => value == null, + validator +); +``` + +- `FormBuilderValidators.transform` +```dart +// Old API +FormBuilderValidators.transform( + (value) => value?.toString().trim(), + validator +); + +// New API equivalent +Validators.transformAndValidate( + (value) => value?.toString().trim(), + next: validator +); +``` + +### Datetime validators + +- `FormBuilderValidators.dateFuture()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/datetime/date_future_validator.dart#L29), thus, a custom validator should be implemented. + - Example: + +```dart +// Old API: +FormBuilderValidators.dateFuture(errorText: 'Date must be in the future'); + +// New API (closest): +Validators.dateTime( + Validators.after( + DateTime.now(), + afterMsg: (_, __)=>'Date must be in the future', + ), +); +``` + +- `FormBuilderValidators.datePast()`:there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/datetime/date_past_validator.dart#L29), thus, a custom validator should be implemented. + - Example: + +```dart +// Old API: +FormBuilderValidators.datePast(errorText: 'Date must be in the past'); + +// New API (closest): +Validators.dateTime( + Validators.before( + DateTime.now(), + beforeMsg: (_, __)=>'Date must be in the past', + ), +); +``` + +- `FormBuilderValidators.dateRange()` + +```dart +// Old API +FormBuilderValidators.dateRange( + minDate, + maxDate, + errorText: 'Date must be between min and max dates' +); + +// New API equivalent +Validators.dateTime( + Validators.betweenDateTime( + minDate, + maxDate, + betweenDateTimeMsg: (_, __, ___) => 'Date must be between min and max dates' + ), +); +``` + +- `FormBuilderValidators.dateTime()`: this validator only checks if the input of type `DateTime?` is not null. Something close would be the following example: + +```dart +// Old API: +FormBuilderValidators.dateTime(errorText: 'Invalid date time'); + +// New API (close): +final errorText = 'Invalid date time'; +Validators.required(Validators.dateTime(null, (_)=>errorText), errorText); +``` + +- `FormBuilderValidators.date()` +```dart +// Old API +FormBuilderValidators.date(errorText: 'Invalid date time'); + +// New API +Validators.dateTime(null, (_)=>'Invalid date time'); +``` +- `FormBuilderValidators.time()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/datetime/time_validator.dart#L48). But it is a combination of `Validators.dateTime` with `Validators.match` using some specific regex. + +- `FormBuilderValidators.timeZone()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/datetime/timezone_validator.dart#L75). +Something close would be: +```dart +Validators.inList(validTimezones, inListMsg: (_, __)=> 'Invalid timezone') +``` + +### File validators + +- `FormBuilderValidators.fileExtension()` +```dart +// Old API: +FormBuilderValidators.fileExtension( + ['pdf', 'txt', 'png'], + errorText: 'Invalid extension', +); + +// New API: +Validators.matchesAllowedExtensions( + // Actually, the extension is '.pdf' and not 'pdf' + ['.pdf', '.txt', '.png'], + matchesAllowedExtensionsMsg:(_)=>'Invalid extension', + caseSensitive: false, +); +``` +- `FormBuilderValidators.fileName()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/file/file_name_validator.dart#L46). Something close would be: +```dart +// Old API: +FormBuilderValidators.fileName( + regex: RegExp(r'^[a-zA-Z0-9_\-\.]+$'), + errorText: 'invalid file name' +); + +// New API: +Validators.match( + RegExp(r'^[a-zA-Z0-9_\-\.]+$'), + matchMsg: (_)=>'invalid file name', +); +``` + +- `FormBuilderValidators.fileSize()` +```dart +// base1024Conversion:true +// Old API: +FormBuilderValidators.fileSize( + 12 * 1024, + base1024Conversion:true, + errorText:'error text', +); + +// New API: +Validators.maxFileSize( + 12 * 1024, + base: Base.b1024, + maxFileSizeMsg: (_, __, ___)=>'error text', +); +//-------------------------------------------------------------------------- +// base1024Conversion:false +// Old API: +FormBuilderValidators.fileSize( + 12 * 1000, + base1024Conversion:false, + errorText:'error text', +); + +// New API: +Validators.maxFileSize( + 12 * 1000, + base: Base.b1000, + maxFileSizeMsg: (_, __, ___)=>'error text', +); +``` +- `FormBuilderValidators.mimeType()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/file/mime_type_validator.dart#L47). Something close would be: +```dart +// Old API: +FormBuilderValidators.mimeType( + // This regex is the same as the default + regex: RegExp(r'^[a-zA-Z0-9!#$&^_-]+\/[a-zA-Z0-9!#$&^_-]+$'), + errorText: 'error text', +); + +// New API: +Validators.match( + RegExp(r'^[a-zA-Z0-9!#$&^_-]+\/[a-zA-Z0-9!#$&^_-]+$'), + matchMsg: (_)=>'error text' +); +``` +- `FormBuilderValidators.path()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/file/path_validator.dart#L46). Something close would be: +```dart +// Old API: +FormBuilderValidators.path( + errorText: 'error text', +); + +// New API: +Validators.match( + RegExp(r'^((\/|\\|[a-zA-Z]:\/)?([^<>:"|?*]+(\/|\\)?)+)$'), + matchMsg: (_)=>'error text' +); +``` + +### Finance validators + +- `FormBuilderValidators.bic()` +```dart +// Old API (no regex): +FormBuilderValidators.bic( + errorText: 'error text', +); + +// New API: +Validators.bic( + bicMsg: (_)=>'error text' +); + +//------------------------------------------------------------------------------ +// Old API (with regex): +FormBuilderValidators.bic(regex: someRegex); + +// New API: +Validators.bic(isBic: (input)=>someRegex.hasMatch(input)); +``` +- `FormBuilderValidators.creditCardCVC()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/finance/credit_card_cvc_validator.dart#L29). Something close would be: +```dart +// Old API: +FormBuilderValidators.creditCardCVC( + errorText: 'invalid CVC number' +); + +// New API: +Validators.and([ + Validators.int(null, 'invalid CVC number'), + Validators.equalLength(3, equalLengthMsg: 'invalid CVC number'), +]); +``` +- `FormBuilderValidators.creditCardExpirationDate()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/finance/credit_card_expiration_date_validator.dart#L52). +- `FormBuilderValidators.creditCard()` +```dart +// Old API: +FormBuilderValidators.creditCard( + errorText: 'invalid credit card', +); + +// New API: +Validators.creditCard( + creditCardMsg: 'invalid credit card', +); + +``` + +- `FormBuilderValidators.iban()` +```dart +// Old API: +// OBS.: There is a bug in the regex parameter. It is not used at all. +FormBuilderValidators.iban(errorText: 'invalid iban'); + +// New API: +Validators.iban(ibanMsg: (_)=>'invalid iban'); +``` + +### Identity validators + +- `FormBuilderValidators.city()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/identity/city_validator.dart#L53). Something close would be: +```dart +// Old API: +FormBuilderValidators.city( + regex: RegExp(r'^[A-Z][a-zA-Z\s]+$'), + citiesWhitelist: ['CityA', 'CityB', 'CityC'], + citiesBlacklist: ['CityD', 'CityE'], + errorText: 'invalid city', +); + +// New API (expects input as String): +Validators.and([ + Validators.match( + RegExp(r'^[A-Z][a-zA-Z\s]+$'), + matchMsg: (_)=>'invalid city' + ), + Validators.inList( + ['CityA', 'CityB', 'CityC'], + inListMsg: (_, __) => 'invalid city', + ), + Validators.notInList( + ['CityD', 'CityE'], + notInListMsg: (_, __) => 'invalid city', + ), +]); +``` + +- `FormBuilderValidators.country()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/identity/country_validator.dart#L42). Something close would be: +```dart +// Old API: +FormBuilderValidators.country( + countryWhitelist: ['CountryA', 'CountryB', 'CountryC'], + countryBlacklist: ['CountryD', 'CountryE'], + errorText: 'invalid country', +); + +// New API (expects input as String): +Validators.and([ + Validators.inList( + ['CountryA', 'CountryB', 'CountryC'], + inListMsg: (_, __) => 'invalid country', + ), + Validators.notInList( + ['CountryD', 'CountryE'], + notInListMsg: (_, __) => 'invalid country', + ), +]); +``` + +- `FormBuilderValidators.firstName()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/identity/firstname_validator.dart#L53). Something close would be: +```dart +// Old API: +FormBuilderValidators.firstName( + regex: RegExp(r'^[A-Z][a-zA-Z]+$'), + firstNameWhitelist: ['NameA', 'NameB', 'NameC'], + firstNameBlacklist: ['NameD', 'NameE'], + errorText: 'invalid name', +); + +// New API (expects input as String): +Validators.and([ + Validators.match( + RegExp(r'^[A-Z][a-zA-Z]+$'), + matchMsg: (_)=>'invalid name' + ), + Validators.inList( + ['NameA', 'NameB', 'NameC'], + inListMsg: (_, __) => 'invalid name', + ), + Validators.notInList( + ['NameD', 'NameE'], + notInListMsg: (_, __) => 'invalid name', + ), +]); +``` +- `FormBuilderValidators.lastName()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/identity/lastname_validator.dart#L53). Something close would be: +```dart +// Old API: +FormBuilderValidators.lastName( + regex: RegExp(r'^[A-Z][a-zA-Z]+$'), + lastNameWhitelist: ['NameA', 'NameB', 'NameC'], + lastNameBlacklist: ['NameD', 'NameE'], + errorText: 'invalid name', +); + +// New API (expects input as String): +Validators.and([ + Validators.match( + RegExp(r'^[A-Z][a-zA-Z]+$'), + matchMsg: (_)=>'invalid name' + ), + Validators.inList( + ['NameA', 'NameB', 'NameC'], + inListMsg: (_, __) => 'invalid name', + ), + Validators.notInList( + ['NameD', 'NameE'], + notInListMsg: (_, __) => 'invalid name', + ), +]); +``` +- `FormBuilderValidators.passport()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/identity/passport_number_validator.dart#L53). Something close would be: +```dart +// Old API: +FormBuilderValidators.passport( + regex: RegExp(r'^[A-Za-z0-9]{6,9}$'), + passportNumberNameWhitelist: ['PassA', 'PassB', 'PassC'], + passportNumberNameBlacklist: ['PassD', 'PassE'], + errorText: 'invalid passport', +); + +// New API (expects input as String): +Validators.and([ + Validators.match( + RegExp(r'^[A-Za-z0-9]{6,9}$'), + matchMsg: (_)=>'invalid passport' + ), + Validators.inList( + ['PassA', 'PassB', 'PassC'], + inListMsg: (_, __) => 'invalid passport', + ), + Validators.notInList( + ['PassD', 'PassE'], + notInListMsg: (_, __) => 'invalid passport', + ), +]); +``` +- `FormBuilderValidators.password()` +```dart +// Old API: +FormBuilderValidators.password( + minLength: 1, + maxLength: 32, + minUppercaseCount: 2, + minLowercaseCount: 2, + minNumberCount: 2, + minSpecialCharCount: 2, + errorText: 'error text', +); + +// New API: +Validators.password( + minLength: 1, + maxLength: 32, + minUppercaseCount: 2, + minLowercaseCount: 2, + minNumberCount: 2, + minSpecialCharCount: 2, + passwordMsg:(_) => 'error text', +); +``` +- `FormBuilderValidators.ssn()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/identity/ssn_validator.dart#L47) +- `FormBuilderValidators.state()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/identity/state_validator.dart#L53). Something close would be: +```dart +// Old API: +FormBuilderValidators.state( + regex: RegExp(r'^[A-Z][a-zA-Z\s]+$'), + stateWhitelist: ['stateA', 'stateB', 'stateC'], + stateBlacklist: ['stateD', 'stateE'], + errorText: 'invalid state', +); + +// New API (expects input as String): +Validators.and([ + Validators.match( + RegExp(r'^[A-Z][a-zA-Z\s]+$'), + matchMsg: (_)=>'invalid state' + ), + Validators.inList( + ['stateA', 'stateB', 'stateC'], + inListMsg: (_, __) => 'invalid state', + ), + Validators.notInList( + ['stateD', 'stateE'], + notInListMsg: (_, __) => 'invalid state', + ), +]); +``` +- `FormBuilderValidators.street()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/identity/street_validator.dart#L53). Something close would be: +```dart +// Old API: +FormBuilderValidators.street( + regex: RegExp(r'^[A-Z0-9][a-zA-Z0-9\s]*$'), + streetWhitelist: ['streetA', 'streetB', 'streetC'], + streetBlacklist: ['streetD', 'streetE'], + errorText: 'invalid street', +); + +// New API (expects input as String): +Validators.and([ + Validators.match( + RegExp(r'^[A-Z0-9][a-zA-Z0-9\s]*$'), + matchMsg: (_)=>'invalid street' + ), + Validators.inList( + ['streetA', 'streetB', 'streetC'], + inListMsg: (_, __) => 'invalid street', + ), + Validators.notInList( + ['streetD', 'streetE'], + notInListMsg: (_, __) => 'invalid street', + ), +]); +``` +- `FormBuilderValidators.username()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/identity/username_validator.dart#L71). Something close would be: +```dart +// Old API: +FormBuilderValidators.username( + minLength:4, + maxLength:10, + allowNumbers:true, + allowUnderscore:false, + allowDots:false, + allowDash:false, + allowSpace:false, + allowSpecialChar:false, + errorText: 'invalid username', +); + +// New API: +Validators.and([ + Validators.minLength(4, minLengthMsg: (_, __)=>'invalid username'), + Validators.maxLength(10, minLengthMsg: (_, __)=>'invalid username'), + Validators.hasMaxNumericChars(10, minLengthMsg: (_, __)=>'invalid username'), + +]); + + +``` +- `FormBuilderValidators.zipCode()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/identity/zip_code_validator.dart#L43). Something close would be: +```dart +// Old API: +FormBuilderValidators.zipCode( + errorText: 'error text', +); + +// New API: +Validators.match( + RegExp(r'^\d{5}(?:[-\s]\d{4})?$'), + matchMsg: (_)=>'error text' +); +``` + + +### Network validators + +- `FormBuilderValidators.email()` +```dart +// Old API: +FormBuilderValidators.email( + regex: emailRegex, + errorText: 'invalid email', +); + +// New API: +Validators.email( + regex: emailRegex, + emailMsg: (_)=>'invalid email', +); +``` + +- `FormBuilderValidators.ip()` +```dart +// For IPv4 +// Old API: +FormBuilderValidators.ip( + regex: ipRegex, + errorText: 'invalid ipV4', +); + +// New API: +Validators.ip( + regex: ipRegex, + emailMsg: (_)=>'invalid ipV4', +); + +// For IPv6 +// Old API: +FormBuilderValidators.ip( + version: 6 + regex: ipRegex, + errorText: 'invalid ipV4', +); + +// New API: +Validators.ip( + version: IpVersion.iPv6, + regex: ipRegex, + emailMsg: (_)=>'invalid ipV4', +); +``` + +- `FormBuilderValidators.latitude()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/network/latitude_validator.dart#L40). But something close would be: +```dart +// Old API: +FormBuilderValidators.latitude(); + +// New API: +Validators.transformAndValidate( + (String input) => input.replaceAll(',', '.'), + next: Validators.double(Validators.between(-90, 90)), +); + +``` + +- `FormBuilderValidators.longitude()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/network/longitude_validator.dart#L40). But something close would be: +```dart +// Old API: +FormBuilderValidators.longitude(); + +// New API: +Validators.transformAndValidate( + (String input) => input.replaceAll(',', '.'), + next: Validators.double(Validators.between(-180, 180)), +); + +``` + +TODO continue from here... +- `FormBuilderValidators.macAddress()` - requires the field's to be a valid MAC address. +- `FormBuilderValidators.phoneNumber()` - requires the field's value to be a valid phone number. +- `FormBuilderValidators.portNumber()` - requires the field's to be a valid port number. +- `FormBuilderValidators.url()` - requires the field's value to be a valid URL. + +### Numeric validators + +- `FormBuilderValidators.between()` - requires the field's to be between two numbers. +- `FormBuilderValidators.evenNumber()` - requires the field's to be an even number. +- `FormBuilderValidators.integer()` - requires the field's value to be an integer. +- `FormBuilderValidators.max()` - requires the field's value to be less than or equal to the provided number. +- `FormBuilderValidators.min()` - requires the field's value to be greater than or equal to the provided number. +- `FormBuilderValidators.negativeNumber()` - requires the field's to be a negative number. +- `FormBuilderValidators.notZeroNumber()` - requires the field's to be not a number zero. +- `FormBuilderValidators.numeric()` - requires the field's value to be a valid number. +- `FormBuilderValidators.oddNumber()` - requires the field's to be an odd number. +- `FormBuilderValidators.positiveNumber()` - requires the field's to be a positive number. +- `FormBuilderValidators.prime()` - requires the field's to be a prime number. + +### String validators + +- `FormBuilderValidators.alphabetical()` - requires the field's to contain only alphabetical characters. +- `FormBuilderValidators.contains()` - requires the substring to be in the field's value. +- `FormBuilderValidators.endsWith()` - requires the substring to be the end of the field's value. +- `FormBuilderValidators.lowercase()` - requires the field's value to be lowercase. +- `FormBuilderValidators.matchNot()` - requires the field's value to not match the provided regex pattern. +- `FormBuilderValidators.match()` - requires the field's value to match the provided regex pattern. +- `FormBuilderValidators.maxWordsCount()` - requires the word count of the field's value to be less than or equal to the provided maximum count. +- `FormBuilderValidators.minWordsCount()` - requires the word count of the field's value to be greater than or equal to the provided minimum count. +- `FormBuilderValidators.singleLine()` - requires the field's string to be a single line of text. +- `FormBuilderValidators.startsWith()` - requires the substring to be the start of the field's value. +- `FormBuilderValidators.uppercase()` - requires the field's value to be uppercase. + +### Use-case validators + +- `FormBuilderValidators.base64()` - requires the field's to be a valid base64 string. +- `FormBuilderValidators.colorCode()` - requires the field's value to be a valid color code. +- `FormBuilderValidators.duns()` - requires the field's value to be a valid DUNS. +- `FormBuilderValidators.isbn()` - requires the field's to be a valid ISBN. +- `FormBuilderValidators.json()` - requires the field's to be a valid json string. +- `FormBuilderValidators.languageCode()` - requires the field's to be a valid language code. +- `FormBuilderValidators.licensePlate()` - requires the field's to be a valid license plate. +- `FormBuilderValidators.uuid()` - requires the field's to be a valid uuid. +- `FormBuilderValidators.vin()` - requires the field's to be a valid VIN number. + +### Extension method validators + +Used for chaining and combining multiple validators. + +- `FormBuilderValidator.and()` - Combines the current validator with another validator using logical AND. +- `FormBuilderValidator.or()` - Combines the current validator with another validator using logical OR. +- `FormBuilderValidator.when()` - Adds a condition to apply the validator only if the condition is met. +- `FormBuilderValidator.unless()` - Adds a condition to apply the validator only if the condition is not met. +- `FormBuilderValidator.transform()` - Transforms the value before applying the validator. +- `FormBuilderValidator.skipWhen()` - Skips the validator if the condition is met. +- `FormBuilderValidator.log()` - Logs the value during the validation process. +- `FormBuilderValidator.withErrorMessage()` - Overrides the error message of the current validator. diff --git a/example/lib/basic_examples.dart b/example/lib/basic_examples.dart index f746a3b0..c9277308 100644 --- a/example/lib/basic_examples.dart +++ b/example/lib/basic_examples.dart @@ -34,13 +34,13 @@ class BasicExamplesPage extends StatelessWidget { labelText: 'Type "I want to delete" to confirm the action.'), autovalidateMode: AutovalidateMode.onUserInteraction, - validator: Validators.isEqual('I want to delete'), + validator: Validators.equal('I want to delete'), ), TextFormField( decoration: const InputDecoration( labelText: 'Username (should not be "RESERVED")'), autovalidateMode: AutovalidateMode.onUserInteraction, - validator: Validators.isNotEqual('RESERVED'), + validator: Validators.notEqual('RESERVED'), ), Align( alignment: Alignment.centerLeft, @@ -54,7 +54,7 @@ class BasicExamplesPage extends StatelessWidget { const InputDecoration(labelText: 'Input must not be null'), autovalidateMode: AutovalidateMode.onUserInteraction, validator: (String? input) { - final String? isRequiredMsg = Validators.isRequired()(input); + final String? isRequiredMsg = Validators.required()(input); return isRequiredMsg ?.toUpperCase() .replaceFirst('OVERRIDE: ', ''); diff --git a/example/lib/forms_with_validate_granularlly.dart b/example/lib/forms_with_validate_granularlly.dart index dad155fe..4848ac89 100644 --- a/example/lib/forms_with_validate_granularlly.dart +++ b/example/lib/forms_with_validate_granularlly.dart @@ -75,7 +75,7 @@ class _BodyState extends State<_Body> { hintText: 'Enter your full name', prefixIcon: Icon(Icons.person), ), - validator: V.isRequired(V.match(RegExp('[A-Z].*'), + validator: V.required(V.match(RegExp('[A-Z].*'), matchMsg: (_) => 'The name must start with uppercase')), textInputAction: TextInputAction.next, ), @@ -87,7 +87,7 @@ class _BodyState extends State<_Body> { hintText: 'Enter your email', prefixIcon: Icon(Icons.email), ), - validator: V.isRequired(V.email()), + validator: V.required(V.email()), keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.next, ), @@ -100,9 +100,8 @@ class _BodyState extends State<_Body> { hintText: 'YYYY-MM-DD', prefixIcon: Icon(Icons.calendar_today), ), - validator: V.isRequired(V.isDateTime(V.isBefore( - DateTime.now(), - isBeforeMsg: (_, __) => 'Date must be in the past.'))), + validator: V.required(V.dateTime(V.before(DateTime.now(), + beforeMsg: (_, __) => 'Date must be in the past.'))), keyboardType: TextInputType.datetime, textInputAction: TextInputAction.next, onTap: () async { @@ -127,7 +126,7 @@ class _BodyState extends State<_Body> { prefixIcon: Icon(Icons.height), suffixText: 'm', ), - validator: V.isRequired(V.isNum(V.between(0.5, 2.5, + validator: V.required(V.num(V.between(0.5, 2.5, betweenMsg: (_, num min, num max, __, ___) => 'Please enter a realistic height [$min-${max}m]'))), keyboardType: TextInputType.number, @@ -142,7 +141,7 @@ class _BodyState extends State<_Body> { prefixIcon: Icon(Icons.monitor_weight), suffixText: 'kg', ), - validator: V.isOptional(V.isNum(V.between(20, 300, + validator: V.optional(V.num(V.between(20, 300, betweenMsg: (_, num min, num max, ____, _____) => 'weight must be in [$min, ${max}kg]'))), keyboardType: TextInputType.number, @@ -156,7 +155,7 @@ class _BodyState extends State<_Body> { hintText: 'Enter your phone number', prefixIcon: Icon(Icons.phone), ), - validator: V.isRequired(), + validator: V.required(), keyboardType: TextInputType.phone, textInputAction: TextInputAction.next, ), @@ -181,9 +180,8 @@ class _BodyState extends State<_Body> { child: Text('Invalid option 2'), ), ]).toList(), - validator: V.isRequired(V.containsElement( - validBloodTypeOptions, - containsElementMsg: (_, List v) => + validator: V.required(V.inList(validBloodTypeOptions, + inListMsg: (_, List v) => 'The option must be one of: ${v.join(', ')}.')), onChanged: (String? value) { setState(() { diff --git a/example/lib/generic_examples.dart b/example/lib/generic_examples.dart index f527db19..c107c985 100644 --- a/example/lib/generic_examples.dart +++ b/example/lib/generic_examples.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:form_builder_validators/form_builder_validators.dart' - show Validators, Validator; + show Validator, Validators; /// alias for Validators class. typedef V = Validators; @@ -24,8 +24,8 @@ class GenericExamplesPage extends StatelessWidget { decoration: const InputDecoration(labelText: 'Age'), keyboardType: TextInputType.number, autovalidateMode: AutovalidateMode.always, - validator: V.isRequired(V.and(>[ - V.isNum(V.lessThan(70), (_) => 'La edad debe ser numérica.'), + validator: V.required(V.and(>[ + V.num(V.lessThan(70), (_) => 'La edad debe ser numérica.'), /// Include your own custom `FormFieldValidator` function, if you want /// Ensures positive values only. We could also have used `FormBuilderValidators.min( 0)` instead @@ -47,7 +47,7 @@ class GenericExamplesPage extends StatelessWidget { labelText: 'Required Field', prefixIcon: Icon(Icons.star), ), - validator: V.isRequired(), + validator: V.required(), autofillHints: const [AutofillHints.name], textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, @@ -59,12 +59,11 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.numbers), ), keyboardType: TextInputType.number, - validator: V.isRequired(V.isNum()), + validator: V.required(V.num()), autofillHints: const [AutofillHints.oneTimeCode], textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), - /* TODO implement the email and url validator // Email Validator TextFormField( decoration: const InputDecoration( @@ -72,7 +71,7 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.email), ), keyboardType: TextInputType.emailAddress, - validator: v.email(), + validator: V.required(V.email()), autofillHints: const [AutofillHints.email], textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, @@ -84,19 +83,18 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.link), ), keyboardType: TextInputType.url, - validator: v.url(), + validator: V.required(V.url()), autofillHints: const [AutofillHints.url], textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), - */ // Min Length Validator TextFormField( decoration: const InputDecoration( labelText: 'Min Length Field', prefixIcon: Icon(Icons.text_fields), ), - validator: V.isRequired(V.minLength(5)), + validator: V.required(V.minLength(5)), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), @@ -106,7 +104,7 @@ class GenericExamplesPage extends StatelessWidget { labelText: 'Max Length Field', prefixIcon: Icon(Icons.text_fields), ), - validator: V.isRequired(V.maxLength(10)), + validator: V.required(V.maxLength(10)), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), @@ -117,7 +115,7 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.exposure_neg_1), ), keyboardType: TextInputType.number, - validator: V.isRequired(V.isNum(V.greaterThan(10))), + validator: V.required(V.num(V.greaterThan(10))), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), @@ -128,7 +126,7 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.exposure_plus_1), ), keyboardType: TextInputType.number, - validator: V.isRequired(V.isNum(V.lessThan(100))), + validator: V.required(V.num(V.lessThan(100))), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), @@ -138,34 +136,31 @@ class GenericExamplesPage extends StatelessWidget { labelText: 'Equal Field', prefixIcon: Icon(Icons.check), ), - validator: V.isEqual('test'), + validator: V.equal('test'), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), - /* TODO implement contains substring // Contains Validator TextFormField( decoration: const InputDecoration( labelText: 'Contains "test"', prefixIcon: Icon(Icons.search), ), - validator: FormBuilderValidators.contains('test'), + validator: V.required(V.contains('test')), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), - */ // Match Validator TextFormField( decoration: const InputDecoration( labelText: 'Match Pattern', prefixIcon: Icon(Icons.pattern), ), - validator: V.isRequired(V.match(RegExp(r'^[a-zA-Z0-9]+$'))), + validator: V.required(V.match(RegExp(r'^[a-zA-Z0-9]+$'))), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), - /* TODO implement id, uuid, credit cart, and phone number validators // IP Validator TextFormField( decoration: const InputDecoration( @@ -173,7 +168,7 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.computer), ), keyboardType: TextInputType.number, - validator: v.ip(), + validator: V.required(V.ip()), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), @@ -184,7 +179,7 @@ class GenericExamplesPage extends StatelessWidget { labelText: 'UUID Field', prefixIcon: Icon(Icons.code), ), - validator: v.uuid(), + validator: V.required(V.uuid()), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), @@ -195,11 +190,12 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.credit_card), ), keyboardType: TextInputType.number, - validator: FormBuilderValidators.creditCard(), + validator: V.required(V.creditCard()), autofillHints: const [AutofillHints.creditCardNumber], textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), + /* TODO implement id, uuid, credit cart, and phone number validators // Phone Number Validator TextFormField( decoration: const InputDecoration( @@ -220,7 +216,7 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.lock), ), obscureText: true, - validator: V.isRequired(V.password()), + validator: V.required(V.password()), autofillHints: const [AutofillHints.password], textInputAction: TextInputAction.done, autovalidateMode: AutovalidateMode.always, @@ -232,8 +228,70 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.calendar_today), ), keyboardType: TextInputType.number, - validator: V.isRequired( - V.isNum(V.and(>[V.between(0, 120)]))), + validator: V.required( + V.num(V.and(>[V.between(0, 120)]))), + textInputAction: TextInputAction.done, + autovalidateMode: AutovalidateMode.always, + ), + TextFormField( + decoration: const InputDecoration( + labelText: 'CVC number validator', + prefixIcon: Icon(Icons.numbers), + ), + keyboardType: TextInputType.number, + validator: V.required( + V.and(>[ + V.int(), + V.equalLength(3), + ]), + ), + textInputAction: TextInputAction.done, + autovalidateMode: AutovalidateMode.always, + ), + TextFormField( + decoration: const InputDecoration( + labelText: 'City', + prefixIcon: Icon(Icons.location_city), + ), + validator: V.required( + V.and(>[ + V.match( + RegExp(r'^[A-Z][a-zA-Z\s]+$'), + matchMsg: (_) => 'invalid city', + ), + V.inList( + ['CityA', 'CityB', 'CityC'], + inListMsg: (_, __) => 'invalid city', + ), + V.notInList( + ['CityD', 'CityE'], + notInListMsg: (_, __) => 'invalid city', + ), + ]), + 'invalid city', + ), + textInputAction: TextInputAction.done, + autovalidateMode: AutovalidateMode.always, + ), + TextFormField( + decoration: const InputDecoration( + labelText: 'Latitude', + prefixIcon: Icon(Icons.add_location), + ), + validator: V.required(V.transformAndValidate( + (String input) => input.replaceAll(',', '.'), + next: V.double(V.between(-90, 90)))), + textInputAction: TextInputAction.done, + autovalidateMode: AutovalidateMode.always, + ), + TextFormField( + decoration: const InputDecoration( + labelText: 'Longitude', + prefixIcon: Icon(Icons.add_location), + ), + validator: V.required(V.transformAndValidate( + (String input) => input.replaceAll(',', '.'), + next: V.double(V.between(-180, 180)))), textInputAction: TextInputAction.done, autovalidateMode: AutovalidateMode.always, ), diff --git a/lib/l10n/intl_ar.arb b/lib/l10n/intl_ar.arb index 01462f20..8db766e0 100644 --- a/lib/l10n/intl_ar.arb +++ b/lib/l10n/intl_ar.arb @@ -62,6 +62,7 @@ "startsWithErrorText": "القيمة يجب أن تبدأ بـ {value}.", "endsWithErrorText": "القيمة يجب أن تنتهي بـ {value}.", "containsErrorText": "القيمة يجب أن تحتوي على {value}.", + "doesNotContainElementErrorText": "القيمة قد لا تكون في القائمة.", "betweenErrorText": "القيمة يجب أن تكون بين {min} و {max}.", "containsElementErrorText": "القيمة يجب أن تكون ضمن القائمة المسموح بها.", "ibanErrorText": "هذا الحقل يتطلب IBAN صالح.", diff --git a/lib/l10n/intl_bg.arb b/lib/l10n/intl_bg.arb index a368d290..350780ef 100644 --- a/lib/l10n/intl_bg.arb +++ b/lib/l10n/intl_bg.arb @@ -64,6 +64,7 @@ "containsErrorText": "Стойността трябва да съдържа {value}.", "betweenErrorText": "Стойността трябва да е между {min} и {max}.", "containsElementErrorText": "Стойността трябва да е в списъка с допустими стойности.", + "doesNotContainElementErrorText": "Стойността не може да бъде в списъка.", "ibanErrorText": "Това поле изисква валиден IBAN.", "uniqueErrorText": "Това поле изисква уникална стойност.", "bicErrorText": "Това поле изисква валиден BIC код.", diff --git a/lib/l10n/intl_bn.arb b/lib/l10n/intl_bn.arb index 3ac53c17..582c9a23 100644 --- a/lib/l10n/intl_bn.arb +++ b/lib/l10n/intl_bn.arb @@ -64,6 +64,7 @@ "containsErrorText": "মানটি {value} ধারণ করতে হবে।", "betweenErrorText": "মানটি {min} এবং {max} এর মধ্যে হতে হবে।", "containsElementErrorText": "মানটি অনুমোদিত তালিকার মধ্যে থাকতে হবে।", + "doesNotContainElementErrorText": "মান তালিকায় থাকতে পারবে না।", "ibanErrorText": "একটি বৈধ IBAN প্রয়োজন।", "uniqueErrorText": "মানটি অবশ্যই অনন্য মানগুলির মধ্যে অদ্বিতীয় হতে হবে।", "bicErrorText": "একটি বৈধ BIC প্রয়োজন।", diff --git a/lib/l10n/intl_bs.arb b/lib/l10n/intl_bs.arb index 56dc721d..4e6d5a00 100644 --- a/lib/l10n/intl_bs.arb +++ b/lib/l10n/intl_bs.arb @@ -64,6 +64,7 @@ "containsErrorText": "Vrijednost mora sadržavati {value}.", "betweenErrorText": "Vrijednost mora biti između {min} i {max}.", "containsElementErrorText": "Vrijednost mora biti na listi dopuštenih vrijednosti.", + "doesNotContainElementErrorText": "Vrijednost ne smije biti na listi.", "ibanErrorText": "Unesite validan IBAN.", "uniqueErrorText": "Vrijednost mora biti jedinstvena.", "bicErrorText": "Unesite validan BIC.", diff --git a/lib/l10n/intl_ca.arb b/lib/l10n/intl_ca.arb index e6c8c1cc..bc8a7d4c 100644 --- a/lib/l10n/intl_ca.arb +++ b/lib/l10n/intl_ca.arb @@ -64,6 +64,7 @@ "containsErrorText": "El valor ha de contenir {value}.", "betweenErrorText": "El valor ha d'estar entre {min} i {max}.", "containsElementErrorText": "El valor ha d'estar en la llista de valors permesos.", + "doesNotContainElementErrorText": "El valor no pot estar a la llista.", "ibanErrorText": "Aquest camp requereix un IBAN vàlid.", "uniqueErrorText": "Aquest camp requereix un valor únic.", "bicErrorText": "Aquest camp requereix un codi BIC vàlid.", diff --git a/lib/l10n/intl_cs.arb b/lib/l10n/intl_cs.arb index a0782cc1..6b466bea 100644 --- a/lib/l10n/intl_cs.arb +++ b/lib/l10n/intl_cs.arb @@ -64,6 +64,7 @@ "containsErrorText": "Hodnota musí obsahovat {value}.", "betweenErrorText": "Hodnota musí být mezi {min} a {max}.", "containsElementErrorText": "Hodnota musí být v seznamu povolených hodnot.", + "doesNotContainElementErrorText": "Hodnota nesmí být v seznamu.", "ibanErrorText": "Pole vyžaduje platné IBAN.", "uniqueErrorText": "Hodnota musí být jedinečná.", "bicErrorText": "Pole vyžaduje platný BIC kód.", diff --git a/lib/l10n/intl_da.arb b/lib/l10n/intl_da.arb index 81b4738e..7586df9c 100644 --- a/lib/l10n/intl_da.arb +++ b/lib/l10n/intl_da.arb @@ -64,6 +64,7 @@ "containsErrorText": "Værdien skal indeholde {value}.", "betweenErrorText": "Værdien skal være mellem {min} og {max}.", "containsElementErrorText": "Værdien skal være på listen over tilladte værdier.", + "doesNotContainElementErrorText": "Værdien må ikke være på listen.", "ibanErrorText": "Dette felt kræver en gyldig IBAN.", "uniqueErrorText": "Dette felt skal være unikt.", "bicErrorText": "Dette felt kræver en gyldig BIC.", diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index af1fbd34..42e26ebb 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -64,6 +64,7 @@ "containsErrorText": "Der Wert muss {value} enthalten.", "betweenErrorText": "Der Wert muss zwischen {min} und {max} liegen.", "containsElementErrorText": "Der Wert muss in der Liste der zulässigen Werte sein.", + "doesNotContainElementErrorText": "Wert darf nicht in der Liste enthalten sein.", "ibanErrorText": "Dieses Feld erfordert eine gültige IBAN.", "uniqueErrorText": "Dieses Feld erfordert einen eindeutigen Wert.", "bicErrorText": "Dieses Feld erfordert eine gültige BIC.", diff --git a/lib/l10n/intl_el.arb b/lib/l10n/intl_el.arb index 1b05ead2..bfa8409c 100644 --- a/lib/l10n/intl_el.arb +++ b/lib/l10n/intl_el.arb @@ -64,6 +64,7 @@ "containsErrorText": "Η τιμή πρέπει να περιέχει {value}.", "betweenErrorText": "Η τιμή πρέπει να είναι μεταξύ {min} και {max}.", "containsElementErrorText": "Η τιμή πρέπει να είναι στη λίστα των επιτρεπόμενων τιμών.", + "doesNotContainElementErrorText": "Η τιμή δεν μπορεί να είναι στη λίστα.", "ibanErrorText": "Η τιμή πρέπει να είναι έγκυρο IBAN.", "uniqueErrorText": "Η τιμή πρέπει να είναι μοναδική.", "bicErrorText": "Η τιμή πρέπει να είναι έγκυρος BIC.", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index b77fd412..e0f0cfea 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -380,6 +380,7 @@ } }, "containsElementErrorText": "Value must be in list.", + "doesNotContainElementErrorText": "Value may not be in list.", "ibanErrorText": "Value must be a valid IBAN.", "uniqueErrorText": "Value must be unique.", "bicErrorText": "Value must be a valid BIC.", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index a62ea60c..e84d09e7 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -64,6 +64,7 @@ "containsErrorText": "El valor debe contener {value}.", "betweenErrorText": "El valor debe estar entre {min} y {max}.", "containsElementErrorText": "El valor debe estar en la lista de valores permitidos.", + "doesNotContainElementErrorText": "El valor no puede estar en la lista.", "ibanErrorText": "Este campo requiere un IBAN válido.", "uniqueErrorText": "Este campo requiere un valor único.", "bicErrorText": "Este campo requiere un código BIC válido.", diff --git a/lib/l10n/intl_et.arb b/lib/l10n/intl_et.arb index 173e1b38..0a4e352f 100644 --- a/lib/l10n/intl_et.arb +++ b/lib/l10n/intl_et.arb @@ -64,6 +64,7 @@ "containsErrorText": "Väärtus peab sisaldama {value}.", "betweenErrorText": "Väärtus peab olema vahemikus {min} kuni {max}.", "containsElementErrorText": "Väärtus peab olema lubatud väärtuste nimekirjas.", + "doesNotContainElementErrorText": "Väärtus ei tohi olla nimekirjas.", "ibanErrorText": "See väli nõuab kehtivat IBAN-i.", "uniqueErrorText": "See väli peab olema unikaalne.", "bicErrorText": "See väli nõuab kehtivat BIC koodi.", diff --git a/lib/l10n/intl_fa.arb b/lib/l10n/intl_fa.arb index 11d1081e..722d7855 100644 --- a/lib/l10n/intl_fa.arb +++ b/lib/l10n/intl_fa.arb @@ -64,6 +64,7 @@ "containsErrorText": "مقدار باید شامل {value} باشد.", "betweenErrorText": "مقدار باید بین {min} و {max} باشد.", "containsElementErrorText": "مقدار باید در لیست مقادیر مجاز باشد.", + "doesNotContainElementErrorText": "مقدار نباید در لیست باشد.", "ibanErrorText": "این ورودی به شماره IBAN معتبر نیاز دارد.", "uniqueErrorText": "این ورودی باید یکتا باشد.", "bicErrorText": "این ورودی به شماره BIC معتبر نیاز دارد.", diff --git a/lib/l10n/intl_fi.arb b/lib/l10n/intl_fi.arb index b96245ef..fe4bccfb 100644 --- a/lib/l10n/intl_fi.arb +++ b/lib/l10n/intl_fi.arb @@ -64,6 +64,7 @@ "containsErrorText": "Arvon on sisällettävä {value}.", "betweenErrorText": "Arvon on oltava välillä {min} ja {max}.", "containsElementErrorText": "Arvon on oltava sallittujen arvojen luettelossa.", + "doesNotContainElementErrorText": "Arvo ei saa olla luettelossa.", "ibanErrorText": "Vaaditaan oikean muotoinen IBAN-tilinumero.", "uniqueErrorText": "Kentän arvon tulee olla uniikki.", "bicErrorText": "Vaaditaan oikean muotoinen BIC-koodi.", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 5dc1fc09..813fa5e6 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -64,6 +64,7 @@ "containsErrorText": "La valeur doit contenir {value}.", "betweenErrorText": "La valeur doit être comprise entre {min} et {max}.", "containsElementErrorText": "La valeur doit être dans la liste des valeurs autorisées.", + "doesNotContainElementErrorText": "La valeur ne peut pas être dans la liste.", "ibanErrorText": "Ce champ nécessite un IBAN valide.", "uniqueErrorText": "Ce champ doit être unique.", "bicErrorText": "Ce champ nécessite un code BIC valide.", diff --git a/lib/l10n/intl_he.arb b/lib/l10n/intl_he.arb index 6834b6af..43585979 100644 --- a/lib/l10n/intl_he.arb +++ b/lib/l10n/intl_he.arb @@ -64,6 +64,7 @@ "containsErrorText": "הערך חייב להכיל את {value}.", "betweenErrorText": "הערך חייב להיות בין {min} לבין {max}.", "containsElementErrorText": "הערך חייב להיות ברשימת הערכים המותרים.", + "doesNotContainElementErrorText": "הערך לא יכול להיות ברשימה.", "ibanErrorText": "שדה זה דורש מספר IBAN תקין.", "uniqueErrorText": "שדה זה דורש ערך ייחודי.", "bicErrorText": "שדה זה דורש מזהה BIC תקין.", diff --git a/lib/l10n/intl_hi.arb b/lib/l10n/intl_hi.arb index a8117b7b..dd527ca9 100644 --- a/lib/l10n/intl_hi.arb +++ b/lib/l10n/intl_hi.arb @@ -64,6 +64,7 @@ "containsErrorText": "मान में {value} होना चाहिए।", "betweenErrorText": "मान {min} और {max} के बीच होना चाहिए।", "containsElementErrorText": "मान सूची में होना चाहिए।", + "doesNotContainElementErrorText": "मान सूची में नहीं हो सकता.", "ibanErrorText": "मान को वैध IBAN होना चाहिए।", "uniqueErrorText": "मान अद्वितीय होनी चाहिए।", "bicErrorText": "मान को वैध BIC होना चाहिए।", diff --git a/lib/l10n/intl_hr.arb b/lib/l10n/intl_hr.arb index 52083af8..4e8eb37f 100644 --- a/lib/l10n/intl_hr.arb +++ b/lib/l10n/intl_hr.arb @@ -64,6 +64,7 @@ "containsErrorText": "Vrijednost mora sadržavati {value}.", "betweenErrorText": "Vrijednost mora biti između {min} i {max}.", "containsElementErrorText": "Vrijednost mora biti na listi dopuštenih vrijednosti.", + "doesNotContainElementErrorText": "Vrijednost ne smije biti na popisu.", "ibanErrorText": "Unesite validan IBAN.", "uniqueErrorText": "Vrijednost mora biti jedinstvena.", "bicErrorText": "Unesite validan BIC.", diff --git a/lib/l10n/intl_hu.arb b/lib/l10n/intl_hu.arb index ed565531..66932188 100644 --- a/lib/l10n/intl_hu.arb +++ b/lib/l10n/intl_hu.arb @@ -64,6 +64,7 @@ "containsErrorText": "Az értéknek tartalmaznia kell {value}-t.", "betweenErrorText": "Az értéknek {min} és {max} között kell lennie.", "containsElementErrorText": "Az értéknek az engedélyezett értékek listájában kell lennie.", + "doesNotContainElementErrorText": "Az érték nem lehet a listában.", "ibanErrorText": "Ez a mező érvényes IBAN számot igényel.", "uniqueErrorText": "Ez a mező egyedi értéket igényel.", "bicErrorText": "Ez a mező érvényes BIC kódot igényel.", diff --git a/lib/l10n/intl_id.arb b/lib/l10n/intl_id.arb index 0082f56e..bdb2be3c 100644 --- a/lib/l10n/intl_id.arb +++ b/lib/l10n/intl_id.arb @@ -64,6 +64,7 @@ "containsErrorText": "Nilai harus mengandung {value}.", "betweenErrorText": "Nilai harus antara {min} dan {max}.", "containsElementErrorText": "Nilai harus ada dalam daftar nilai yang diizinkan.", + "doesNotContainElementErrorText": "Nilai tidak boleh ada dalam daftar.", "ibanErrorText": "IBAN tidak valid.", "uniqueErrorText": "Nilai harus unik.", "bicErrorText": "BIC tidak valid.", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index 6d2a256c..57fcb539 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -64,6 +64,7 @@ "containsErrorText": "Il valore deve contenere {value}.", "betweenErrorText": "Il valore deve essere compreso tra {min} e {max}.", "containsElementErrorText": "Il valore deve essere presente nell'elenco dei valori consentiti.", + "doesNotContainElementErrorText": "Il valore non può essere nell'elenco.", "ibanErrorText": "Questo campo richiede un IBAN valido.", "uniqueErrorText": "Questo campo richiede un valore unico.", "bicErrorText": "Questo campo richiede un codice BIC valido.", diff --git a/lib/l10n/intl_ja.arb b/lib/l10n/intl_ja.arb index 87a37a0d..91987f44 100644 --- a/lib/l10n/intl_ja.arb +++ b/lib/l10n/intl_ja.arb @@ -64,6 +64,7 @@ "containsErrorText": "値は{value}を含む必要があります。", "betweenErrorText": "値は{min}から{max}の間である必要があります。", "containsElementErrorText": "値は許可された値のリストに含まれている必要があります。", + "doesNotContainElementErrorText": "値はリストに含まれていない可能性があります。", "ibanErrorText": "有効なIBANを入力してください。", "uniqueErrorText": "値は一意である必要があります。", "bicErrorText": "有効なBICを入力してください。", diff --git a/lib/l10n/intl_km.arb b/lib/l10n/intl_km.arb index 879d1fb2..05c8b892 100644 --- a/lib/l10n/intl_km.arb +++ b/lib/l10n/intl_km.arb @@ -64,6 +64,7 @@ "containsErrorText": "ទិន្នន័យ​នេះត្រូវតែរួមបញ្ចូលជាមួយ {value}។", "betweenErrorText": "ទិន្នន័យ​នេះត្រូវតែនៅចន្លោះ {min} និង {max}។", "containsElementErrorText": "ទិន្នន័យ​នេះ​ត្រូវតែមាននៅក្នុងបញ្ជីតម្លៃដែលអនុញ្ញាតតែប៉ុណ្ណោះ។", + "doesNotContainElementErrorText": "តម្លៃមិនអាចមានក្នុងបញ្ជីបានទេ។", "ibanErrorText": "ទិន្នន័យនេះត្រូវតែជា IBAN តែប៉ុណ្ណោះ។", "uniqueErrorText": "ទិន្នន័យនេះត្រូវតែជាទិន្នន័យតែមួយ។", "bicErrorText": "ទិន្នន័យនេះត្រូវតែជា BIC តែប៉ុណ្ណោះ។", diff --git a/lib/l10n/intl_ko.arb b/lib/l10n/intl_ko.arb index 7645894c..48399315 100644 --- a/lib/l10n/intl_ko.arb +++ b/lib/l10n/intl_ko.arb @@ -64,6 +64,7 @@ "containsErrorText": "값은 {value}를 포함해야 합니다.", "betweenErrorText": "값은 {min}와 {max} 사이여야 합니다.", "containsElementErrorText": "값은 허용된 값 목록에 있어야 합니다.", + "doesNotContainElementErrorText": "값은 목록에 포함되지 않을 수 있습니다.", "ibanErrorText": "유효한 IBAN을 입력해 주세요.", "uniqueErrorText": "고유해야 합니다.", "bicErrorText": "유효한 BIC를 입력해 주세요.", diff --git a/lib/l10n/intl_ku.arb b/lib/l10n/intl_ku.arb index c40a13b7..3c1a52cd 100644 --- a/lib/l10n/intl_ku.arb +++ b/lib/l10n/intl_ku.arb @@ -64,6 +64,7 @@ "containsErrorText": "نرخ دەبێت تێبکەوێت {value}.", "betweenErrorText": "نرخ دەبێت لە نێوان {min} و {max} بێت.", "containsElementErrorText": "نرخ دەبێت لە لیست بێت.", + "doesNotContainElementErrorText": "Nirx nabe ku di lîsteyê de be.", "ibanErrorText": "نرخ دەبێت IBAN-ێکی دروست بێت.", "uniqueErrorText": "نرخ دەبێت یەکتر بێت.", "bicErrorText": "نرخ دەبێت BIC-ێکی دروست بێت.", diff --git a/lib/l10n/intl_lo.arb b/lib/l10n/intl_lo.arb index 0c144540..74bfa59a 100644 --- a/lib/l10n/intl_lo.arb +++ b/lib/l10n/intl_lo.arb @@ -64,6 +64,7 @@ "containsErrorText": "ຄ່າຕ້ອງປະກອບດ້ວຍ {value}.", "betweenErrorText": "ຄ່າຕ້ອງຢູ່ລະຫວ່າງ {min} ແລະ {max}.", "containsElementErrorText": "ຄ່າຕ້ອງຢູ່ໃນລາຍຊື່ຂອງມູນຄ່າທີ່ອະນຸຍາດ.", + "doesNotContainElementErrorText": "ຄ່າອາດຈະບໍ່ຢູ່ໃນລາຍການ.", "ibanErrorText": "ຄ່າໃນຟອມນີ້ຕ້ອງເປັນລະຫັດ IBAN ທີ່ຖືກຕ້ອງ.", "uniqueErrorText": "ຄ່າໃນຟອມນີ້ຕ້ອງມີຄ່າທີ່ບໍ່ດີທີ່ສຸດ.", "bicErrorText": "ຄ່າໃນຟອມນີ້ຕ້ອງເປັນລະຫັດ BIC ທີ່ຖືກຕ້ອງ.", diff --git a/lib/l10n/intl_lv.arb b/lib/l10n/intl_lv.arb index 117de1ac..6c4edf8a 100644 --- a/lib/l10n/intl_lv.arb +++ b/lib/l10n/intl_lv.arb @@ -64,6 +64,7 @@ "containsErrorText": "Vērtībai jāsatur {value}.", "betweenErrorText": "Vērtībai jābūt starp {min} un {max}.", "containsElementErrorText": "Vērtībai jābūt sarakstā.", + "doesNotContainElementErrorText": "Vērtība nedrīkst būt sarakstā.", "ibanErrorText": "Vērtībai jābūt derīgam IBAN.", "uniqueErrorText": "Vērtībai jābūt unikālai.", "bicErrorText": "Vērtībai jābūt derīgam BIC.", diff --git a/lib/l10n/intl_mn.arb b/lib/l10n/intl_mn.arb index f91f1646..4de48be2 100644 --- a/lib/l10n/intl_mn.arb +++ b/lib/l10n/intl_mn.arb @@ -64,6 +64,7 @@ "containsErrorText": "Утга нь {value}-г агуулсан байх ёстой.", "betweenErrorText": "Утга нь {min} ба {max} хооронд байх ёстой.", "containsElementErrorText": "Утга нь зөвшөөрөгдсөн утгуудын жагсаалтад байх ёстой.", + "doesNotContainElementErrorText": "Утга жагсаалтад байж болохгүй.", "ibanErrorText": "IBAN алдаатай байна.", "uniqueErrorText": "Утга давхардаж байна.", "bicErrorText": "BIC код алдаатай байна.", diff --git a/lib/l10n/intl_ms.arb b/lib/l10n/intl_ms.arb index ee067002..9c9a9c3d 100644 --- a/lib/l10n/intl_ms.arb +++ b/lib/l10n/intl_ms.arb @@ -64,6 +64,7 @@ "containsErrorText": "Nilai mesti mengandungi {value}.", "betweenErrorText": "Nilai mesti antara {min} dan {max}.", "containsElementErrorText": "Nilai mesti berada dalam senarai nilai yang dibenarkan.", + "doesNotContainElementErrorText": "Nilai tidak boleh berada dalam senarai.", "ibanErrorText": "Ruangan ini memerlukan IBAN yang sah.", "uniqueErrorText": "Ruangan ini memerlukan nilai yang unik.", "bicErrorText": "Ruangan ini memerlukan BIC yang sah.", diff --git a/lib/l10n/intl_ne.arb b/lib/l10n/intl_ne.arb index 01728a92..0a62586b 100644 --- a/lib/l10n/intl_ne.arb +++ b/lib/l10n/intl_ne.arb @@ -64,6 +64,7 @@ "containsErrorText": "मानले {value} समावेश गर्नुपर्छ।", "betweenErrorText": "मान {min} र {max} को बीचमा हुनुपर्छ।", "containsElementErrorText": "मान अनुमत मानहरूको सूचीमा हुनुपर्छ।", + "doesNotContainElementErrorText": "मान सूचीमा नहुन सक्छ।", "ibanErrorText": "यो क्षेत्रलाई मान्य IBAN चाहिन्छ।", "uniqueErrorText": "यो क्षेत्रलाई मान्य एकमात्र डाटा चाहिन्छ।", "bicErrorText": "यो क्षेत्रलाई मान्य BIC कोड चाहिन्छ।", diff --git a/lib/l10n/intl_nl.arb b/lib/l10n/intl_nl.arb index 30418dbd..3351c34c 100644 --- a/lib/l10n/intl_nl.arb +++ b/lib/l10n/intl_nl.arb @@ -64,6 +64,7 @@ "containsErrorText": "De waarde moet {value} bevatten.", "betweenErrorText": "De waarde moet tussen {min} en {max} liggen.", "containsElementErrorText": "De waarde moet in de lijst met toegestane waarden staan.", + "doesNotContainElementErrorText": "Waarde mag niet in de lijst staan.", "ibanErrorText": "Een geldige IBAN is vereist.", "uniqueErrorText": "De waarde moet uniek zijn.", "bicErrorText": "Een geldige BIC is vereist.", diff --git a/lib/l10n/intl_no.arb b/lib/l10n/intl_no.arb index 5688cc2a..604735aa 100644 --- a/lib/l10n/intl_no.arb +++ b/lib/l10n/intl_no.arb @@ -64,6 +64,7 @@ "containsErrorText": "Verdien må inneholde {value}.", "betweenErrorText": "Verdien må være mellom {min} og {max}.", "containsElementErrorText": "Verdien må være i listen over tillatte verdier.", + "doesNotContainElementErrorText": "Verdien kan ikke være i listen.", "ibanErrorText": "Dette feltet krever en gyldig IBAN.", "uniqueErrorText": "Dette feltet krever en unik verdi.", "bicErrorText": "Dette feltet krever en gyldig BIC.", diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index 70df2e71..e4a8f6dc 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -64,6 +64,7 @@ "containsErrorText": "Wartość musi zawierać {value}.", "betweenErrorText": "Wartość musi być pomiędzy {min} a {max}.", "containsElementErrorText": "Wartość musi być na liście dozwolonych wartości.", + "doesNotContainElementErrorText": "Wartość nie może znajdować się na liście.", "ibanErrorText": "To pole wymaga prawidłowego numeru IBAN.", "uniqueErrorText": "To pole wymaga unikalnej wartości.", "bicErrorText": "To pole wymaga prawidłowego numeru BIC.", diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb index 32d01356..aa2c873f 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -64,6 +64,7 @@ "containsErrorText": "O valor deve conter {value}.", "betweenErrorText": "O valor deve estar entre {min} e {max}.", "containsElementErrorText": "O valor deve estar na lista de valores permitidos.", + "doesNotContainElementErrorText": "O valor não pode estar na lista.", "ibanErrorText": "Este campo requer um IBAN válido.", "uniqueErrorText": "Este campo requer um valor único.", "bicErrorText": "Este campo requer um código BIC válido.", diff --git a/lib/l10n/intl_ro.arb b/lib/l10n/intl_ro.arb index fe641608..34f3ff47 100644 --- a/lib/l10n/intl_ro.arb +++ b/lib/l10n/intl_ro.arb @@ -64,6 +64,7 @@ "containsErrorText": "Valoarea trebuie să conțină {value}.", "betweenErrorText": "Valoarea trebuie să fie între {min} și {max}.", "containsElementErrorText": "Valoarea trebuie să fie în lista valorilor permise.", + "doesNotContainElementErrorText": "Valoarea nu poate fi în listă.", "ibanErrorText": "Acest câmp necesită un IBAN valid.", "uniqueErrorText": "Valoarea trebuie să fie unică.", "bicErrorText": "Acest câmp necesită un cod BIC valid.", diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index 9778b859..db6b37c4 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -64,6 +64,7 @@ "containsErrorText": "Значение должно содержать {value}.", "betweenErrorText": "Значение должно быть между {min} и {max}.", "containsElementErrorText": "Значение должно быть в списке допустимых значений.", + "doesNotContainElementErrorText": "Значение не может быть в списке.", "ibanErrorText": "Поле должно быть действительным IBAN номером.", "uniqueErrorText": "Поле должно быть уникальным.", "bicErrorText": "Поле должно быть действительным BIC кодом.", diff --git a/lib/l10n/intl_sk.arb b/lib/l10n/intl_sk.arb index 852e8362..64d97c11 100644 --- a/lib/l10n/intl_sk.arb +++ b/lib/l10n/intl_sk.arb @@ -64,6 +64,7 @@ "containsErrorText": "Hodnota musí obsahovať {value}.", "betweenErrorText": "Hodnota musí byť medzi {min} a {max}.", "containsElementErrorText": "Hodnota musí byť v zozname povolených hodnôt.", + "doesNotContainElementErrorText": "Hodnota nesmie byť v zozname.", "ibanErrorText": "Toto pole vyžaduje platné IBAN číslo.", "uniqueErrorText": "Toto pole vyžaduje unikátnu hodnotu.", "bicErrorText": "Toto pole vyžaduje platný BIC kód.", diff --git a/lib/l10n/intl_sl.arb b/lib/l10n/intl_sl.arb index 5a55c642..f7d70203 100644 --- a/lib/l10n/intl_sl.arb +++ b/lib/l10n/intl_sl.arb @@ -64,6 +64,7 @@ "containsErrorText": "Vrednost mora vsebovati {value}.", "betweenErrorText": "Vrednost mora biti med {min} in {max}.", "containsElementErrorText": "Vrednost mora biti v seznamu dovoljenih vrednosti.", + "doesNotContainElementErrorText": "Vrednost ne sme biti na seznamu.", "ibanErrorText": "Vnesite veljaven IBAN.", "uniqueErrorText": "Vrednost mora biti edinstvena.", "bicErrorText": "Vnesite veljaven BIC.", diff --git a/lib/l10n/intl_sq.arb b/lib/l10n/intl_sq.arb index e4b7cc62..6fe47b32 100644 --- a/lib/l10n/intl_sq.arb +++ b/lib/l10n/intl_sq.arb @@ -64,6 +64,7 @@ "containsErrorText": "Vlera duhet të përmbajë {value}.", "betweenErrorText": "Vlera duhet të jetë ndërmjet {min} dhe {max}.", "containsElementErrorText": "Vlera duhet të jetë në listën e vlefshme.", + "doesNotContainElementErrorText": "Vlera nuk mund të jetë në listë.", "ibanErrorText": "Kjo fushë kërkon një IBAN të vlefshëm.", "uniqueErrorText": "Kjo vlerë duhet të jetë unike.", "bicErrorText": "Kjo fushë kërkon një kod BIC të vlefshëm.", diff --git a/lib/l10n/intl_sv.arb b/lib/l10n/intl_sv.arb index c0ee955b..fd5bdb51 100644 --- a/lib/l10n/intl_sv.arb +++ b/lib/l10n/intl_sv.arb @@ -64,6 +64,7 @@ "containsErrorText": "Värdet måste innehålla {value}.", "betweenErrorText": "Värdet måste vara mellan {min} och {max}.", "containsElementErrorText": "Värdet måste vara med i listan över tillåtna värden.", + "doesNotContainElementErrorText": "Värdet får inte finnas i listan.", "ibanErrorText": "Detta fält kräver ett giltigt IBAN-nummer.", "uniqueErrorText": "Detta fält kräver ett unikt värde.", "bicErrorText": "Detta fält kräver ett giltigt BIC-nummer.", diff --git a/lib/l10n/intl_sw.arb b/lib/l10n/intl_sw.arb index 93a4d170..bb511179 100644 --- a/lib/l10n/intl_sw.arb +++ b/lib/l10n/intl_sw.arb @@ -64,6 +64,7 @@ "containsErrorText": "Thamani lazima iwe na {value}.", "betweenErrorText": "Thamani lazima iwe kati ya {min} na {max}.", "containsElementErrorText": "Thamani lazima iwe kwenye orodha ya thamani zinazokubalika.", + "doesNotContainElementErrorText": "Thamani haiwezi kuwa kwenye orodha.", "ibanErrorText": "Sehemu hii inahitaji IBAN halali.", "uniqueErrorText": "Thamani lazima iwe ya kipekee.", "bicErrorText": "Sehemu hii inahitaji BIC halali.", diff --git a/lib/l10n/intl_ta.arb b/lib/l10n/intl_ta.arb index 11f35ad7..506221e3 100644 --- a/lib/l10n/intl_ta.arb +++ b/lib/l10n/intl_ta.arb @@ -64,6 +64,7 @@ "containsErrorText": "மதிப்பு {value} ஐ கொண்டிருக்க வேண்டும்.", "betweenErrorText": "மதிப்பு {min} மற்றும் {max} இடையில் இருக்க வேண்டும்.", "containsElementErrorText": "மதிப்பு அனுமதிக்கப்பட்ட மதிப்புகளின் பட்டியலில் இருக்க வேண்டும்.", + "doesNotContainElementErrorText": "மதிப்பு பட்டியலில் இருக்கக்கூடாது.", "ibanErrorText": "இந்த உள்ளீட்டுக்கு சரியான ஐபான் தேவை.", "uniqueErrorText": "இந்த உள்ளீடு அதிகமான மதிப்புகளை கொண்டிருக்க வேண்டும்.", "bicErrorText": "இந்த உள்ளீட்டுக்கு சரியான BIC குறியீடு தேவை.", diff --git a/lib/l10n/intl_th.arb b/lib/l10n/intl_th.arb index ebcd0e48..e3c0e2c7 100644 --- a/lib/l10n/intl_th.arb +++ b/lib/l10n/intl_th.arb @@ -64,6 +64,7 @@ "containsErrorText": "ข้อมูลนี้ต้องประกอบด้วย {value}", "betweenErrorText": "ข้อมูลนี้ต้องอยู่ระหว่าง {min} และ {max}", "containsElementErrorText": "ข้อมูลนี้ต้องอยู่ในรายการค่าที่อนุญาต", + "doesNotContainElementErrorText": "ค่าต้องไม่อยู่ในรายการ", "ibanErrorText": "ข้อมูลนี้ต้องเป็น IBAN ที่ถูกต้อง", "uniqueErrorText": "ข้อมูลนี้ต้องเป็นค่าที่ไม่ซ้ำกัน", "bicErrorText": "ข้อมูลนี้ต้องเป็นรหัส BIC ที่ถูกต้อง", diff --git a/lib/l10n/intl_tr.arb b/lib/l10n/intl_tr.arb index 711ca988..ed82ba32 100644 --- a/lib/l10n/intl_tr.arb +++ b/lib/l10n/intl_tr.arb @@ -64,6 +64,7 @@ "containsErrorText": "Değer {value} içermelidir.", "betweenErrorText": "Değer {min} ile {max} arasında olmalıdır.", "containsElementErrorText": "Değer izin verilen değerler listesinde olmalıdır.", + "doesNotContainElementErrorText": "Değer listede olmayabilir.", "ibanErrorText": "Bu alan geçerli bir IBAN gerektirir.", "uniqueErrorText": "Bu alanın değeri benzersiz olmalıdır.", "bicErrorText": "Bu alan geçerli bir BIC gerektirir.", diff --git a/lib/l10n/intl_uk.arb b/lib/l10n/intl_uk.arb index e3fa6322..39c1be28 100644 --- a/lib/l10n/intl_uk.arb +++ b/lib/l10n/intl_uk.arb @@ -64,6 +64,7 @@ "containsErrorText": "Значення має містити {value}.", "betweenErrorText": "Значення має бути між {min} і {max}.", "containsElementErrorText": "Значення має бути в списку дозволених значень.", + "doesNotContainElementErrorText": "Значення не може бути у списку.", "ibanErrorText": "Поле має бути дійсним IBAN номером.", "uniqueErrorText": "Поле має бути унікальним.", "bicErrorText": "Поле має бути дійсним BIC кодом.", diff --git a/lib/l10n/intl_vi.arb b/lib/l10n/intl_vi.arb index 8028309f..b47f073a 100644 --- a/lib/l10n/intl_vi.arb +++ b/lib/l10n/intl_vi.arb @@ -64,6 +64,7 @@ "containsErrorText": "Giá trị phải chứa {value}.", "betweenErrorText": "Giá trị phải nằm trong khoảng từ {min} đến {max}.", "containsElementErrorText": "Giá trị phải nằm trong danh sách các giá trị hợp lệ.", + "doesNotContainElementErrorText": "Giá trị không được có trong danh sách.", "ibanErrorText": "Yêu cầu nhập đúng số IBAN.", "uniqueErrorText": "Yêu cầu giá trị duy nhất.", "bicErrorText": "Yêu cầu nhập đúng mã BIC.", diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 91c2a9dd..ebed8879 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -64,6 +64,7 @@ "containsErrorText": "值必须包含 {value}。", "betweenErrorText": "值必须在 {min} 和 {max} 之间。", "containsElementErrorText": "值必须在允许的值列表中。", + "doesNotContainElementErrorText": "值不能在列表中。", "ibanErrorText": "该字段必须是有效的 IBAN。", "uniqueErrorText": "该字段必须是唯一的。", "bicErrorText": "该字段必须是有效的 BIC。", diff --git a/lib/l10n/intl_zh_Hant.arb b/lib/l10n/intl_zh_Hant.arb index 5f541606..081c735f 100644 --- a/lib/l10n/intl_zh_Hant.arb +++ b/lib/l10n/intl_zh_Hant.arb @@ -64,6 +64,7 @@ "containsErrorText": "值必須包含 {value}。", "betweenErrorText": "值必須在 {min} 和 {max} 之間。", "containsElementErrorText": "值必須在允許的值列表中。", + "doesNotContainElementErrorText": "值不能在列表中。", "ibanErrorText": "此欄位需要有效的 IBAN。", "uniqueErrorText": "此欄位需要唯一的值。", "bicErrorText": "此欄位需要有效的 BIC 編碼。", diff --git a/lib/src/form_builder_validators.dart b/lib/src/form_builder_validators.dart index d24dcbba..ff73b1a7 100644 --- a/lib/src/form_builder_validators.dart +++ b/lib/src/form_builder_validators.dart @@ -1,4 +1,7 @@ // coverage:ignore-file +import 'dart:core' as c; +import 'dart:core'; + import 'package:flutter/widgets.dart'; import '../form_builder_validators.dart'; @@ -1738,6 +1741,8 @@ class FormBuilderValidators { ).validate; } +//********************************* NEW API ******************************************************************************** + /// A class that is used as an aggregator/namespace for all the available /// validators in this package. final class Validators { @@ -1788,7 +1793,7 @@ final class Validators { String prefix = '', String suffix = '', String? separator, - bool printErrorAsSoonAsPossible = true, + c.bool printErrorAsSoonAsPossible = true, }) => val.and(validators, prefix: prefix, @@ -1868,7 +1873,7 @@ final class Validators { /// - `T`: Type of value being validated, may be a nullable Object /// {@endtemplate} static Validator validateIf( - bool Function(T value) condition, Validator v) => + c.bool Function(T value) condition, Validator v) => val.validateIf(condition, v); /// {@template validator_skip_if} @@ -1898,7 +1903,7 @@ final class Validators { /// - `T`: Type of value being validated, may be a nullable Object /// {@endtemplate} static Validator skipIf( - bool Function(T value) condition, Validator v) => + c.bool Function(T value) condition, Validator v) => val.skipIf(condition, v); // Debug print validator @@ -1930,7 +1935,7 @@ final class Validators { val.debugPrintValidator(next: next, logOnInput: logOnInput); // Equality validators - /// {@template validator_is_equal} + /// {@template validator_equal} /// Creates a validator that checks if a given input matches `referenceValue` /// using the equality (`==`) operator. /// @@ -1938,7 +1943,7 @@ final class Validators { /// ## Parameters /// - `referenceValue` (`T`): The value to compare against the input. This serves as /// the reference for equality checking. - /// - `isEqualMsg` (`String Function(T input, T referenceValue)?`): Optional + /// - `equalMsg` (`String Function(T input, T referenceValue)?`): Optional /// custom error message generator. Takes the `input` and the `referenceValue` /// as parameters and returns a custom error message. /// @@ -1955,14 +1960,14 @@ final class Validators { /// ## Examples /// ```dart /// // Basic usage for password confirmation - /// final confirmAction = isEqual('Type this to confirm the action'); + /// final confirmAction = Validator.equal('Type this to confirm the action'); /// assert(confirmAction('Type this to confirm the action') == null); // null returned (validation passes) /// assert(confirmAction(12345) != null); // Error message returned /// /// // Using custom error message - /// final specificValueValidator = isEqual( + /// final specificValueValidator = Validator.equal( /// 42, - /// isEqualMsg: (_, value) => 'Value must be exactly $value', + /// equalMsg: (_, value) => 'Value must be exactly $value', /// ); /// ``` /// @@ -1972,20 +1977,20 @@ final class Validators { /// - The error message uses the string representation of the value via /// `toString()`, which might not be ideal for all types. /// {@endtemplate} - static Validator isEqual( + static Validator equal( T value, { - String Function(T input, T referenceValue)? isEqualMsg, + String Function(T input, T referenceValue)? equalMsg, }) => - val.isEqual(value, isEqualMsg: isEqualMsg); + val.equal(value, equalMsg: equalMsg); - /// {@template validator_is_not_equal} + /// {@template validator_not_equal} /// Creates a validator that checks if a given input is not equal to /// `referenceValue` using the not-equal (`!=`) operator. /// /// ## Parameters /// - `referenceValue` (`T`): The reference value to compare against. Input must /// not equal this value to pass validation. - /// - `isNotEqualMsg` (`String Function(T input, T referenceValue)?`): Optional + /// - `notEqualMsg` (`String Function(T input, T referenceValue)?`): Optional /// custom error message generator. Takes the `input` and the `referenceValue` /// as parameters and returns a custom error message. /// @@ -2001,14 +2006,14 @@ final class Validators { /// ## Examples /// ```dart /// // Basic usage with strings - /// final validator = isNotEqual('reserved'); + /// final validator = Validators.notEqual('reserved'); /// assert(validator('not-reserved') == null); // null (validation passes) /// assert(validator('reserved') != null); // "Value must not be equal to reserved" /// /// // Custom error message - /// final customValidator = isNotEqual( + /// final customValidator = Validators.notEqual( /// 42, - /// isNotEqualMsg: (_, value) => 'Please choose a number other than $value', + /// notEqualMsg: (_, value) => 'Please choose a number other than $value', /// ); /// ``` /// @@ -2018,14 +2023,14 @@ final class Validators { /// - The error message uses the string representation of the value via /// `toString()`, which might not be ideal for all types /// {@endtemplate} - static Validator isNotEqual( + static Validator notEqual( T value, { - String Function(T input, T referenceValue)? isNotEqualMsg, + String Function(T input, T referenceValue)? notEqualMsg, }) => - val.isNotEqual(value, isNotEqualMsg: isNotEqualMsg); + val.notEqual(value, notEqualMsg: notEqualMsg); // Required validators - /// {@template validator_is_required} + /// {@template validator_required} /// Generates a validator function that enforces required field validation for /// form inputs. This validator ensures that a field has a non-null, non-empty /// value before any subsequent validation is performed. @@ -2040,7 +2045,7 @@ final class Validators { /// - `next` (`Validator?`): An optional subsequent validator function that /// will be applied after the required validation passes. This allows for /// chaining multiple validation rules. - /// - `isRequiredMsg` (`String?`): An optional custom error message to display + /// - `requiredMsg` (`String?`): An optional custom error message to display /// when the field is empty or null. If not provided, defaults to the /// localized required field error text. /// @@ -2053,13 +2058,13 @@ final class Validators { /// ## Examples /// ```dart /// // Basic required field validation - /// final validator = isRequired(); - /// print(validator(null)); // Returns localized error message - /// print(validator('')); // Returns localized error message - /// print(validator('value')); // Returns null (validation passed) + /// final validator = Validators.required(); + /// assert(validator(null) != null); // Returns localized error message + /// assert(validator('') != null); // Returns localized error message + /// assert(validator('value') == null); // Returns null (validation passed) /// /// // Chaining with another validator - /// final complexValidator = isRequired( + /// final complexValidator = Validators.required( /// (value) => value.length < 10 ? 'Too long' : null, /// 'Custom required message' /// ); @@ -2069,60 +2074,13 @@ final class Validators { /// - The validator assumes empty strings/maps/iterables, white strings, and null /// values are equivalent for validation purposes /// {@endtemplate} - static Validator isRequired([ + static Validator required([ Validator? next, - String? isRequiredMsg, + String? requiredMsg, ]) => - val.isRequired(next, isRequiredMsg); - - /// {@template validator_validate_with_default} - /// Creates a validator function that applies a default value before validation, - /// making sure the `next` validator will always receive a non-null input. - /// - /// This function generates a new validator that first replaces null input - /// with a specified default value, then applies `next` validator. - /// - /// ## Type Parameters - /// - `T`: The non-nullable version of the type of the input being validated. - /// It must extend from `Object`. - /// - /// ## Parameters - /// - `defaultValue` (`T`): The fallback non-null value to use when input is null. - /// - `next` (`Validator`): The validation function to apply after the default - /// value has been potentially substituted. - /// - /// ## Returns - /// Returns a new `Validator` function that accepts nullable input and - /// produces validation results based on the combined default value substitution - /// and validation logic. The returned validator is a function that: - /// - Returns null if the value, potentially replaced with the default, passes - /// the `next` validation - /// - Returns an error message string if validation fails - /// - /// ## Examples - /// ```dart - /// // Create a validator that requires a minimum length of 3 - /// final minLength = (String value) => - /// value.length >= 3 ? null : 'Must be at least 3 characters'; - /// - /// // Wrap it with a default value of 'N/A' - /// final defaultValue = 'default value'; - /// final validator = validateWithDefault('N/A', minLength); - /// - /// print(validator(null)); // Returns null (valid) - /// print(validator('ab')); // Returns 'Must be at least 3 characters' - /// print(validator('abc')); // Returns null (valid) - /// // Equivalent to: - /// print(minLength(null ?? defaultValue)); // Returns null (valid) - /// print(minLength('ab' ?? defaultValue)); // Returns 'Must be at least 3 characters' - /// print(minLength('abc' ?? defaultValue)); // Returns null (valid) - /// ``` - /// {@endtemplate} - static Validator validateWithDefault( - T defaultValue, Validator next) => - val.validateWithDefault(defaultValue, next); + val.required(next, requiredMsg); - /// {@template validator_is_optional} + /// {@template validator_optional} /// Creates a validator function that makes a field optional while allowing additional validation /// rules. This validator is particularly useful in form validation scenarios where certain /// fields are not mandatory but still need to conform to specific rules when provided. @@ -2141,7 +2099,7 @@ final class Validators { /// - `next` (`Validator?`): An optional subsequent validator function that will be /// applied only if the input value is provided (non-null and non-empty). This allows /// for chaining validation rules. - /// - `isOptionalMsg` (`String Function(T input, String nextErrorMessage)?`): An + /// - `optionalMsg` (`String Function(T input, String nextErrorMessage)?`): An /// optional error message that takes the `input` and the `nextErrorMessage` as /// parameters and returns the custom error message. /// @@ -2155,29 +2113,76 @@ final class Validators { /// ## Examples /// ```dart /// // Basic optional string validator - /// final validator = isOptional(); + /// final validator = Validators.optional(); /// /// // Optional validator with additional email validation - /// final emailValidator = isOptional( + /// final emailValidator = Validators.optional( /// validateEmail, /// (_, error) => 'Invalid email format: $error', /// ); /// /// // Usage with different inputs - /// print(validator(null)); // Returns: null (valid) - /// print(validator('')); // Returns: null (valid) - /// print(emailValidator('invalid@email')); // Returns: error message + /// assert(validator(null) == null); // Returns: null (valid) + /// assert(validator('') == null); // Returns: null (valid) + /// assert(emailValidator('invalid@email') != null); // Returns: error message /// ``` /// /// ## Caveats /// - The validator assumes empty strings/maps/iterables, white strings, and null values are /// equivalent for validation purposes, all them are considered valid. /// {@endtemplate} - static Validator isOptional([ + static Validator optional([ Validator? next, - String Function(T input, String nextErrorMsg)? isOptionalMsg, + String Function(T input, String nextErrorMsg)? optionalMsg, ]) => - val.isOptional(next, isOptionalMsg); + val.optional(next, optionalMsg); + + /// {@template validator_validate_with_default} + /// Creates a validator function that applies a default value before validation, + /// making sure the `next` validator will always receive a non-null input. + /// + /// This function generates a new validator that first replaces null input + /// with a specified default value, then applies `next` validator. + /// + /// ## Type Parameters + /// - `T`: The non-nullable version of the type of the input being validated. + /// It must extend from `Object`. + /// + /// ## Parameters + /// - `defaultValue` (`T`): The fallback non-null value to use when input is null. + /// - `next` (`Validator`): The validation function to apply after the default + /// value has been potentially substituted. + /// + /// ## Returns + /// Returns a new `Validator` function that accepts nullable input and + /// produces validation results based on the combined default value substitution + /// and validation logic. The returned validator is a function that: + /// - Returns null if the value, potentially replaced with the default, passes + /// the `next` validation + /// - Returns an error message string if validation fails + /// + /// ## Examples + /// ```dart + /// // Create a validator that requires a minimum length of 3 + /// final minLength = (String value) => + /// value.length >= 3 ? null : 'Must be at least 3 characters'; + /// + /// // Wrap it with a default value of 'N/A' + /// final defaultValue = 'default value'; + /// final validator = Validators.validateWithDefault('N/A', minLength); + /// + /// assert(validator(null) == null); // Returns null (valid) + /// assert(validator('ab') != null); // Returns 'Must be at least 3 characters' + /// assert(validator('abc') == null); // Returns null (valid) + /// // Equivalent to: + /// assert(minLength(null ?? defaultValue) == null); // Returns null (valid) + /// assert(minLength('ab' ?? defaultValue) != null); // Returns 'Must be at least 3 characters' + /// assert(minLength('abc' ?? defaultValue) == null); // Returns null (valid) + /// ``` + /// {@endtemplate} + static Validator validateWithDefault( + T defaultValue, Validator next) => + val.validateWithDefault(defaultValue, next); // Transform Validator @@ -2245,7 +2250,7 @@ final class Validators { ); // Type Validator - /// {@template validator_is_string} + /// {@template validator_string} /// Creates a validator that verifies if an input value is a [String]. If the /// check succeeds, the transformed value will be passed to the `next` /// validator. @@ -2257,7 +2262,7 @@ final class Validators { /// - `next` (`Validator?`): An optional subsequent validator that processes /// the input after successful string validation. Receives the validated input /// as a [String]. - /// - `isStringMsg` (`String Function(T input)?`): An optional custom error message + /// - `stringMsg` (`String Function(T input)?`): An optional custom error message /// generator function that takes the input as parameter and returns a customized error /// message. /// @@ -2270,18 +2275,18 @@ final class Validators { /// ## Examples /// ```dart /// // Basic string validation - /// final validator = isString(); + /// final validator = Validators.string(); /// print(validator('valid string')); // null /// print(validator(123)); // 'Must be a string' /// /// // With custom error message - /// final customValidator = isString( - /// isStringMsg: (input) => '${input.toString()} is not a valid String.', + /// final customValidator = Validators.string( + /// stringMsg: (input) => '${input.toString()} is not a valid String.', /// ); /// print(customValidator(42)); // '42 is not a valid string' /// /// // Chaining validators - /// final chainedValidator = isString( + /// final chainedValidator = Validators.string( /// (value) => value.isEmpty ? 'String cannot be empty' : null, /// ); /// print(chainedValidator('')); // 'String cannot be empty' @@ -2293,13 +2298,13 @@ final class Validators { /// example, if the input is a number, it will never transform it to the string /// version by calling `toString` method. /// {@endtemplate} - static Validator isString([ + static Validator string([ Validator? next, - String Function(T input)? isStringMsg, + String Function(T input)? stringMsg, ]) => - val.isString(next, isStringMsg); + val.string(next, stringMsg); - /// {@template validator_is_int} + /// {@template validator_int} /// Creates a validator that verifies if an input value is an [int] or can be /// parsed into an [int]. If the check succeeds, the transformed value will be /// passed to the `next` validator. @@ -2315,7 +2320,7 @@ final class Validators { /// ## Parameters /// - `next` (`Validator?`): An optional subsequent validator that receives /// the converted integer value for additional validation - /// - `isIntMsg` (`String Function(T input)?`): Optional custom error message + /// - `intMsg` (`String Function(T input)?`): Optional custom error message /// generator function that receives the invalid input and returns an error /// message /// @@ -2329,17 +2334,17 @@ final class Validators { /// ## Examples /// ```dart /// // Basic integer validation - /// final validator = isInt(); + /// final validator = Validators.int(); /// print(validator(42)); // null (valid) /// print(validator('123')); // null (valid) /// print(validator('abc')); // 'This field requires a valid integer.' /// /// // With custom error message - /// final customValidator = isInt(null, (input) => 'Custom error for: $input'); + /// final customValidator = Validators.int(null, (input) => 'Custom error for: $input'); /// print(customValidator('abc')); // 'Custom error for: abc' /// /// // With chained validation - /// final rangeValidator = isInt((value) => + /// final rangeValidator = Validators.int((value) => /// value > 100 ? 'Must be less than 100' : null); /// print(rangeValidator('150')); // 'Must be less than 100' /// ``` @@ -2347,13 +2352,13 @@ final class Validators { /// ## Caveats /// - If the input is [String], it will be parsed by the [int.tryParse] method. /// {@endtemplate} - static Validator isInt([ - Validator? next, - String Function(T input)? isIntMsg, + static Validator int([ + Validator? next, + String Function(T input)? intMsg, ]) => - val.isInt(next, isIntMsg); + val.isInt(next, intMsg); - /// {@template validator_is_double} + /// {@template validator_double} /// Creates a validator that verifies if an input value is a [double] or can be /// parsed into a [double]. If the check succeeds, the transformed value will be /// passed to the `next` validator. @@ -2369,7 +2374,7 @@ final class Validators { /// ## Parameters /// - `next` (`Validator?`): An optional subsequent validator that receives /// the converted numeric value for additional validation - /// - `isDoubleMsg` (`String Function(T input)?`): Optional custom error message + /// - `doubleMsg` (`String Function(T input)?`): Optional custom error message /// generator function that receives the invalid input and returns an error /// message /// @@ -2383,7 +2388,7 @@ final class Validators { /// ## Examples /// ```dart /// // Basic number validation - /// final validator = isDouble(); + /// final validator = Validators.double(); /// print(validator(42.0)); // null (valid) /// print(validator(3.14)); // null (valid) /// print(validator('123.45')); // null (valid) @@ -2391,11 +2396,11 @@ final class Validators { /// print(validator('abc')); // 'Please enter a valid number' /// /// // With custom error message - /// final customValidator = isDouble(null, (input) => 'Invalid number: $input'); + /// final customValidator = Validators.double(null, (input) => 'Invalid number: $input'); /// print(customValidator('abc')); // 'Invalid number: abc' /// /// // With chained validation - /// final rangeValidator = isDouble((value) => + /// final rangeValidator = Validators.double((value) => /// value > 1000 ? 'Must be less than 1000' : null); /// print(rangeValidator('1500')); // 'Must be less than 1000' /// ``` @@ -2403,13 +2408,13 @@ final class Validators { /// ## Caveats /// - If the input is [String], it will be parsed by the [double.tryParse] method. /// {@endtemplate} - static Validator isDouble([ - Validator? next, - String Function(T input)? isDoubleMsg, + static Validator double([ + Validator? next, + String Function(T input)? doubleMsg, ]) => - val.isDouble(next, isDoubleMsg); + val.isDouble(next, doubleMsg); - /// {@template validator_is_num} + /// {@template validator_num} /// Creates a validator that verifies if an input value is a [num] or can be /// parsed into a [num]. If the check succeeds, the transformed value will be /// passed to the `next` validator. @@ -2425,7 +2430,7 @@ final class Validators { /// ## Parameters /// - `next` (`Validator?`): An optional subsequent validator that receives /// the converted numeric value for additional validation - /// - `isNumMsg` (`String Function(T input)?`): Optional custom error message + /// - `numMsg` (`String Function(T input)?`): Optional custom error message /// generator function that receives the invalid input and returns an error /// message /// @@ -2439,7 +2444,7 @@ final class Validators { /// ## Examples /// ```dart /// // Basic number validation - /// final validator = isNum(); + /// final validator = Validators.num(); /// print(validator(42)); // null (valid) /// print(validator(3.14)); // null (valid) /// print(validator('123.45')); // null (valid) @@ -2447,11 +2452,11 @@ final class Validators { /// print(validator('abc')); // 'Please enter a valid number' /// /// // With custom error message - /// final customValidator = isNum(null, (input) => 'Invalid number: $input'); + /// final customValidator = Validators.num(null, (input) => 'Invalid number: $input'); /// print(customValidator('abc')); // 'Invalid number: abc' /// /// // With chained validation - /// final rangeValidator = isNum((value) => + /// final rangeValidator = Validators.num((value) => /// value > 1000 ? 'Must be less than 1000' : null); /// print(rangeValidator('1500')); // 'Must be less than 1000' /// ``` @@ -2459,13 +2464,13 @@ final class Validators { /// ## Caveats /// - If the input is [String], it will be parsed by the [num.tryParse] method. /// {@endtemplate} - static Validator isNum([ - Validator? next, - String Function(T input)? isNumMsg, + static Validator num([ + Validator? next, + String Function(T input)? numMsg, ]) => - val.isNum(next, isNumMsg); + val.isNum(next, numMsg); - /// {@template validator_is_bool} + /// {@template validator_bool} /// Creates a validator that verifies if an input value is a [bool] or can be /// parsed into a [bool]. If the check succeeds, the transformed value will be /// passed to the `next` validator. @@ -2481,7 +2486,7 @@ final class Validators { /// ## Parameters /// - `next` (`Validator?`): An optional subsequent validator that receives /// the converted boolean value for additional validation - /// - `isBoolMsg` (`String Function(T input)?`): Optional custom error message + /// - `boolMsg` (`String Function(T input)?`): Optional custom error message /// generator function that receives the invalid input and returns an error /// message /// - `caseSensitive` (`bool`): Controls whether string parsing is case-sensitive. @@ -2501,7 +2506,7 @@ final class Validators { /// ## Examples /// ```dart /// // Basic boolean validation - /// final validator = isBool(); + /// final validator = Validators.bool(); /// print(validator(true)); // null (valid) /// print(validator('true')); // null (valid) /// print(validator('TRUE')); // null (valid) @@ -2509,20 +2514,20 @@ final class Validators { /// print(validator('abc')); // 'This field requires a valid boolean (true or false).' /// /// // With case sensitivity - /// final strictValidator = isBool(null, null, true); + /// final strictValidator = Validators.bool(null, null, true); /// print(strictValidator('True')); // 'This field requires a valid boolean (true or false).' /// print(strictValidator('true')); // null (valid) /// /// // Without trimming - /// final noTrimValidator = isBool(null, null, false, false); + /// final noTrimValidator = Validators.bool(null, null, false, false); /// print(noTrimValidator(' true')); // 'This field requires a valid boolean (true or false).' /// /// // With custom error message - /// final customValidator = isBool(null, (input) => 'Invalid boolean: $input'); + /// final customValidator = Validators.bool(null, (input) => 'Invalid boolean: $input'); /// print(customValidator('abc')); // 'Invalid boolean: abc' /// /// // With chained validation - /// final customValidator = isBool((value) => + /// final customValidator = Validators.bool((value) => /// value == true ? 'Must be false' : null); /// print(customValidator('true')); // 'Must be false' /// ``` @@ -2530,14 +2535,14 @@ final class Validators { /// ## Caveats /// - If the input is [String], it will be parsed by the [bool.tryParse] method /// {@endtemplate} - static Validator isBool( - [Validator? next, - String Function(T input)? isBoolMsg, - bool caseSensitive = false, - bool trim = true]) => - val.isBool(next, isBoolMsg, caseSensitive, trim); - - /// {@template validator_is_date_time} + static Validator bool( + [Validator? next, + String Function(T input)? boolMsg, + c.bool caseSensitive = false, + c.bool trim = true]) => + val.isBool(next, boolMsg, caseSensitive, trim); + + /// {@template validator_date_time} /// Creates a validator that verifies if an input value is a [DateTime] or can be /// parsed into a [DateTime]. If the check succeeds, the transformed value will be /// passed to the `next` validator. @@ -2553,7 +2558,7 @@ final class Validators { /// ## Parameters /// - `next` (`Validator?`): An optional subsequent validator that /// receives the converted datetime value for additional validation - /// - `isDateTimeMsg` (`String Function(T input)?`): Optional custom error message + /// - `dateTimeMsg` (`String Function(T input)?`): Optional custom error message /// generator function that receives the invalid input and returns an error /// message /// @@ -2567,21 +2572,21 @@ final class Validators { /// ## Examples /// ```dart /// // Basic datetime validation - /// final validator = isDateTime(); + /// final validator = Validators.dateTime(); /// print(validator(DateTime.now())); // null (valid) /// print(validator('2024-12-31')); // null (valid) /// print(validator('2024-12-31T23:59:59')); // null (valid) /// print(validator('not a date')); // 'This field requires a valid datetime.' /// /// // With custom error message - /// final customValidator = isDateTime( + /// final customValidator = Validators.dateTime( /// null, /// (input) => 'Invalid date format: $input' /// ); /// print(customValidator('abc')); // 'Invalid date format: abc' /// /// // With chained validation - /// final futureValidator = isDateTime((value) => + /// final futureValidator = Validators.dateTime((value) => /// value.isBefore(DateTime.now()) ? 'Date must be in the future' : null); /// print(futureValidator('2020-01-01')); // 'Date must be in the future' /// ``` @@ -2591,11 +2596,11 @@ final class Validators { /// - The function parses a subset of ISO 8601, which includes the subset /// accepted by RFC 3339. /// {@endtemplate} - static Validator isDateTime([ + static Validator dateTime([ Validator? next, - String Function(T input)? isDateTimeMsg, + String Function(T input)? dateTimeMsg, ]) => - val.isDateTime(next, isDateTimeMsg); + val.dateTime(next, dateTimeMsg); // Path validators /// {@template validator_matches_allowed_extensions} @@ -2649,7 +2654,7 @@ final class Validators { static Validator matchesAllowedExtensions( List extensions, { String Function(List)? matchesAllowedExtensionsMsg, - bool caseSensitive = true, + c.bool caseSensitive = true, }) => val.matchesAllowedExtensions( extensions, @@ -2659,56 +2664,6 @@ final class Validators { // String validators - /// {@template validator_uuid} - /// A validator function that checks if a given string matches the UUID format. - /// - /// Creates a validator that ensures the input string conforms to the standard - /// UUID (Universally Unique Identifier) format, consisting of 32 hexadecimal - /// digits displayed in 5 groups separated by hyphens (8-4-4-4-12). - /// - /// ## Parameters - /// - `regex` (`RegExp?`): Optional custom regular expression pattern to override - /// the default UUID validation pattern. Useful for supporting different UUID - /// formats or adding additional constraints. - /// - `uuidMsg` (`String Function(String input)?`): Optional callback function that - /// generates a custom error message based on the invalid input. If not provided, - /// defaults to the standard form builder localization error text. - /// - /// ## Returns - /// Returns a `Validator` function that accepts a string input and returns: - /// - `null` if the input is valid - /// - An error message string if the input is invalid - /// - /// ## Examples - /// ```dart - /// // Using default UUID validation - /// final validator = uuid(); - /// print(validator('123e4567-e89b-12d3-a456-426614174000')); // null - /// print(validator('invalid-uuid')); // Returns error message - /// - /// // Using custom error message - /// final customValidator = uuid( - /// uuidMsg: (input) => 'Invalid UUID format: $input', - /// ); - /// - /// // Using custom regex pattern - /// final customPatternValidator = uuid( - /// regex: RegExp(r'^[0-9]{8}-[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{12}$'), - /// ); - /// ``` - /// - /// ## Caveats - /// - The default regex pattern accepts both uppercase and lowercase hexadecimal - /// digits (0-9, a-f, A-F) - /// - The validation only checks the format, not the actual UUID version or - /// variant compliance - /// {@endtemplate} - static Validator uuid({ - RegExp? regex, - String Function(String input)? uuidMsg, - }) => - val.uuid(regex: regex, uuidMsg: uuidMsg); - /// {@template validator_contains} /// Creates a validator function that checks if a string contains a specific /// substring. The validation can be performed with or without case sensitivity. @@ -2752,7 +2707,7 @@ final class Validators { /// {@endtemplate} static Validator contains( String substring, { - bool caseSensitive = true, + c.bool caseSensitive = true, String Function(String substring, String input)? containsMsg, }) => val.contains( @@ -2812,9 +2767,9 @@ final class Validators { /// be provided for special language requirements /// {@endtemplate} static Validator hasMinUppercaseChars({ - int min = 1, - int Function(String)? customUppercaseCounter, - String Function(String input, int min)? hasMinUppercaseCharsMsg, + c.int min = 1, + c.int Function(String input)? customUppercaseCounter, + String Function(String input, c.int min)? hasMinUppercaseCharsMsg, }) => val.hasMinUppercaseChars( min: min, @@ -2834,9 +2789,9 @@ final class Validators { /// ## Parameters /// - `min` (`int`): The minimum number of lowercase characters required. Defaults /// to 1. - /// - `customLowercaseCounter` (`int Function(String)?`): Optional custom function - /// to count lowercase characters. If not provided, uses a default Unicode-based - /// counter. + /// - `customLowercaseCounter` (`int Function(String input)?`): Optional custom function + /// to count lowercase characters. It receives the user input as parameter. + /// If not provided, uses a default Unicode-based counter. /// - `hasMinLowercaseCharsMsg` (`String Function(String input, int min)?`): /// Optional function to generate custom error messages. Receives the input and /// the minimum lowercase count required and returns an error message string. @@ -2873,9 +2828,9 @@ final class Validators { /// be provided for special language requirements /// {@endtemplate} static Validator hasMinLowercaseChars({ - int min = 1, - int Function(String)? customLowercaseCounter, - String Function(String input, int min)? hasMinLowercaseCharsMsg, + c.int min = 1, + c.int Function(String input)? customLowercaseCounter, + String Function(String input, c.int min)? hasMinLowercaseCharsMsg, }) => val.hasMinLowercaseChars( min: min, @@ -2895,7 +2850,7 @@ final class Validators { /// ## Parameters /// - `min` (`int`): The minimum number of numeric characters required. Defaults /// to 1. - /// - `customNumericCounter` (`int Function(String)?`): Optional custom function + /// - `customNumericCounter` (`int Function(String input)?`): Optional custom function /// to count numeric characters. If not provided, uses a default regex-based /// counter matching digits 0-9. /// - `hasMinNumericCharsMsg` (`String Function(String input, int min)?`): @@ -2939,9 +2894,9 @@ final class Validators { /// function should be provided for special numbering requirements /// {@endtemplate} static Validator hasMinNumericChars({ - int min = 1, - int Function(String)? customNumericCounter, - String Function(String input, int min)? hasMinNumericCharsMsg, + c.int min = 1, + c.int Function(String input)? customNumericCounter, + String Function(String input, c.int min)? hasMinNumericCharsMsg, }) => val.hasMinNumericChars( min: min, @@ -3006,9 +2961,9 @@ final class Validators { /// should be provided for specific character set requirements /// {@endtemplate} static Validator hasMinSpecialChars({ - int min = 1, - int Function(String)? customSpecialCounter, - String Function(String input, int min)? hasMinSpecialCharsMsg, + c.int min = 1, + c.int Function(String input)? customSpecialCounter, + String Function(String input, c.int min)? hasMinSpecialCharsMsg, }) => val.hasMinSpecialChars( min: min, @@ -3054,12 +3009,128 @@ final class Validators { /// or phone number validation /// {@endtemplate} static Validator match( - RegExp regex, { + RegExp regExp, { String Function(String input)? matchMsg, }) => - val.match(regex, matchMsg: matchMsg); + val.match(regExp, matchMsg: matchMsg); + + /// {@template validator_uuid} + /// A validator function that checks if a given string matches the UUID format. + /// + /// Creates a validator that ensures the input string conforms to the standard + /// UUID (Universally Unique Identifier) format, consisting of 32 hexadecimal + /// digits displayed in 5 groups separated by hyphens (8-4-4-4-12). + /// + /// ## Parameters + /// - `regex` (`RegExp?`): Optional custom regular expression pattern to override + /// the default UUID validation pattern. Useful for supporting different UUID + /// formats or adding additional constraints. + /// - `uuidMsg` (`String Function(String input)?`): Optional callback function that + /// generates a custom error message based on the invalid input. If not provided, + /// defaults to the standard form builder localization error text. + /// + /// ## Returns + /// Returns a `Validator` function that accepts a string input and returns: + /// - `null` if the input is valid + /// - An error message string if the input is invalid + /// + /// ## Examples + /// ```dart + /// // Using default UUID validation + /// final validator = uuid(); + /// print(validator('123e4567-e89b-12d3-a456-426614174000')); // null + /// print(validator('invalid-uuid')); // Returns error message + /// + /// // Using custom error message + /// final customValidator = uuid( + /// uuidMsg: (input) => 'Invalid UUID format: $input', + /// ); + /// + /// // Using custom regex pattern + /// final customPatternValidator = uuid( + /// regex: RegExp(r'^[0-9]{8}-[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{12}$'), + /// ); + /// ``` + /// + /// ## Caveats + /// - The default regex pattern accepts both uppercase and lowercase hexadecimal + /// digits (0-9, a-f, A-F) + /// - The validation only checks the format, not the actual UUID version or + /// variant compliance + /// {@endtemplate} + static Validator uuid({ + RegExp? regex, + String Function(String input)? uuidMsg, + }) => + val.uuid(regex: regex, uuidMsg: uuidMsg); // Collection validators + /// {@template validator_equal_length} + /// Creates a validator function that checks if the input collection's length equals + /// the specified length. The validator returns `null` for valid input and an error + /// message for invalid input. + /// + /// If validation fails and no custom error message generator is provided via + /// [equalLengthMsg], returns the default localized error message from + /// `FormBuilderLocalizations.current.equalLengthErrorText(expectedLength)`. + /// + /// ## Type Parameters + /// - `T`: The type of input to validate. Must be a collection, in other words, + /// it must be one of `String`, `Iterable` or `Map`. + /// + /// ## Parameters + /// - `expectedLength` (`int`): The exact length required. Must be non-negative. + /// - `equalLengthMsg` (`String Function(T input, int expectedLength)?`): Optional + /// function to generate custom error messages. Receives the input and the + /// expected length, returning an error message string. + /// + /// ## Return Value + /// A `Validator` function that produces: + /// - `null` for valid inputs (length == expectedLength) + /// - An error message string for invalid inputs (length != expectedLength) + /// + /// ## Throws + /// - `ArgumentError` when: + /// - [expectedLength] is negative + /// - input runtime type is not a collection + /// + /// ## Examples + /// ```dart + /// // String validation + /// final stringValidator = equalLength(3); + /// print(stringValidator('abc')); // Returns null + /// print(stringValidator('ab')); // Returns error message + /// print(stringValidator('abcd')); // Returns error message + /// + /// // List validation + /// final listValidator = equalLength(2); + /// print(listValidator([1, 2])); // Returns null + /// print(listValidator([1])); // Returns error message + /// print(listValidator([1, 2, 3])); // Returns error message + /// + /// // Custom error message + /// final customValidator = equalLength( + /// 5, + /// equalLengthMsg: (_, expectedLength) => + /// 'Text must be exactly $expectedLength chars long!', + /// ); + /// ``` + /// + /// ## Caveats + /// - Type parameter `T` must be restricted to `String`, `Map`, or `Iterable`. + /// While the compiler cannot enforce this restriction, it is the developer's + /// responsibility to maintain this constraint. + /// - The validator treats non-collection inputs as implementation errors rather + /// than validation failures. Validate input types before passing them to + /// this validator. + /// {@endtemplate} + static Validator equalLength(c.int expectedLength, + {String Function(T input, c.int expectedLength)? equalLengthMsg}) => + val.equalLength( + expectedLength, + equalLengthMsg: equalLengthMsg, + ); + /// {@template validator_min_length} /// Creates a validator function that checks if the input collection's length is /// greater than or equal to `min`. The validator returns `null` for valid input @@ -3116,8 +3187,8 @@ final class Validators { /// than validation failures. Validate input types before passing them to /// this validator. /// {@endtemplate} - static Validator minLength(int min, - {String Function(T input, int min)? minLengthMsg}) => + static Validator minLength(c.int min, + {String Function(T input, c.int min)? minLengthMsg}) => val.minLength(min, minLengthMsg: minLengthMsg); /// {@template validator_max_length} @@ -3175,8 +3246,8 @@ final class Validators { /// than validation failures. Validate input types before passing them to /// this validator. /// {@endtemplate} - static Validator maxLength(int max, - {String Function(T input, int max)? maxLengthMsg}) => + static Validator maxLength(c.int max, + {String Function(T input, c.int max)? maxLengthMsg}) => val.maxLength(max, maxLengthMsg: maxLengthMsg); /// {@template validator_between_length} @@ -3243,88 +3314,22 @@ final class Validators { /// this validator. /// {@endtemplate} static Validator betweenLength( - int min, - int max, { - String Function(T input, {required int min, required int max})? + c.int min, + c.int max, { + String Function(T input, {required c.int min, required c.int max})? betweenLengthMsg, }) => val.betweenLength(min, max, betweenLengthMsg: betweenLengthMsg); - /// {@template validator_equal_length} - /// Creates a validator function that checks if the input collection's length equals - /// the specified length. The validator returns `null` for valid input and an error - /// message for invalid input. - /// - /// If validation fails and no custom error message generator is provided via - /// [equalLengthMsg], returns the default localized error message from - /// `FormBuilderLocalizations.current.equalLengthErrorText(expectedLength)`. - /// - /// ## Type Parameters - /// - `T`: The type of input to validate. Must be a collection, in other words, - /// it must be one of `String`, `Iterable` or `Map`. - /// - /// ## Parameters - /// - `expectedLength` (`int`): The exact length required. Must be non-negative. - /// - `equalLengthMsg` (`String Function(T input, int expectedLength)?`): Optional - /// function to generate custom error messages. Receives the input and the - /// expected length, returning an error message string. - /// - /// ## Return Value - /// A `Validator` function that produces: - /// - `null` for valid inputs (length == expectedLength) - /// - An error message string for invalid inputs (length != expectedLength) - /// - /// ## Throws - /// - `ArgumentError` when: - /// - [expectedLength] is negative - /// - input runtime type is not a collection - /// - /// ## Examples - /// ```dart - /// // String validation - /// final stringValidator = equalLength(3); - /// print(stringValidator('abc')); // Returns null - /// print(stringValidator('ab')); // Returns error message - /// print(stringValidator('abcd')); // Returns error message - /// - /// // List validation - /// final listValidator = equalLength(2); - /// print(listValidator([1, 2])); // Returns null - /// print(listValidator([1])); // Returns error message - /// print(listValidator([1, 2, 3])); // Returns error message - /// - /// // Custom error message - /// final customValidator = equalLength( - /// 5, - /// equalLengthMsg: (_, expectedLength) => - /// 'Text must be exactly $expectedLength chars long!', - /// ); - /// ``` - /// - /// ## Caveats - /// - Type parameter `T` must be restricted to `String`, `Map`, or `Iterable`. - /// While the compiler cannot enforce this restriction, it is the developer's - /// responsibility to maintain this constraint. - /// - The validator treats non-collection inputs as implementation errors rather - /// than validation failures. Validate input types before passing them to - /// this validator. - /// {@endtemplate} - static Validator equalLength(int expectedLength, - {String Function(T input, int expectedLength)? equalLengthMsg}) => - val.equalLength( - expectedLength, - equalLengthMsg: equalLengthMsg, - ); - // DateTime Validators - /// {@template validator_is_after} + /// {@template validator_after} /// Creates a [DateTime] validator that checks if an input date occurs after /// `reference`. /// /// ## Parameters /// - `reference` (`DateTime`): The baseline date against which the input will be compared. /// This serves as the minimum acceptable date (exclusive by default). - /// - `isAfterMsg` (`String Function(DateTime input, DateTime reference)?`): Optional custom + /// - `afterMsg` (`String Function(DateTime input, DateTime reference)?`): Optional custom /// error message generator. When provided, it receives both the input and reference /// dates to construct a context-aware error message. /// - `inclusive` (`bool`): When set to `true`, allows the input date to exactly match @@ -3338,36 +3343,36 @@ final class Validators { /// ## Examples /// ```dart /// // Basic usage requiring date after January 1st, 2025 - /// final validator = isAfter(DateTime(2025)); + /// final validator = Validators.after(DateTime(2025)); /// /// // Inclusive validation allowing exact match - /// final inclusiveValidator = isAfter( + /// final inclusiveValidator = Validators.after( /// DateTime(2024), /// inclusive: true, /// ); /// /// // Custom error message - /// final customValidator = isAfter( + /// final customValidator = Validators.after( /// DateTime(2024), /// isAfterMsg: (_, ref) => 'Please select a date after ${ref.toString()}', /// ); /// ``` /// {@endtemplate} - static Validator isAfter( + static Validator after( DateTime reference, { - String Function(DateTime input, DateTime reference)? isAfterMsg, - bool inclusive = false, + String Function(DateTime input, DateTime reference)? afterMsg, + c.bool inclusive = false, }) => - val.isAfter(reference, isAfterMsg: isAfterMsg, inclusive: inclusive); + val.after(reference, afterMsg: afterMsg, inclusive: inclusive); - /// {@template validator_is_before} + /// {@template validator_before} /// Creates a [DateTime] validator that checks if an input date occurs before /// `reference`. /// /// ## Parameters /// - `reference` (`DateTime`): The baseline date against which the input will be compared. /// This serves as the maximum acceptable date (exclusive by default). - /// - `isBeforeMsg` (`String Function(DateTime input, DateTime reference)?`): Optional custom + /// - `beforeMsg` (`String Function(DateTime input, DateTime reference)?`): Optional custom /// error message generator. When provided, it receives both the input and reference /// dates to construct a context-aware error message. /// - `inclusive` (`bool`): When set to `true`, allows the input date to exactly match @@ -3382,29 +3387,29 @@ final class Validators { /// ## Examples /// ```dart /// // Basic usage requiring date before January 1st, 2025 - /// final validator = isBefore(DateTime(2025)); + /// final validator = Validators.before(DateTime(2025)); /// /// // Inclusive validation allowing exact match - /// final inclusiveValidator = isBefore( + /// final inclusiveValidator = Validators.before( /// DateTime(2024), /// inclusive: true, /// ); /// /// // Custom error message - /// final customValidator = isBefore( + /// final customValidator = Validators.before( /// DateTime(2024), /// isBeforeMsg: (_, ref) => 'Please select a date before ${ref.toString()}', /// ); /// ``` /// {@endtemplate} - static Validator isBefore( + static Validator before( DateTime reference, { - String Function(DateTime input, DateTime reference)? isBeforeMsg, - bool inclusive = false, + String Function(DateTime input, DateTime reference)? beforeMsg, + c.bool inclusive = false, }) => - val.isBefore(reference, isBeforeMsg: isBeforeMsg, inclusive: inclusive); + val.before(reference, beforeMsg: beforeMsg, inclusive: inclusive); - /// {@template validator_is_date_time_between} + /// {@template validator_between_date_time} /// Creates a [DateTime] validator that checks if an input date falls within a specified /// range defined by `minReference` and `maxReference`. /// @@ -3417,7 +3422,7 @@ final class Validators { /// Input dates must occur after this date (or equal to it if `minInclusive` is true). /// - `maxReference` (`DateTime`): The upper bound of the acceptable date range. /// Input dates must occur before this date (or equal to it if `maxInclusive` is true). - /// - `isDateTimeBetweenMsg` (`String Function(DateTime, DateTime, DateTime)?`): Optional + /// - `betweenDateTimeMsg` (`String Function(DateTime, DateTime, DateTime)?`): Optional /// custom error message generator. When provided, it receives the input date and both /// reference dates to construct a context-aware error message. /// - `minInclusive` (`bool`): When set to `true`, allows the input date to exactly match @@ -3438,13 +3443,13 @@ final class Validators { /// ## Examples /// ```dart /// // Basic usage requiring date between 2023 and 2025 - /// final validator = isDateTimeBetween( + /// final validator = Validators.betweenDateTime( /// DateTime(2023), /// DateTime(2025), /// ); /// /// // Inclusive validation allowing exact matches - /// final inclusiveValidator = isDateTimeBetween( + /// final inclusiveValidator = Validators.betweenDateTime( /// DateTime(2023), /// DateTime(2025), /// minInclusive: true, @@ -3452,30 +3457,72 @@ final class Validators { /// ); /// /// // Custom error message - /// final customValidator = isDateTimeBetween( + /// final customValidator = Validators.betweenDateTime( /// DateTime(2023), /// DateTime(2025), - /// isDateTimeBetweenMsg: (_, min, max) => + /// betweenDateTimeMsg: (_, min, max) => /// 'Please select a date between ${min.toString()} and ${max.toString()}', /// ); /// ``` /// {@endtemplate} - static Validator isDateTimeBetween( + static Validator betweenDateTime( DateTime minReference, DateTime maxReference, { String Function( DateTime input, DateTime minReference, DateTime maxReference)? - isDateTimeBetweenMsg, - bool leftInclusive = false, - bool rightInclusive = false, + betweenDateTimeMsg, + c.bool leftInclusive = false, + c.bool rightInclusive = false, }) => - val.isDateTimeBetween(minReference, maxReference, - isDateTimeBetweenMsg: isDateTimeBetweenMsg, + val.betweenDateTime(minReference, maxReference, + betweenDateTimeMsg: betweenDateTimeMsg, minInclusive: leftInclusive, maxInclusive: rightInclusive); + /// {@template validator_max_file_size} + /// Validates that a file size in bytes is less than or equal to `max`. + /// + /// This validator compares the input integer (representing bytes) against a + /// maximum size threshold (`max`). The comparison can be performed using + /// either 1000-based units (B, kB, MB, etc.) or 1024-based units (B, KiB, + /// MiB, etc.) depending on the selected [base]. + /// + /// ## Parameters + /// - `max` (`int`): The maximum allowed file size in bytes + /// - `base` (`Base`): The base unit system to use for calculations and error messages. + /// Defaults to [Base.b1024] + /// - `maxFileSizeMsg` (`String Function(int input, int max, Base base)?`): + /// Optional custom error message generator that receives the input size, + /// maximum size, and base to produce a tailored error message + /// + /// ## Returns + /// A [Validator] function that returns `null` when the input is valid (less than or equal + /// to the maximum size), or an error message string when validation fails + /// + /// ## Examples + /// ```dart + /// // Create a validator restricting files to 5 MiB using 1024-based units + /// final validator = maxFileSize(5 * 1024 * 1024); + /// + /// // Create a validator restricting files to 5 MB using 1000-based units + /// final validator = maxFileSize(5 * 1000 * 1000, base: Base.b1000); + /// + /// // Using a custom error message + /// final validator = maxFileSize( + /// 10 * 1024 * 1024, + /// maxFileSizeMsg: (input, max, base) => 'File too large: ${formatBytes(input, base)}', + /// ); + /// ``` + /// {@endtemplate} + static Validator maxFileSize( + c.int max, { + val.Base base = val.Base.b1024, + String Function(c.int input, c.int max, val.Base base)? maxFileSizeMsg, + }) => + val.maxFileSize(max, base: base, maxFileSizeMsg: maxFileSizeMsg); + // Generic type validators - /// {@template validator_contains_element} + /// {@template validator_in_list} /// Creates a validator function that verifies if a given input is in `values`. /// /// ## Type Parameters @@ -3485,7 +3532,7 @@ final class Validators { /// ## Parameters /// - `values` (`List`): A non-empty list of valid values to check against. The input /// will be validated against these values. - /// - `containsElementMsg` (`String Function(T input, List values)?`): Optional callback + /// - `inListMsg` (`String Function(T input, List values)?`): Optional callback /// function that generates a custom error message when validation fails. The function /// receives the invalid input and the list of valid values as parameters. If not provided, /// defaults to the localized error text from FormBuilderLocalizations. @@ -3502,9 +3549,9 @@ final class Validators { /// ## Examples /// ```dart /// // Creating a validator with a custom error message generator - /// final countryValidator = containsElement( + /// final countryValidator = Validators.inList( /// ['USA', 'Canada', 'Mexico'], - /// containsElementMsg: (input, values) => + /// inListMsg: (input, values) => /// 'Country $input is not in allowed list: ${values.join(", ")}', /// ); /// @@ -3513,11 +3560,56 @@ final class Validators { /// final valid = countryValidator('USA'); // Returns null (valid) /// ``` /// {@endtemplate} - static Validator containsElement( + static Validator inList( List values, { - String Function(T input, List values)? containsElementMsg, + String Function(T input, List values)? inListMsg, }) => - val.containsElement(values, containsElementMsg: containsElementMsg); + val.inList(values, inListMsg: inListMsg); + + /// {@template validator_not_in_list} + /// Creates a validator function that verifies if a given input is not in + /// `values`. + /// + /// ## Type Parameters + /// - `T`: The type of elements to validate. Must extend Object?, allowing nullable + /// types. + /// + /// ## Parameters + /// - `values` (`List`): A non-empty list of invalid values to check + /// against. The input will be validated against these values. + /// - `notInListMsg` (`String Function(T input, List values)?`): Optional callback + /// function that generates a custom error message when validation fails. The function + /// receives the invalid input and the list of invalid values as parameters. If not provided, + /// defaults to the localized error text from FormBuilderLocalizations. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns null if the input value does not exist in the provided list + /// - Returns a generated error message if the input was found in the list. + /// + /// ## Throws + /// - `AssertionError`: Thrown if the provided values list is empty, which would + /// make any input valid. + /// + /// ## Examples + /// ```dart + /// // Creating a validator with a custom error message generator + /// final countryValidator = Validators.notInList( + /// ['USA', 'Canada', 'Mexico'], + /// notInListMsg: (input, values) => + /// 'Country $input is in the forbidden list: ${values.join(", ")}', + /// ); + /// + /// // Using the validator + /// final result = countryValidator('Brazil'); // Returns null (valid) + /// final valid = countryValidator('USA'); // Returns "Country USA is in the forbidden list: USA, Canada, Mexico" + /// ``` + /// {@endtemplate} + static Validator notInList( + List values, { + String Function(T input, List values)? notInListMsg, + }) => + val.notInList(values, notInListMsg: notInListMsg); /// {@template validator_is_true} /// Creates a validator function that checks if a given input represents a `true` @@ -3566,8 +3658,8 @@ final class Validators { /// {@endtemplate} static Validator isTrue( {String Function(T input)? isTrueMsg, - bool caseSensitive = false, - bool trim = true}) => + c.bool caseSensitive = false, + c.bool trim = true}) => val.isTrue( isTrueMsg: isTrueMsg, caseSensitive: caseSensitive, trim: trim); @@ -3618,12 +3710,83 @@ final class Validators { /// {@endtemplate} static Validator isFalse( {String Function(T input)? isFalseMsg, - bool caseSensitive = false, - bool trim = false}) => + c.bool caseSensitive = false, + c.bool trim = false}) => val.isFalse( isFalseMsg: isFalseMsg, caseSensitive: caseSensitive, trim: trim); // Numeric validators + /// {@template validator_between} + /// Creates a validator function that checks if a numeric input falls within a specified + /// range defined by `min` and `max` values. + /// + /// ## Type Parameters + /// - `T`: A numeric type that extends [num], allowing `int`, `double` or + /// `num` validations + /// + /// ## Parameters + /// - `min` (`T`): The lower bound of the valid range + /// - `max` (`T`): The upper bound of the valid range + /// - `minInclusive` (`bool`): Determines if the lower bound is inclusive. Defaults to `true` + /// - `maxInclusive` (`bool`): Determines if the upper bound is inclusive. Defaults to `true` + /// - `betweenMsg` (`String Function(T input, T min, T max, bool minInclusive, bool maxInclusive)?`): + /// Optional custom error message generator that takes the input value, inclusivity flags, + /// and range bounds as parameters + /// + /// + /// ## Returns + /// Returns a [Validator] function that: + /// - Returns `null` if the input falls within the specified range according to the + /// inclusivity settings + /// - Returns an error message string if validation fails, either from the custom + /// `betweenMsg` function or the default localized error text from + /// [FormBuilderLocalizations] + /// + /// ## Throw + /// - `AssertionError`: when `max` is not greater than or equal to `min`. + /// + /// ## Examples + /// ```dart + /// // Basic usage with inclusive bounds + /// final ageValidator = between(18, 65); // [18, 65] + /// + /// // Exclusive upper bound for decimal values + /// final priceValidator = between( // [0.0, 100.0) + /// 0.0, + /// 100.0, + /// maxInclusive: false, + /// ); + /// + /// // Custom error message + /// final scoreValidator = between( // + /// 0.0, + /// 10.0, + /// betweenMsg: (_, min, max, __, ___) => + /// 'Score must be between $min and $max (inclusive)', + /// ); + /// ``` + /// + /// ## Caveats + /// - The default behavior uses inclusive bounds (`>=` and `<=`) + /// {@endtemplate} + static Validator between(T min, T max, + {c.bool minInclusive = true, + c.bool maxInclusive = true, + String Function( + T input, + T min, + T max, + c.bool minInclusive, + c.bool maxInclusive, + )? betweenMsg}) => + val.between( + min, + max, + minInclusive: minInclusive, + maxInclusive: maxInclusive, + betweenMsg: betweenMsg, + ); + /// {@template validator_greater_than} /// Creates a validator function that checks if a numeric input exceeds `reference`. /// @@ -3657,8 +3820,8 @@ final class Validators { /// ## Caveats /// - The validator uses strict greater than comparison (`>`) /// {@endtemplate} - static Validator greaterThan(T reference, - {String Function(num input, num reference)? greaterThanMsg}) => + static Validator greaterThan(T reference, + {String Function(c.num input, c.num reference)? greaterThanMsg}) => val.greaterThan(reference, greaterThanMsg: greaterThanMsg); /// {@template validator_greater_than_or_equal_to} @@ -3697,8 +3860,8 @@ final class Validators { /// ## Caveats /// - The validator uses greater than or equal to comparison (`>=`) /// {@endtemplate} - static Validator greaterThanOrEqualTo(T reference, - {String Function(num input, num reference)? + static Validator greaterThanOrEqualTo(T reference, + {String Function(c.num input, c.num reference)? greaterThanOrEqualToMsg}) => val.greaterThanOrEqualTo(reference, greaterThanOrEqualToMsg: greaterThanOrEqualToMsg); @@ -3736,8 +3899,8 @@ final class Validators { /// ## Caveats /// - The validator uses strict less than comparison (`<`) /// {@endtemplate} - static Validator lessThan(T reference, - {String Function(num input, num reference)? lessThanMsg}) => + static Validator lessThan(T reference, + {String Function(c.num input, c.num reference)? lessThanMsg}) => val.lessThan(reference, lessThanMsg: lessThanMsg); /// {@template validator_less_than_or_equal_to} @@ -3776,83 +3939,58 @@ final class Validators { /// ## Caveats /// - The validator uses less than or equal to comparison (`<=`) /// {@endtemplate} - static Validator lessThanOrEqualTo(T reference, - {String Function(num input, num reference)? lessThanOrEqualToMsg}) => + static Validator lessThanOrEqualTo(T reference, + {String Function(c.num input, c.num reference)? + lessThanOrEqualToMsg}) => val.lessThanOrEqualTo(reference, lessThanOrEqualToMsg: lessThanOrEqualToMsg); - /// {@template validator_between} - /// Creates a validator function that checks if a numeric input falls within a specified - /// range defined by `min` and `max` values. - /// - /// ## Type Parameters - /// - `T`: A numeric type that extends [num], allowing `int`, `double` or - /// `num` validations + // User information validators + + /// {@template validator_email} + /// A validator function that checks if a given string is a valid email address. + /// Uses either a custom or default RFC 5322 compliant regular expression for validation. /// /// ## Parameters - /// - `min` (`T`): The lower bound of the valid range - /// - `max` (`T`): The upper bound of the valid range - /// - `minInclusive` (`bool`): Determines if the lower bound is inclusive. Defaults to `true` - /// - `maxInclusive` (`bool`): Determines if the upper bound is inclusive. Defaults to `true` - /// - `betweenMsg` (`String Function(T input, T min, T max, bool minInclusive, bool maxInclusive)?`): - /// Optional custom error message generator that takes the input value, inclusivity flags, - /// and range bounds as parameters + /// - `regex` (`RegExp?`): Optional custom regular expression for email validation. + /// If not provided, uses a default RFC 5322 compliant pattern that supports: + /// - ASCII characters + /// - Unicode characters (including IDN domains) + /// - Special characters in local part + /// - Quoted strings + /// - Multiple dots /// + /// - `emailMsg` (`String Function(String input)?`): Optional custom error message + /// generator function that takes the invalid input and returns a custom error + /// message. If not provided, uses the default localized error text. /// /// ## Returns - /// Returns a [Validator] function that: - /// - Returns `null` if the input falls within the specified range according to the - /// inclusivity settings - /// - Returns an error message string if validation fails, either from the custom - /// `betweenMsg` function or the default localized error text from - /// [FormBuilderLocalizations] - /// - /// ## Throw - /// - `AssertionError`: when `max` is not greater than or equal to `min`. + /// Returns a `Validator` function that: + /// - Returns `null` if the email is valid + /// - Returns an error message string if the email is invalid /// /// ## Examples + /// Basic usage with default settings: /// ```dart - /// // Basic usage with inclusive bounds - /// final ageValidator = between(18, 65); // [18, 65] - /// - /// // Exclusive upper bound for decimal values - /// final priceValidator = between( // [0.0, 100.0) - /// 0.0, - /// 100.0, - /// maxInclusive: false, - /// ); + /// final emailValidator = email(); + /// final result = emailValidator('user@example.com'); + /// print(result); // null (valid email) + /// ``` /// - /// // Custom error message - /// final scoreValidator = between( // - /// 0.0, - /// 10.0, - /// betweenMsg: (_, min, max, __, ___) => - /// 'Score must be between $min and $max (inclusive)', + /// Using custom regex and error message: + /// ```dart + /// final customValidator = email( + /// regex: RegExp(r'^[a-zA-Z0-9.]+@company\.com$'), + /// emailMsg: (input) => '$input is not a valid company email', /// ); /// ``` - /// - /// ## Caveats - /// - The default behavior uses inclusive bounds (`>=` and `<=`) /// {@endtemplate} - static Validator between(T min, T max, - {bool minInclusive = true, - bool maxInclusive = true, - String Function( - T input, - T min, - T max, - bool minInclusive, - bool maxInclusive, - )? betweenMsg}) => - val.between( - min, - max, - minInclusive: minInclusive, - maxInclusive: maxInclusive, - betweenMsg: betweenMsg, - ); + static Validator email({ + RegExp? regex, + String Function(String input)? emailMsg, + }) => + val.email(regex: regex, emailMsg: emailMsg); - // User information validators /// {@template validator_password} /// Creates a composite validator for password validation that enforces multiple /// password strength requirements simultaneously. @@ -3896,12 +4034,12 @@ final class Validators { /// are not available to the user. /// {@endtemplate} static Validator password({ - int minLength = 8, - int maxLength = 32, - int minUppercaseCount = 1, - int minLowercaseCount = 1, - int minNumberCount = 1, - int minSpecialCharCount = 1, + c.int minLength = 8, + c.int maxLength = 32, + c.int minUppercaseCount = 1, + c.int minLowercaseCount = 1, + c.int minNumberCount = 1, + c.int minSpecialCharCount = 1, String Function(String input)? passwordMsg, }) => val.password( @@ -3959,50 +4097,6 @@ final class Validators { }) => val.phoneNumber(regex: regex, phoneNumberMsg: phoneNumberMsg); - /// {@template validator_email} - /// A validator function that checks if a given string is a valid email address. - /// Uses either a custom or default RFC 5322 compliant regular expression for validation. - /// - /// ## Parameters - /// - `regex` (`RegExp?`): Optional custom regular expression for email validation. - /// If not provided, uses a default RFC 5322 compliant pattern that supports: - /// - ASCII characters - /// - Unicode characters (including IDN domains) - /// - Special characters in local part - /// - Quoted strings - /// - Multiple dots - /// - /// - `emailMsg` (`String Function(String input)?`): Optional custom error message - /// generator function that takes the invalid input and returns a custom error - /// message. If not provided, uses the default localized error text. - /// - /// ## Returns - /// Returns a `Validator` function that: - /// - Returns `null` if the email is valid - /// - Returns an error message string if the email is invalid - /// - /// ## Examples - /// Basic usage with default settings: - /// ```dart - /// final emailValidator = email(); - /// final result = emailValidator('user@example.com'); - /// print(result); // null (valid email) - /// ``` - /// - /// Using custom regex and error message: - /// ```dart - /// final customValidator = email( - /// regex: RegExp(r'^[a-zA-Z0-9.]+@company\.com$'), - /// emailMsg: (input) => '$input is not a valid company email', - /// ); - /// ``` - /// {@endtemplate} - static Validator email({ - RegExp? regex, - String Function(String input)? emailMsg, - }) => - val.email(regex: regex, emailMsg: emailMsg); - // Finance validators /// {@template validator_credit_card} /// A validator function for credit card number validation that supports major card @@ -4051,11 +4145,109 @@ final class Validators { /// ``` /// {@endtemplate} static Validator creditCard({ + // TODO(ArturAssisComp): turn this into a function, becoming more generic. RegExp? regex, String Function(String input)? creditCardMsg, }) => val.creditCard(regex: regex, creditCardMsg: creditCardMsg); + ///{@template validator_iban} + /// A validator function that checks if a string is a valid International Bank + /// Account Number (IBAN). + /// + /// Returns null if the input is a valid IBAN format, otherwise returns an + /// error message. The validator performs standard IBAN validation including + /// length check, character conversion, and checksum calculation according to + /// the ISO 13616 standard. + /// + /// ## Parameters + /// - `isIban` (`bool Function(String input)?`): Optional custom validation + /// function that determines if the input is a valid IBAN. If provided, this + /// function overrides the default validation logic. + /// - `ibanMsg` (`String Function(String input)?`): Optional function that + /// returns a custom error message when validation fails. If not provided, + /// the default localized error message is used. + /// + /// ## Returns + /// A `Validator` function that accepts a string input and returns + /// null for valid IBANs or an error message string for invalid IBANs. + /// + /// ## Examples + /// ```dart + /// // Basic usage with default validation and error message + /// final validator = Validators.iban(); + /// assert(validator('GB82 WEST 1234 5698 7654 32') == null); // Valid IBAN + /// assert(validator('invalid123') != null); // Invalid IBAN + /// + /// // Using custom validation logic + /// final customValidator = FormBuilderValidators.iban( + /// isIban: (input) => input.startsWith('DE'), + /// ibanMsg: (input) => 'Only German IBANs are accepted', + /// ); + /// assert(customValidator('DE89 3704 0044 0532 0130 00') == null); // Valid German IBAN + /// assert(customValidator('GB82 WEST 1234 5698 7654 32') != null); // Not a German IBAN + /// ``` + /// + /// ## Caveats + /// - The validator removes all spaces from the input before validation + /// - The validation is case-insensitive as the input is converted to uppercase + /// - The minimum length check for IBANs is set to 15 characters (after removing spaces) + /// {@endtemplate} + static Validator iban({ + c.bool Function(String input)? isIban, + String Function(String input)? ibanMsg, + }) => + val.iban(isIban: isIban, ibanMsg: ibanMsg); + + ///{@template validator_bic} + /// Creates a validator that checks if a string is a valid BIC (Bank Identifier Code). + /// + /// A BIC validator checks string inputs against standard BIC format regulations. The + /// validator returns `null` for valid BICs and an error message for invalid inputs. + /// + /// BIC codes must consist of 8 or 11 characters: 4 bank code letters, 2 country code + /// letters, 2 location code alphanumeric characters, and optionally 3 branch code + /// alphanumeric characters. + /// + /// ## Parameters + /// - `isBic` (`bool Function(String input)?`): Optional custom function to determine + /// if a string is a valid BIC. If provided, this function overrides the default + /// BIC validation logic. + /// - `bicMsg` (`String Function(String input)?`): Optional custom function to generate + /// error messages for invalid BICs. If provided, this function overrides the default + /// error message. + /// + /// ## Returns + /// A `Validator` function that returns: + /// - `null` if the input is a valid BIC + /// - An error message string if the input is not a valid BIC + /// + /// ## Examples + /// ```dart + /// // Using default validation + /// final validator = bic(); + /// assert(validator('DEUTDEFF') == null); // Valid: 8-character BIC + /// assert(validator('DEUTDEFFXXX') == null); // Valid: 11-character BIC + /// assert(validator('deut deff xxx') == null); // Valid: spaces are removed and case is normalized + /// assert(validator('DEUT123') != null); // Invalid: too short + /// assert(validator('DEUTDEFFXXXX') != null); // Invalid: too long + /// assert(validator('123TDEFF') != null); // Invalid: first 4 chars must be letters + /// + /// // Using custom validation and error message + /// final validator = bic( + /// isBic: (value) => value.startsWith('DEUT'), + /// bicMsg: (value) => 'BIC must start with DEUT, got: $value', + /// ); + /// assert(validator('DEUTDEFF') == null); // Valid: starts with DEUT + /// assert(validator('ABCDDEFF') != null); // Invalid: custom error message + /// ``` + ///{@endtemplate} + static Validator bic({ + c.bool Function(String input)? isBic, + String Function(String input)? bicMsg, + }) => + val.bic(isBic: isBic, bicMsg: bicMsg); + // Network validators /// {@template validator_ip} @@ -4082,8 +4274,8 @@ final class Validators { /// ```dart /// // Basic IPv4 validation /// final ipv4Validator = ip(); - /// print(ipv4Validator('192.168.1.1')); // null (valid) - /// print(ipv4Validator('256.1.2.3')); // Returns error message (invalid) + /// assert(ipv4Validator('192.168.1.1') == null); // null (valid) + /// assert(ipv4Validator('256.1.2.3') != null); // Returns error message (invalid) /// /// // Custom error message for IPv6 /// final ipv6Validator = ip( @@ -4139,14 +4331,14 @@ final class Validators { /// ```dart /// // Basic URL validation /// final validator = url(); - /// print(validator('https://example.com')); // Returns: null + /// assert(validator('https://example.com') == null); // Returns: null /// /// // Custom protocol validation /// final ftpValidator = url( /// protocols: ['ftp'], /// requireProtocol: true /// ); - /// print(ftpValidator('ftp://server.com')); // Returns: null + /// assert(ftpValidator('ftp://server.com') == null); // Returns: null /// /// // With host filtering /// final restrictedValidator = url( @@ -4157,9 +4349,9 @@ final class Validators { /// {@endtemplate} static Validator url({ List protocols = val.kDefaultUrlValidationProtocols, - bool requireTld = true, - bool requireProtocol = false, - bool allowUnderscore = false, + c.bool requireTld = true, + c.bool requireProtocol = false, + c.bool allowUnderscore = false, List hostAllowList = const [], List hostBlockList = const [], RegExp? regex, diff --git a/lib/src/validators/core_validators/equality_validators.dart b/lib/src/validators/core_validators/equality_validators.dart index 7aedd1a4..289fd42b 100644 --- a/lib/src/validators/core_validators/equality_validators.dart +++ b/lib/src/validators/core_validators/equality_validators.dart @@ -1,29 +1,29 @@ import '../../../localization/l10n.dart'; import '../constants.dart'; -/// {@macro validator_is_equal} -Validator isEqual( +/// {@macro validator_equal} +Validator equal( T referenceValue, { - String Function(T input, T referenceValue)? isEqualMsg, + String Function(T input, T referenceValue)? equalMsg, }) { return (T input) { return referenceValue == input ? null - : isEqualMsg?.call(input, referenceValue) ?? + : equalMsg?.call(input, referenceValue) ?? FormBuilderLocalizations.current .equalErrorText(referenceValue.toString()); }; } -/// {@macro validator_is_not_equal} -Validator isNotEqual( +/// {@macro validator_not_equal} +Validator notEqual( T referenceValue, { - String Function(T input, T referenceValue)? isNotEqualMsg, + String Function(T input, T referenceValue)? notEqualMsg, }) { return (T input) { return referenceValue != input ? null - : isNotEqualMsg?.call(input, referenceValue) ?? + : notEqualMsg?.call(input, referenceValue) ?? FormBuilderLocalizations.current .notEqualErrorText(referenceValue.toString()); }; diff --git a/lib/src/validators/core_validators/required_validators.dart b/lib/src/validators/core_validators/required_validators.dart index 5c999a77..7d92c20f 100644 --- a/lib/src/validators/core_validators/required_validators.dart +++ b/lib/src/validators/core_validators/required_validators.dart @@ -1,17 +1,16 @@ import '../../../localization/l10n.dart'; import '../constants.dart'; -/// {@macro validator_is_required} -Validator isRequired([ +/// {@macro validator_required} +Validator required([ Validator? next, - String? isRequiredMsg, + String? requiredMsg, ]) { String? finalValidator(T? value) { final (bool isValid, T? transformedValue) = _isRequiredValidateAndConvert(value); if (!isValid) { - return isRequiredMsg ?? - FormBuilderLocalizations.current.requiredErrorText; + return requiredMsg ?? FormBuilderLocalizations.current.requiredErrorText; } return next?.call(transformedValue!); } @@ -25,10 +24,10 @@ Validator validateWithDefault( return (T? value) => next(value ?? defaultValue); } -/// {@macro validator_is_optional} -Validator isOptional([ +/// {@macro validator_optional} +Validator optional([ Validator? next, - String Function(T input, String nextErrorMsg)? isOptionalMsg, + String Function(T input, String nextErrorMsg)? optionalMsg, ]) { return (T? input) { final (bool isValid, T? transformedValue) = @@ -42,7 +41,7 @@ Validator isOptional([ return null; } - return isOptionalMsg?.call(input!, nextErrorMessage) ?? + return optionalMsg?.call(input!, nextErrorMessage) ?? FormBuilderLocalizations.current.isOptionalErrorText(nextErrorMessage); }; } diff --git a/lib/src/validators/core_validators/type_validators.dart b/lib/src/validators/core_validators/type_validators.dart index 607999a5..6ac04b89 100644 --- a/lib/src/validators/core_validators/type_validators.dart +++ b/lib/src/validators/core_validators/type_validators.dart @@ -3,16 +3,16 @@ import '../../../localization/l10n.dart'; import '../constants.dart'; -/// {@macro validator_is_string} -Validator isString([ +/// {@macro validator_string} +Validator string([ Validator? next, - String Function(T input)? isStringMsg, + String Function(T input)? stringMsg, ]) { String? finalValidator(T input) { final (bool isValid, String? typeTransformedValue) = _isStringValidateAndConvert(input); if (!isValid) { - return isStringMsg?.call(input) ?? + return stringMsg?.call(input) ?? FormBuilderLocalizations.current.isStringErrorText; } return next?.call(typeTransformedValue!); @@ -28,7 +28,7 @@ Validator isString([ return (false, null); } -/// {@macro validator_is_int} +/// {@macro validator_int} Validator isInt( [Validator? next, String Function(T input)? isIntMsg]) { String? finalValidator(T input) { @@ -57,7 +57,7 @@ Validator isInt( return (false, null); } -/// {@macro validator_is_num} +/// {@macro validator_num} Validator isNum( [Validator? next, String Function(T input)? isNumMsg]) { String? finalValidator(T input) { @@ -86,7 +86,7 @@ Validator isNum( return (false, null); } -/// {@macro validator_is_double} +/// {@macro validator_double} Validator isDouble( [Validator? next, String Function(T input)? isDoubleMsg]) { String? finalValidator(T input) { @@ -117,7 +117,7 @@ Validator isDouble( return (false, null); } -/// {@macro validator_is_bool} +/// {@macro validator_bool} Validator isBool( [Validator? next, String Function(T input)? isBoolMsg, @@ -152,16 +152,16 @@ Validator isBool( return (false, null); } -/// {@macro validator_is_date_time} -Validator isDateTime([ +/// {@macro validator_date_time} +Validator dateTime([ Validator? next, - String Function(T input)? isDateTimeMsg, + String Function(T input)? dateTimeMsg, ]) { String? finalValidator(T input) { final (bool isValid, DateTime? typeTransformedValue) = _isDateTimeValidateAndConvert(input); if (!isValid) { - return isDateTimeMsg?.call(input) ?? + return dateTimeMsg?.call(input) ?? FormBuilderLocalizations.current.dateTimeErrorText; } return next?.call(typeTransformedValue!); diff --git a/lib/src/validators/datetime_validators.dart b/lib/src/validators/datetime_validators.dart index 5ff13fd7..c51952b3 100644 --- a/lib/src/validators/datetime_validators.dart +++ b/lib/src/validators/datetime_validators.dart @@ -1,44 +1,44 @@ import '../../localization/l10n.dart'; import 'constants.dart'; -/// {@macro validator_is_after} -Validator isAfter( +/// {@macro validator_after} +Validator after( DateTime reference, { - String Function(DateTime input, DateTime reference)? isAfterMsg, + String Function(DateTime input, DateTime reference)? afterMsg, bool inclusive = false, }) { return (DateTime input) { return input.isAfter(reference) || (inclusive ? input.isAtSameMomentAs(reference) : false) ? null - : isAfterMsg?.call(input, reference) ?? + : afterMsg?.call(input, reference) ?? FormBuilderLocalizations.current .dateMustBeAfterErrorText(reference.toLocal()); }; } -/// {@macro validator_is_before} -Validator isBefore( +/// {@macro validator_before} +Validator before( DateTime reference, { - String Function(DateTime input, DateTime reference)? isBeforeMsg, + String Function(DateTime input, DateTime reference)? beforeMsg, bool inclusive = false, }) { return (DateTime input) { return input.isBefore(reference) || (inclusive ? input.isAtSameMomentAs(reference) : false) ? null - : isBeforeMsg?.call(input, reference) ?? + : beforeMsg?.call(input, reference) ?? FormBuilderLocalizations.current .dateMustBeBeforeErrorText(reference.toLocal()); }; } -/// {@macro validator_is_date_time_between} -Validator isDateTimeBetween( +/// {@macro validator_between_date_time} +Validator betweenDateTime( DateTime minReference, DateTime maxReference, { String Function(DateTime input, DateTime minReference, DateTime maxReference)? - isDateTimeBetweenMsg, + betweenDateTimeMsg, bool minInclusive = false, bool maxInclusive = false, }) { @@ -52,7 +52,7 @@ Validator isDateTimeBetween( (input.isAfter(minReference) || (minInclusive ? input.isAtSameMomentAs(minReference) : false)) ? null - : isDateTimeBetweenMsg?.call(input, minReference, maxReference) ?? + : betweenDateTimeMsg?.call(input, minReference, maxReference) ?? FormBuilderLocalizations.current.dateMustBeBetweenErrorText( minReference.toLocal(), maxReference.toLocal()); }; diff --git a/lib/src/validators/file_validators.dart b/lib/src/validators/file_validators.dart new file mode 100644 index 00000000..72025e11 --- /dev/null +++ b/lib/src/validators/file_validators.dart @@ -0,0 +1,120 @@ +import 'dart:math'; + +import 'package:intl/intl.dart'; + +import '../../localization/l10n.dart'; +import 'constants.dart'; + +/// Defines the unit systems used for file size calculations and representations. +/// +/// This enum provides two common standards for representing file sizes: +/// - 1000-based units (B, kB, MB, GB, TB) where 1 kilobyte = 1000 bytes +/// - 1024-based units (B, KiB, MiB, GiB, TiB) where 1 kibibyte = 1024 bytes +/// +/// ## Examples +/// ```dart +/// // Format 1_500_000 bytes using the 1000-based system +/// final readableSize = formatBytes(1_500_000, Base.b1000); // "1.5 MB" +/// +/// // Format 1_500_000 bytes using the 1024-based system +/// final readableSize = formatBytes(1_500_000, Base.b1024); // "1.46 MiB" +/// ``` +enum Base { + /// 1KB is equivalent to 10^3 = 1000 B + b1000(1000, ['B', 'KB', 'MB', 'GB', 'TB']), + + /// 1KiB is equivalent to 2^10 = 1024 B + b1024(1024, ['B', 'KiB', 'MiB', 'GiB', 'TiB']); + + /// The number of bytes in one 'kilo' unit. + final int base; + + /// The units available for the current base. + List get units => List.unmodifiable(_units); + final List _units; + + const Base(this.base, this._units); +} + +/// {@macro validator_max_file_size} +Validator maxFileSize( + int max, { + Base base = Base.b1024, + String Function(int input, int max, Base base)? maxFileSizeMsg, +}) { + return (int input) { + if (input <= max) { + return null; + } + if (maxFileSizeMsg != null) { + return maxFileSizeMsg(input, max, base); + } + + final (String formattedMax, String formattedInput) = + formatBoth(max, input, base); + + return FormBuilderLocalizations.current + .fileSizeErrorText(formattedMax, formattedInput); + }; +} + +/// Helper function to format bytes into a human-readable string (e.g., KB, MB, GB) +/// for both v1 and v2 in such a way that their representation will be different. +/// +/// ## Error +/// - `ArgumentError`: throws `ArgumentError` when v1 == v2. +(String v1, String v2) formatBoth(int v1, int v2, Base b) { + if (v1 == v2) { + throw ArgumentError.value( + v2, 'input', "'input' must be different from 'max'"); + } + assert(v1 != v2); + final int base = b.base; + final List units = b.units; + final List formattingMask = [ + NumberFormat("#,##0.#"), + NumberFormat("#,##0,000.#"), + NumberFormat("#,##0,000,000.#"), + NumberFormat("#,##0,000,000,000.#"), + NumberFormat("#,##0,000,000,000,000.#"), + ]; + + // format max + final int v1DigitGroups = (log(v1) / log(base)).floor(); + final double v1Size = v1 / pow(base, v1DigitGroups); + + // format input + final int v2DigitGroups = (log(v2) / log(base)).floor(); + final double v2Size = v2 / pow(base, v2DigitGroups); + String formattedV1; + String formattedV2; + for (final (int i, NumberFormat formatMask) in formattingMask.indexed) { + formattedV1 = + "${formatMask.format(v1Size * pow(base, i))} ${units[v1DigitGroups - i]}"; + formattedV2 = + "${formatMask.format(v2Size * pow(base, i))} ${units[v2DigitGroups - i]}"; + if (formattedV1 != formattedV2) { + return (formattedV1, formattedV2); + } + } + throw StateError( + "Unable to format 'max' and 'input' to distinct string representations despite having different values (max=$v1, input=$v2)"); +} + +/// Helper function to format bytes into a human-readable string (e.g., KB, MB, GB). +/// +/// ## Parameters: +/// - [bytes] The size in bytes to be formatted. +/// +/// ## Returns: +/// A formatted string representing the size in human-readable units. +String formatBytes(int bytes, Base b) { + double log10(num x) => log(x) / ln10; + if (bytes <= 0) return '0 B'; + final int base = b.base; + final List units = b.units; + final int digitGroups = (log10(bytes) / log10(base)).floor(); + final double size = bytes / pow(base, digitGroups); + + return "${NumberFormat("#,##0.#").format(size)} ${units[digitGroups]}"; +} diff --git a/lib/src/validators/finance_validators.dart b/lib/src/validators/finance_validators.dart index 1fc11f0b..3f924ce2 100644 --- a/lib/src/validators/finance_validators.dart +++ b/lib/src/validators/finance_validators.dart @@ -18,6 +18,32 @@ Validator creditCard({ }; } +/// {@macro validator_bic} +Validator bic({ + bool Function(String input)? isBic, + String Function(String input)? bicMsg, +}) { + return (String input) { + return (isBic?.call(input) ?? _isBIC(input)) + ? null + : (bicMsg?.call(input) ?? + FormBuilderLocalizations.current.bicErrorText); + }; +} + +/// {@macro validator_iban} +Validator iban({ + bool Function(String input)? isIban, + String Function(String input)? ibanMsg, +}) { + return (String input) { + return isIban?.call(input) ?? _isIBAN(input) + ? null + : (ibanMsg?.call(input) ?? + FormBuilderLocalizations.current.ibanErrorText); + }; +} + //****************************************************************************** //* Aux functions * //****************************************************************************** @@ -51,3 +77,50 @@ bool _isCreditCard(String value, RegExp regex) { return (sum % 10 == 0); } + +/// Check if the string is a valid BIC string. +/// +/// ## Parameters: +/// - [value] The string to be evaluated. +/// +/// ## Returns: +/// A boolean indicating whether the value is a valid BIC. +bool _isBIC(String value) { + final String bic = value.replaceAll(' ', '').toUpperCase(); + final RegExp regex = RegExp(r'^[A-Z]{4}[A-Z]{2}\w{2}(\w{3})?$'); + + if (bic.length != 8 && bic.length != 11) { + return false; + } + + return regex.hasMatch(bic); +} + +/// Check if the string is a valid IBAN. +bool _isIBAN(String value) { + final String iban = value.replaceAll(' ', '').toUpperCase(); + + if (iban.length < 15) { + return false; + } + + final String rearranged = iban.substring(4) + iban.substring(0, 4); + final String numericIban = rearranged.split('').map((String char) { + final int charCode = char.codeUnitAt(0); + return charCode >= 65 && charCode <= 90 ? (charCode - 55).toString() : char; + }).join(); + + int remainder = int.parse(numericIban.substring(0, 9)) % 97; + for (int i = 9; i < numericIban.length; i += 7) { + remainder = int.parse( + remainder.toString() + + numericIban.substring( + i, + i + 7 < numericIban.length ? i + 7 : numericIban.length, + ), + ) % + 97; + } + + return remainder == 1; +} diff --git a/lib/src/validators/generic_type_validators.dart b/lib/src/validators/generic_type_validators.dart index 8234c1de..e08f83a8 100644 --- a/lib/src/validators/generic_type_validators.dart +++ b/lib/src/validators/generic_type_validators.dart @@ -1,10 +1,10 @@ import '../../localization/l10n.dart'; import 'constants.dart'; -/// {@macro validator_contains_element} -Validator containsElement( +/// {@macro validator_in_list} +Validator inList( List values, { - String Function(T input, List values)? containsElementMsg, + String Function(T input, List values)? inListMsg, }) { if (values.isEmpty) { throw ArgumentError.value( @@ -14,11 +14,29 @@ Validator containsElement( return (T input) { return setOfValues.contains(input) ? null - : containsElementMsg?.call(input, values) ?? + : inListMsg?.call(input, values) ?? FormBuilderLocalizations.current.containsElementErrorText; }; } +/// {@macro validator_not_in_list} +Validator notInList( + List values, { + String Function(T input, List values)? notInListMsg, +}) { + if (values.isEmpty) { + throw ArgumentError.value( + '[]', 'values', 'The list of values must not be empty'); + } + final Set setOfValues = values.toSet(); + return (T input) { + return !setOfValues.contains(input) + ? null + : notInListMsg?.call(input, values) ?? + FormBuilderLocalizations.current.doesNotContainElementErrorText; + }; +} + /// {@macro validator_is_true} Validator isTrue( {String Function(T input)? isTrueMsg, @@ -39,7 +57,7 @@ Validator isTrue( }; } -/// {@macro validator_is_false} +/// {@macro validator_false} Validator isFalse( {String Function(T input)? isFalseMsg, bool caseSensitive = false, diff --git a/lib/src/validators/string_validators.dart b/lib/src/validators/string_validators.dart index 8ad6ab93..5e27c592 100644 --- a/lib/src/validators/string_validators.dart +++ b/lib/src/validators/string_validators.dart @@ -113,11 +113,11 @@ Validator hasMinSpecialChars({ /// {@macro validator_match} Validator match( - RegExp regex, { + RegExp regExp, { String Function(String input)? matchMsg, }) { return (String input) { - return regex.hasMatch(input) + return regExp.hasMatch(input) ? null : matchMsg?.call(input) ?? FormBuilderLocalizations.current.matchErrorText; diff --git a/lib/src/validators/validators.dart b/lib/src/validators/validators.dart index a1f57f02..41923aa3 100644 --- a/lib/src/validators/validators.dart +++ b/lib/src/validators/validators.dart @@ -2,6 +2,7 @@ export 'collection_validators.dart'; export 'constants.dart'; export 'core_validators/core_validators.dart'; export 'datetime_validators.dart'; +export 'file_validators.dart'; export 'finance_validators.dart'; export 'generic_type_validators.dart'; export 'network_validators.dart'; diff --git a/test/src/validators/core_validators/equality_validators/is_equal_validator_test.dart b/test/src/validators/core_validators/equality_validators/equal_validator_test.dart similarity index 94% rename from test/src/validators/core_validators/equality_validators/is_equal_validator_test.dart rename to test/src/validators/core_validators/equality_validators/equal_validator_test.dart index b2fce6ec..0c133096 100644 --- a/test/src/validators/core_validators/equality_validators/is_equal_validator_test.dart +++ b/test/src/validators/core_validators/equality_validators/equal_validator_test.dart @@ -7,7 +7,7 @@ class _CustomClass {} void main() { final _CustomClass myObject = _CustomClass(); - group('Validator: isEqual', () { + group('Validator: equal', () { final List< ({ String description, @@ -85,7 +85,7 @@ void main() { testFails: bool testFails ) in testCases) { test(desc, () { - final Validator v = isEqual(referenceValue); + final Validator v = equal(referenceValue); expect( v(userInput), @@ -100,7 +100,7 @@ void main() { const String ref = 'hello'; const String customErrorMessage = 'custom error'; final Validator v = - isEqual(ref, isEqualMsg: (_, __) => customErrorMessage); + equal(ref, equalMsg: (_, __) => customErrorMessage); // success expect(v(ref), isNull); diff --git a/test/src/validators/core_validators/equality_validators/is_not_equal_validator_test.dart b/test/src/validators/core_validators/equality_validators/not_equal_validator_test.dart similarity index 94% rename from test/src/validators/core_validators/equality_validators/is_not_equal_validator_test.dart rename to test/src/validators/core_validators/equality_validators/not_equal_validator_test.dart index 2f844bd2..3ba03607 100644 --- a/test/src/validators/core_validators/equality_validators/is_not_equal_validator_test.dart +++ b/test/src/validators/core_validators/equality_validators/not_equal_validator_test.dart @@ -7,7 +7,7 @@ class _CustomClass {} void main() { final _CustomClass myObject = _CustomClass(); - group('Validator: isNoEqual', () { + group('Validator: notEqual', () { final List< ({ String description, @@ -88,7 +88,7 @@ void main() { testFails: bool testFails ) in testCases) { test(desc, () { - final Validator v = isNotEqual(referenceValue); + final Validator v = notEqual(referenceValue); expect( v(userInput), @@ -103,7 +103,7 @@ void main() { const String ref = 'hello'; const String customErrorMessage = 'custom error'; final Validator v = - isNotEqual(ref, isNotEqualMsg: (_, __) => customErrorMessage); + notEqual(ref, notEqualMsg: (_, __) => customErrorMessage); // success expect(v(123), isNull); diff --git a/test/src/validators/core_validators/required_validators/is_optional_validator_test.dart b/test/src/validators/core_validators/required_validators/optional_validator_test.dart similarity index 80% rename from test/src/validators/core_validators/required_validators/is_optional_validator_test.dart rename to test/src/validators/core_validators/required_validators/optional_validator_test.dart index 096f7e71..5249530f 100644 --- a/test/src/validators/core_validators/required_validators/is_optional_validator_test.dart +++ b/test/src/validators/core_validators/required_validators/optional_validator_test.dart @@ -11,9 +11,9 @@ void main() { final String defaultError = FormBuilderLocalizations.current.isOptionalErrorText(errorMultBy6); - group('Validator: isOptional', () { + group('Validator: optional', () { test('Should make the input optional', () { - final Validator v = isOptional(); + final Validator v = optional(); expect(v(null), isNull); expect(v(''), isNull); @@ -25,7 +25,7 @@ void main() { }); test('Should make the input optional with composed validator `v`', () { - final Validator v = isOptional(isMultipleBy6); + final Validator v = optional(isMultipleBy6); expect(v(null), isNull); expect(v(0), isNull); @@ -36,9 +36,8 @@ void main() { test('Should return custom message for invalid input', () { const String customMsg = 'custom error message '; - final Validator v = isOptional(null, (_, __) => customMsg); - final Validator v1 = - isOptional(isMultipleBy6, (_, __) => customMsg); + final Validator v = optional(null, (_, __) => customMsg); + final Validator v1 = optional(isMultipleBy6, (_, __) => customMsg); expect(v(null), isNull); expect(v(''), isNull); diff --git a/test/src/validators/core_validators/required_validators/is_required_validator_test.dart b/test/src/validators/core_validators/required_validators/required_validator_test.dart similarity index 83% rename from test/src/validators/core_validators/required_validators/is_required_validator_test.dart rename to test/src/validators/core_validators/required_validators/required_validator_test.dart index 2b0727fb..966889e1 100644 --- a/test/src/validators/core_validators/required_validators/is_required_validator_test.dart +++ b/test/src/validators/core_validators/required_validators/required_validator_test.dart @@ -11,9 +11,9 @@ void main() { final String defaultError = FormBuilderLocalizations.current.requiredErrorText; - group('Validator: isRequired', () { + group('Validator: required', () { test('Should check if the input value is not null/empty', () { - final Validator v = isRequired(); + final Validator v = required(); expect(v(null), defaultError); expect(v(''), defaultError); @@ -27,7 +27,7 @@ void main() { test( 'Should check if the input value is not null/empty with composed validator `v`', () { - final Validator v = isRequired(isMultipleBy6); + final Validator v = required(isMultipleBy6); expect(v(null), equals(defaultError)); expect(v(0), isNull); @@ -38,8 +38,8 @@ void main() { test('Should return custom message for null input', () { const String customMsg = 'custom error message '; - final Validator v = isRequired(null, customMsg); - final Validator v1 = isRequired(isMultipleBy6, customMsg); + final Validator v = required(null, customMsg); + final Validator v1 = required(isMultipleBy6, customMsg); expect(v(null), equals(customMsg)); expect(v(''), equals(customMsg)); diff --git a/test/src/validators/core_validators/type_validators_test.dart b/test/src/validators/core_validators/type_validators_test.dart index 0b73b806..826993c3 100644 --- a/test/src/validators/core_validators/type_validators_test.dart +++ b/test/src/validators/core_validators/type_validators_test.dart @@ -12,9 +12,9 @@ void main() { String? isLaterThan1995(DateTime input) => input.year > 1995 ? null : errorMsg; - group('Validator: isString', () { + group('Validator: string', () { test('Should only check if the input is a String', () { - final Validator v = isString(); + final Validator v = string(); expect( v(123), equals(FormBuilderLocalizations.current.isStringErrorText)); @@ -24,7 +24,7 @@ void main() { }); test('Should check if the input is a String with length greater than 3', () { - final Validator v = isString(hasLengthGreaterThan3); + final Validator v = string(hasLengthGreaterThan3); expect( v(123), equals(FormBuilderLocalizations.current.isStringErrorText)); @@ -34,7 +34,7 @@ void main() { }); test('Should check if the input is a String with using custom error', () { const String customError = 'custom error'; - final Validator v = isString(null, (_) => customError); + final Validator v = string(null, (_) => customError); expect(v(123), equals(customError)); expect(v('1234'), isNull); @@ -230,10 +230,10 @@ void main() { }); }); - group('Validator: isDateTime', () { + group('Validator: dateTime', () { test('Should only check if the input is an DateTime/parsable to DateTime', () { - final Validator v = isDateTime(); + final Validator v = dateTime(); expect(v('not an DateTime'), equals(FormBuilderLocalizations.current.dateTimeErrorText)); @@ -318,7 +318,7 @@ void main() { }); test('Should check if the input is a DateTime with year later than 1995', () { - final Validator v = isDateTime(isLaterThan1995); + final Validator v = dateTime(isLaterThan1995); expect(v('not a datetime'), equals(FormBuilderLocalizations.current.dateTimeErrorText)); @@ -327,7 +327,7 @@ void main() { test('Should check if the input is a DateTime using custom error', () { const String customError = 'custom error'; - final Validator v = isDateTime(null, (_) => customError); + final Validator v = dateTime(null, (_) => customError); expect(v('not datetime'), equals(customError)); expect(v('1289-02-12'), isNull); diff --git a/test/src/validators/datetime_validators/is_after_validator_test.dart b/test/src/validators/datetime_validators/after_validator_test.dart similarity index 94% rename from test/src/validators/datetime_validators/is_after_validator_test.dart rename to test/src/validators/datetime_validators/after_validator_test.dart index a833f816..a392daa5 100644 --- a/test/src/validators/datetime_validators/is_after_validator_test.dart +++ b/test/src/validators/datetime_validators/after_validator_test.dart @@ -7,7 +7,7 @@ void main() { setUpAll(() async { await initializeDateFormatting('en', null); }); - group('Validator: isAfter', () { + group('Validator: after', () { test('Validation for the year 1994', () { final DateTime reference = DateTime(1994); final DateTime eq = reference.copyWith(); @@ -16,7 +16,7 @@ void main() { final DateTime before1Year = DateTime(1993); final DateTime before1Sec = reference.subtract(const Duration(seconds: 1)); - final Validator v = isAfter(reference); + final Validator v = after(reference); final String errorMsg = FormBuilderLocalizations.current.dateMustBeAfterErrorText(reference); @@ -56,7 +56,7 @@ void main() { final DateTime before1Year = reference.copyWith(year: 2088); final DateTime before1Sec = reference.subtract(const Duration(seconds: 1)); - final Validator v = isAfter(reference, inclusive: true); + final Validator v = after(reference, inclusive: true); final String errorMsg = FormBuilderLocalizations.current.dateMustBeAfterErrorText(reference); @@ -93,7 +93,7 @@ void main() { const String errorMsg = 'error msg'; final DateTime reference = DateTime(2); final Validator v = - isAfter(reference, isAfterMsg: (_, __) => errorMsg); + after(reference, afterMsg: (_, __) => errorMsg); expect( v(reference.copyWith()), diff --git a/test/src/validators/datetime_validators/is_before_validator_test.dart b/test/src/validators/datetime_validators/before_validator_test.dart similarity index 93% rename from test/src/validators/datetime_validators/is_before_validator_test.dart rename to test/src/validators/datetime_validators/before_validator_test.dart index 219f3111..3b7d4f73 100644 --- a/test/src/validators/datetime_validators/is_before_validator_test.dart +++ b/test/src/validators/datetime_validators/before_validator_test.dart @@ -7,7 +7,7 @@ void main() { setUpAll(() async { await initializeDateFormatting('en', null); }); - group('Validator: isBefore', () { + group('Validator: before', () { test('Validation for the year 1994', () { final DateTime reference = DateTime(1994); final DateTime eq = reference.copyWith(); @@ -16,7 +16,7 @@ void main() { final DateTime before1Year = DateTime(1993); final DateTime before1Sec = reference.subtract(const Duration(seconds: 1)); - final Validator v = isBefore(reference); + final Validator v = before(reference); final String errorMsg = FormBuilderLocalizations.current.dateMustBeBeforeErrorText(reference); @@ -56,7 +56,7 @@ void main() { final DateTime before1Year = reference.copyWith(year: 2088); final DateTime before1Sec = reference.subtract(const Duration(seconds: 1)); - final Validator v = isBefore(reference, inclusive: true); + final Validator v = before(reference, inclusive: true); final String errorMsg = FormBuilderLocalizations.current.dateMustBeBeforeErrorText(reference); @@ -93,7 +93,7 @@ void main() { const String errorMsg = 'error msg'; final DateTime reference = DateTime(2); final Validator v = - isBefore(reference, isBeforeMsg: (_, __) => errorMsg); + before(reference, beforeMsg: (_, __) => errorMsg); expect( v(reference.copyWith()), diff --git a/test/src/validators/datetime_validators/is_datetime_between_validator_test.dart b/test/src/validators/datetime_validators/datetime_between_validator_test.dart similarity index 94% rename from test/src/validators/datetime_validators/is_datetime_between_validator_test.dart rename to test/src/validators/datetime_validators/datetime_between_validator_test.dart index d478798a..a944dcec 100644 --- a/test/src/validators/datetime_validators/is_datetime_between_validator_test.dart +++ b/test/src/validators/datetime_validators/datetime_between_validator_test.dart @@ -7,7 +7,7 @@ void main() { setUpAll(() async { await initializeDateFormatting('en', null); }); - group('Validator: isDateTimeBetween', () { + group('Validator: dateTimeBetween', () { test('Validation for the range 1994 and 1997', () { final DateTime leftReference = DateTime(1994); final DateTime rightReference = DateTime(1997); @@ -25,7 +25,7 @@ void main() { final DateTime beforeLeft1Micro = leftReference.subtract(const Duration(microseconds: 1)); final Validator v = - isDateTimeBetween(leftReference, rightReference); + betweenDateTime(leftReference, rightReference); final String errorMsg = FormBuilderLocalizations.current .dateMustBeBetweenErrorText(leftReference, rightReference); @@ -91,7 +91,7 @@ void main() { final DateTime before1Sec = leftReference.subtract(const Duration(seconds: 1)); final Validator v = - isDateTimeBetween(leftReference, rightReference, minInclusive: true); + betweenDateTime(leftReference, rightReference, minInclusive: true); final String errorMsg = FormBuilderLocalizations.current .dateMustBeBetweenErrorText(leftReference, rightReference); @@ -145,9 +145,9 @@ void main() { const String errorMsg = 'error msg'; final DateTime leftReference = DateTime(2); final DateTime rightReference = DateTime(5); - final Validator v = isDateTimeBetween( + final Validator v = betweenDateTime( leftReference, rightReference, - isDateTimeBetweenMsg: (_, __, ___) => errorMsg); + betweenDateTimeMsg: (_, __, ___) => errorMsg); expect( v(rightReference.copyWith()), @@ -173,7 +173,7 @@ void main() { 'Should throw AssertionError when the right reference is not after left reference', () { expect( - () => isDateTimeBetween( + () => betweenDateTime( DateTime(1990, 12, 23, 20), DateTime(1990, 12, 22, 20)), throwsAssertionError); }); diff --git a/test/src/validators/file_validators/max_file_size_validator_test.dart b/test/src/validators/file_validators/max_file_size_validator_test.dart new file mode 100644 index 00000000..39c5ef0d --- /dev/null +++ b/test/src/validators/file_validators/max_file_size_validator_test.dart @@ -0,0 +1,269 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; +import 'package:intl/intl.dart'; + +void main() { + final List formattingMask = [ + NumberFormat("#,##0.#"), + NumberFormat("#,##0,000.#"), + NumberFormat("#,##0,000,000.#"), + NumberFormat("#,##0,000,000,000.#"), + NumberFormat("#,##0,000,000,000,000.#"), + ]; + group('Validator: maxFileSize', () { + final List< + ({ + Base base, + ({String input, String max})? expected, + int input, + int max + })> testCases = <({ + int max, + int input, + Base base, + ({String input, String max})? expected + })>[ + ( + max: 1000, + input: 1000, + base: Base.b1000, + expected: null, + ), + ( + max: 1024, + input: 1024, + base: Base.b1024, + expected: null, + ), + ( + max: 1024, + input: 512, + base: Base.b1024, + expected: null, + ), + ( + max: 1024, + input: 1025, + base: Base.b1024, + expected: ( + max: '${formattingMask[1].format(1024)} B', + input: '${formattingMask[1].format(1025)} B' + ), + ), + ( + max: 1024 * 1024, + input: 1025 * 1024, + base: Base.b1024, + expected: ( + max: '${formattingMask[1].format(1024)} KiB', + input: '${formattingMask[1].format(1025)} KiB' + ), + ), + ( + max: 5_000_000_000_000, + input: 5_000_000_000_234, + base: Base.b1000, + expected: ( + max: '${formattingMask[3].format(5_000_000_000.0)} KB', + input: '${formattingMask[3].format(5_000_000_000.2)} KB' + ), + ), + ( + max: 5_000_000_000_234, + input: 5_000_000_000_234, + base: Base.b1000, + expected: null, + ), + ( + max: 5_000_000_000_234, + input: 5_000_000_000_233, + base: Base.b1000, + expected: null, + ), + ( + max: 5_000_000_000_000, + input: 5_000_000_000_034, + base: Base.b1000, + expected: ( + max: '${formattingMask[4].format(5_000_000_000_000)} B', + input: '${formattingMask[4].format(5_000_000_000_034)} B' + ), + ), + ]; + + for (final ( + max: int max, + input: int input, + base: Base base, + expected: ({String input, String max})? expected + ) in testCases) { + test( + 'should ${expected == null ? 'match' : 'return error'} when max is $max B, input is $input B, and base ${base.base}', + () { + expect( + maxFileSize(max, base: base)(input), + expected == null + ? isNull + : stringContainsInOrder( + [expected.max, expected.input])); + }); + } + + test('should return the custom error msg', () { + final Validator v = maxFileSize(12, + maxFileSizeMsg: (_, int max, __) => + 'File size should be less than $max'); + + expect(v(10), isNull); + expect(v(12), isNull); + expect(v(13), equals('File size should be less than 12')); + }); + }); + + group('Helper function: formatBoth', () { + final List<({Base base, (String, String) expected, int v1, int v2})> + testCases = <({Base base, (String, String) expected, int v1, int v2})>[ + ( + v1: 1024, + v2: 1025, + base: Base.b1000, + expected: ( + '${formattingMask[1].format(1024)} B', + '${formattingMask[1].format(1025)} B' + ) + ), + ( + v1: 1024, + v2: 1025, + base: Base.b1024, + expected: ( + '${formattingMask[1].format(1024)} B', + '${formattingMask[1].format(1025)} B' + ) + ), + ( + v1: 1024, + v2: 1023, + base: Base.b1024, + expected: ( + '${formattingMask[0].format(1.0)} KiB', + '${formattingMask[1].format(1023)} B' + ) + ), + (v1: 1, v2: 2, base: Base.b1000, expected: ('1 B', '2 B')), + (v1: 1, v2: 2, base: Base.b1024, expected: ('1 B', '2 B')), + ( + v1: 10_000, + v2: 12_123, + base: Base.b1000, + expected: ( + '${formattingMask[0].format(10.0)} KB', + '${formattingMask[0].format(12.1)} KB' + ) + ), + ( + v1: 10_000, + v2: 12_123, + base: Base.b1024, + expected: ( + '${formattingMask[0].format(9.8)} KiB', + '${formattingMask[0].format(11.8)} KiB' + ) + ), + ( + v1: 12_124_000, + v2: 12_123_000, + base: Base.b1000, + expected: ( + '${formattingMask[1].format(12_124.0)} KB', + '${formattingMask[1].format(12_123.0)} KB' + ) + ), + ( + v1: 12_124_000, + v2: 12_123_000, + base: Base.b1024, + expected: ( + '${formattingMask[1].format(11_839.84)} KiB', + '${formattingMask[1].format(11_838.87)} KiB' + ) + ), + ( + v1: 12_100_000, + v2: 12_199_999, + base: Base.b1000, + expected: ( + '${formattingMask[0].format(12.1)} MB', + '${formattingMask[0].format(12.2)} MB' + ) + ), + ( + v1: 12_100_000, + v2: 12_199_999, + base: Base.b1024, + expected: ( + '${formattingMask[0].format(11.53)} MiB', + '${formattingMask[0].format(11.63)} MiB' + ) + ), + ( + v1: 9_100_000_000, + v2: 9_100_300_001, + base: Base.b1000, + expected: ( + '${formattingMask[1].format(9_100.0)} MB', + '${formattingMask[1].format(9_100.3)} MB' + ) + ), + ( + v1: 9_100_000_000, + v2: 9_100_000_001, + base: Base.b1024, + expected: ( + '${formattingMask[3].format(9_100_000_000)} B', + '${formattingMask[3].format(9_100_000_001)} B' + ) + ), + ( + v1: 999_231_665_990_003, + v2: 999_231_665_990_004, + base: Base.b1000, + expected: ( + '${formattingMask[4].format(999_231_665_990_003)} B', + '${formattingMask[4].format(999_231_665_990_004)} B' + ) + ), + ( + v1: 999_231_665_990_003, + v2: 999_231_665_990_004, + base: Base.b1024, + expected: ( + '${formattingMask[4].format(999_231_665_990_003)} B', + '${formattingMask[4].format(999_231_665_990_004)} B' + ) + ), + ( + v1: 12 * 1024 * 1024, + v2: 23 * 1024, + base: Base.b1024, + expected: ( + '${formattingMask[0].format(12)} MiB', + '${formattingMask[0].format(23)} KiB' + ) + ), + ]; + + for (final ( + v1: int v1, + v2: int v2, + base: Base base, + expected: (String, String) expected + ) in testCases) { + test( + 'should return $expected when v1:$v1, v2:$v2, and base: ${base.base}', + () { + expect(formatBoth(v1, v2, base).toString(), expected.toString()); + }); + } + }); +} diff --git a/test/src/validators/finance_validators/bic_validator_test.dart b/test/src/validators/finance_validators/bic_validator_test.dart new file mode 100644 index 00000000..c9334890 --- /dev/null +++ b/test/src/validators/finance_validators/bic_validator_test.dart @@ -0,0 +1,50 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/finance_validators.dart'; + +void main() { + group('Validator: bic', () { + final List<({bool fails, String input})> testCases = + <({bool fails, String input})>[ + ( + input: '', + fails: true, + ), + ( + input: 'DEUTDEFF', + fails: false, + ), + ( + input: 'DEUTDEFF500', + fails: false, + ), + ( + input: 'INVALIDBIC', + fails: true, + ), + ]; + + final Validator v = bic(); + for (final (input: String input, fails: bool fails) in testCases) { + test('should ${fails ? 'fail' : 'pass'} for input "$input"', () { + expect(v(input), + fails ? FormBuilderLocalizations.current.bicErrorText : isNull); + }); + } + + test('should return custom error msg', () { + final Validator v = bic(bicMsg: (_) => 'error text'); + + expect(v('BOTKJPJTXXX'), isNull); + expect(v('BOTKJPJTXX'), equals('error text')); + }); + + test('should use the custom logic to check if the input is a valid bic', + () { + final Validator v = + bic(isBic: (String input) => input.length == 3); + expect(v('abc'), isNull); + expect(v('abc '), equals(FormBuilderLocalizations.current.bicErrorText)); + }); + }); +} diff --git a/test/src/validators/finance_validators/iban_validator_test.dart b/test/src/validators/finance_validators/iban_validator_test.dart new file mode 100644 index 00000000..f7cfa960 --- /dev/null +++ b/test/src/validators/finance_validators/iban_validator_test.dart @@ -0,0 +1,105 @@ +import 'package:faker_dart/faker_dart.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/localization/l10n.dart'; +import 'package:form_builder_validators/src/validators/constants.dart'; +import 'package:form_builder_validators/src/validators/finance_validators.dart'; + +void main() { + final Faker faker = Faker.instance; + final String customErrorMessage = faker.lorem.sentence(); + + group('IbanValidator -', () { + test('should return null if the IBAN is valid', () { + // Arrange + final Validator validator = iban(); + const String validIban = 'DE89370400440532013000'; + + // Act + final String? result = validator(validIban); + + // Assert + expect(result, isNull); + }); + + test('should return null if the IBAN is valid with spaces', () { + // Arrange + final Validator validator = iban(); + const String validIban = 'DE89 3704 0044 0532 0130 00'; + + // Act + final String? result = validator(validIban); + + // Assert + expect(result, isNull); + }); + + test('should return the default error message if the IBAN is invalid', () { + // Arrange + final Validator validator = iban(); + const String invalidIban = 'DE89370400440532013001'; + + // Act + final String? result = validator(invalidIban); + + // Assert + expect(result, equals(FormBuilderLocalizations.current.ibanErrorText)); + }); + + test( + 'should return the custom error message if the IBAN Validator is invalid', + () { + // Arrange + final Validator validator = + iban(ibanMsg: (_) => customErrorMessage); + const String invalidIban = 'DE89370400440532013001'; + + // Act + final String? result = validator(invalidIban); + + // Assert + expect(result, equals(customErrorMessage)); + }); + + test( + 'should return the default error message if the value is an empty string', + () { + // Arrange + final Validator validator = iban(); + const String value = ''; + + // Act + final String? result = validator(value); + + // Assert + expect(result, equals(FormBuilderLocalizations.current.ibanErrorText)); + }); + + test( + 'should return the default error message if the IBAN length is less than 15 characters', + () { + // Arrange + final Validator validator = iban(); + const String shortIban = 'DE8937040044'; + + // Act + final String? result = validator(shortIban); + + // Assert + expect(result, equals(FormBuilderLocalizations.current.ibanErrorText)); + }); + + test( + 'should return null if the IBAValidator N length is exactly 15 characters and valid', + () { + // Arrange + final Validator validator = iban(); + const String validShortIban = 'AL47212110090000000235698741'; + + // Act + final String? result = validator(validShortIban); + + // Assert + expect(result, isNull); + }); + }); +} diff --git a/test/src/validators/generic_type_validators/contains_element_validator_test.dart b/test/src/validators/generic_type_validators/in_list_validator_test.dart similarity index 82% rename from test/src/validators/generic_type_validators/contains_element_validator_test.dart rename to test/src/validators/generic_type_validators/in_list_validator_test.dart index 29e92351..cc784320 100644 --- a/test/src/validators/generic_type_validators/contains_element_validator_test.dart +++ b/test/src/validators/generic_type_validators/in_list_validator_test.dart @@ -3,17 +3,17 @@ import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:form_builder_validators/src/validators/validators.dart'; void main() { - group('Validator: containsElement', () { + group('Validator: isInList', () { group('Validations with default error message', () { test('Should return null when int 0 is provided', () { - final Validator validator = containsElement([0]); + final Validator validator = inList([0]); expect(validator(0), isNull); expect(validator(2), equals(FormBuilderLocalizations.current.containsElementErrorText)); }); test('Should return null when int 0 or String "2" is provided', () { - final Validator validator = containsElement([0, '2']); + final Validator validator = inList([0, '2']); expect(validator(0), isNull); expect(validator('2'), isNull); @@ -21,8 +21,7 @@ void main() { equals(FormBuilderLocalizations.current.containsElementErrorText)); }); test('Should return null when int 0, int 2, or null is provided', () { - final Validator validator = - containsElement([0, 2, null]); + final Validator validator = inList([0, 2, null]); expect(validator(0), isNull); expect(validator(2), isNull); @@ -33,14 +32,14 @@ void main() { }); test('Should throw ArgumentError when list input is empty', () { - expect(() => containsElement([]), throwsArgumentError); + expect(() => inList([]), throwsArgumentError); }); test('Should return custom error message when invalid input is provided', () { const String customMessage = 'custom message'; - final Validator validator = containsElement([1, 2, 3], - containsElementMsg: (_, __) => customMessage); + final Validator validator = + inList([1, 2, 3], inListMsg: (_, __) => customMessage); expect(validator(4), equals(customMessage)); }); @@ -48,7 +47,7 @@ void main() { test('should remain immutable when input elements change', () { final List elements = [12, 15, 'hi']; - final Validator v = containsElement(elements); + final Validator v = inList(elements); expect(v(12), isNull); expect(v(15), isNull); diff --git a/test/src/validators/generic_type_validators/not_in_list_validator_test.dart b/test/src/validators/generic_type_validators/not_in_list_validator_test.dart new file mode 100644 index 00000000..3daefcb3 --- /dev/null +++ b/test/src/validators/generic_type_validators/not_in_list_validator_test.dart @@ -0,0 +1,91 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + group('Validator: notInList', () { + group('Validations with default error message', () { + test('Should return null when int 0 is provided', () { + final Validator validator = notInList([2]); + + expect(validator(0), isNull); + expect( + validator(2), + equals(FormBuilderLocalizations + .current.doesNotContainElementErrorText)); + }); + test('Should return null when int 0 or String "2" is provided', () { + final Validator validator = notInList(['0', 2]); + + expect(validator(0), isNull); + expect(validator('2'), isNull); + expect( + validator(2), + equals(FormBuilderLocalizations + .current.doesNotContainElementErrorText)); + }); + test('Should return null when int 0, int 2, or null is provided', () { + final Validator validator = notInList(['0', '2']); + + expect(validator(0), isNull); + expect(validator(2), isNull); + expect(validator(null), isNull); + expect( + validator('2'), + equals(FormBuilderLocalizations + .current.doesNotContainElementErrorText)); + }); + }); + + test('Should throw ArgumentError when list input is empty', () { + expect(() => notInList([]), throwsArgumentError); + }); + + test('Should return custom error message when invalid input is provided', + () { + const String customMessage = 'custom message'; + final Validator validator = + notInList([1, 2, 3], notInListMsg: (_, __) => customMessage); + + expect(validator(1), equals(customMessage)); + }); + + test('should remain immutable when input elements change', () { + final List elements = [12.02, 2, true]; + + final Validator v = notInList(elements); + + expect(v(12), isNull); + expect(v(15), isNull); + expect(v('hi'), isNull); + expect(v(12.02), + FormBuilderLocalizations.current.doesNotContainElementErrorText); + expect(v(2), + FormBuilderLocalizations.current.doesNotContainElementErrorText); + expect(v(true), + FormBuilderLocalizations.current.doesNotContainElementErrorText); + + elements.removeLast(); + expect(v(12), isNull); + expect(v(15), isNull); + expect(v('hi'), isNull); + expect(v(12.02), + FormBuilderLocalizations.current.doesNotContainElementErrorText); + expect(v(2), + FormBuilderLocalizations.current.doesNotContainElementErrorText); + expect(v(true), + FormBuilderLocalizations.current.doesNotContainElementErrorText); + + elements.add(true); + expect(v(12), isNull); + expect(v(15), isNull); + expect(v('hi'), isNull); + expect(v(12.02), + FormBuilderLocalizations.current.doesNotContainElementErrorText); + expect(v(2), + FormBuilderLocalizations.current.doesNotContainElementErrorText); + expect(v(true), + FormBuilderLocalizations.current.doesNotContainElementErrorText); + }); + }); +}