From 09a45147bcfc15329ae78795e243eb60efee211b Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Thu, 6 Feb 2025 10:55:46 -0300 Subject: [PATCH 01/27] first commit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d8b53e1..056058a5 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. From 960707b99a79a8f7eeff48519b6763c384e003c5 Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Mon, 10 Feb 2025 11:29:42 -0300 Subject: [PATCH 02/27] add how it works section --- README-updated.md | 518 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 518 insertions(+) create mode 100644 README-updated.md diff --git a/README-updated.md b/README-updated.md new file mode 100644 index 00000000..6d33646a --- /dev/null +++ b/README-updated.md @@ -0,0 +1,518 @@ +# 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) + - [Bool validators](#bool-validators) + - [Collection validators](#collection-validators) + - [Core validators](#core-validators) + - [Datetime validators](#datetime-validators) + - [File validators](#file-validators) + - [Finance validators](#finance-validators) + - [Identity validators](#identity-validators) + - [Network validators](#network-validators) + - [Numeric validators](#numeric-validators) + - [String validators](#string-validators) + - [Use-case validators](#use-case-validators) + - [Extension method validators](#extension-method-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 does 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, mail, +URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit card, etc., with default `errorText` messages. + + +### Bool validators + +- `FormBuilderValidators.hasLowercaseChars()` - requires the field's to contain a specified number of lowercase characters. +- `FormBuilderValidators.hasNumericChars()` - requires the field's to contain a specified number of numeric characters. +- `FormBuilderValidators.hasSpecialChars()` - requires the field's to contain a specified number of special characters. +- `FormBuilderValidators.hasUppercaseChars()` - requires the field's to contain a specified number of uppercase characters. +- `FormBuilderValidators.isFalse()` - requires the field's to be false. +- `FormBuilderValidators.isTrue()` - requires the field's to be true. + +### Collection validators + +- `FormBuilderValidators.containsElement()` - requires the field's to be in the provided list. +- `FormBuilderValidators.equalLength()` - requires the length of the field's value to be equal to the provided minimum length. +- `FormBuilderValidators.maxLength()` - requires the length of the field's value to be less than or equal to the provided maximum size. +- `FormBuilderValidators.minLength()` - requires the length of the field's value to be greater than or equal to the provided minimum length. +- `FormBuilderValidators.range()` - requires the field's to be within a range. +- `FormBuilderValidators.unique()` - requires the field's to be unique in the provided list. + +### Core validators + +- `FormBuilderValidators.aggregate()` - runs the validators in parallel, collecting all errors. +- `FormBuilderValidators.compose()` - runs each validator against the value provided. +- `FormBuilderValidators.conditional()` - conditionally runs a validator against the value provided. +- `FormBuilderValidators.defaultValue()` - runs the validator using the default value when the provided value is null. +- `FormBuilderValidators.equal()` - requires the field's value to be equal to the provided object. +- `FormBuilderValidators.log()` - runs the validator and logs the value at a specific point in the validation chain. +- `FormBuilderValidators.notEqual()` - requires the field's value to be not equal to the provided object. +- `FormBuilderValidators.or()` - runs each validator against the value provided and passes when any works. +- `FormBuilderValidators.required()` - requires the field to have a non-empty value. +- `FormBuilderValidators.skipWhen()` - runs the validator and skips the validation when a certain condition is met. +- `FormBuilderValidators.transform()` - transforms the value before running the validator. + +### Datetime validators + +- `FormBuilderValidators.dateFuture()` - requires the field's value to be in the future. +- `FormBuilderValidators.datePast()` - requires the field's value to be a in the past. +- `FormBuilderValidators.dateRange()` - requires the field's value to be a within a date range. +- `FormBuilderValidators.dateTime()` - requires the field's value to be a valid date time. +- `FormBuilderValidators.date()` - requires the field's value to be a valid date string. +- `FormBuilderValidators.time()` - requires the field's value to be a valid time string. +- `FormBuilderValidators.timeZone()` - requires the field's value to be a valid time zone. + +### File validators + +- `FormBuilderValidators.fileExtension()` - requires the field's value to a valid file extension. +- `FormBuilderValidators.fileName()` - requires the field's to be a valid file name. +- `FormBuilderValidators.fileSize()` - requires the field's to be less than the max size. +- `FormBuilderValidators.mimeType()` - requires the field's value to a valid MIME type. +- `FormBuilderValidators.path()` - requires the field's to be a valid file or folder path. + +### Finance validators + +- `FormBuilderValidators.bic()` - requires the field's to be a valid BIC. +- `FormBuilderValidators.creditCardCVC()` - requires the field's value to be a valid credit card CVC number. +- `FormBuilderValidators.creditCardExpirationDate()` - requires the field's value to be a valid credit card expiration date and can check if not expired yet. +- `FormBuilderValidators.creditCard()` - requires the field's value to be a valid credit card number. +- `FormBuilderValidators.iban()` - requires the field's to be a valid IBAN. + +### Identity validators + +- `FormBuilderValidators.city()` - requires the field's value to be a valid city name. +- `FormBuilderValidators.country()` - requires the field's value to be a valid country name. +- `FormBuilderValidators.firstName()` - requires the field's value to be a valid first name. +- `FormBuilderValidators.lastName()` - requires the field's value to be a valid last name. +- `FormBuilderValidators.passportNumber()` - requires the field's value to be a valid passport number. +- `FormBuilderValidators.password()` - requires the field's to be a valid password that matched required conditions. +- `FormBuilderValidators.ssn()` - requires the field's to be a valid SSN (Social Security Number). +- `FormBuilderValidators.state()` - requires the field's value to be a valid state name. +- `FormBuilderValidators.street()` - requires the field's value to be a valid street name. +- `FormBuilderValidators.username()` - requires the field's to be a valid username that matched required conditions. +- `FormBuilderValidators.zipCode()` - requires the field's to be a valid zip code. + +### Network validators + +- `FormBuilderValidators.email()` - requires the field's value to be a valid email address. +- `FormBuilderValidators.ip()` - requires the field's value to be a valid IP address. +- `FormBuilderValidators.latitude()` - requires the field's to be a valid latitude. +- `FormBuilderValidators.longitude()` - requires the field's to be a valid longitude. +- `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. + +## 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`. + +```Dart +return MaterialApp( + supportedLocales: [ + Locale('de'), + Locale('en'), + Locale('es'), + Locale('fr'), + Locale('it'), + ... + ], + localizationsDelegates: [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + FormBuilderLocalizations.delegate, + ], +``` + +### Basic use + +```Dart +TextFormField( + decoration: InputDecoration(labelText: 'Name'), + autovalidateMode: AutovalidateMode.always, + validator: FormBuilderValidators.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 + +The `FormBuilderValidators` class comes with a handy static function named `compose()`, which takes a list of `FormFieldValidator` 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 returned string. + +Example: + +```Dart +TextFormField( + decoration: InputDecoration(labelText: 'Age'), + keyboardType: TextInputType.number, + autovalidateMode: AutovalidateMode.always, + validator: FormBuilderValidators.compose([ + /// Makes this field required + FormBuilderValidators.required(), + + /// Ensures the value entered is numeric - with a custom error message + FormBuilderValidators.numeric(errorText: 'La edad debe ser numérica.'), + + /// Sets a maximum value of 70 + FormBuilderValidators.max(70), + + /// Include your own custom `FormFieldValidator` function, if you want + /// Ensures positive values only. We could also have used `FormBuilderValidators.min(0)` instead + (val) { + final number = int.tryParse(val); + 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 + +### 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 `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. + 1. Code duplication: When composing validators, the same null-checking logic must be repeated + across multiple validators, violating the DRY principle. + 1. 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. + 1. 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. + 1. 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?) isRequired(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 isRequired(isEven()), does. +final validator = isRequired(isEven()); +``` + +By introducing this level of indirection, we achieve: + 1. Clean separation between null checks and validation logic + 1. More composable validators + 1. Specific error messages for missing vs invalid input + 1. 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` folder. +2. Implement it using the `BaseValidator` or `TranslatedValidator` class. Override the `validateValue` method and let the base class handle the null check in the `validate` method. +3. When using a `TranslatedValidator, Override the `translatedErrorText` property and return the correct translation from `FormBuilderLocalizations.current.`. +4. Make sure to pass `errorText` and `checkNullOrEmpty` to the base class. +5. Add static method to `form_builder_validators.dart` that uses the new validator. +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) + + +# API changes draft +During the process of exploration of new possibilities for the new API, I realized that there are +basically three layers of validators: required layer, type layer and the specialized layer. Instead of +repeating the computations for required and type layer for each validator composition, it is possible +to decouple them, avoiding this redundancy and taking benefits from the Dart compiler. + +During the exploration, I implemented some elementary validators that would make it possible, by +composition, to create more sophisticated validators. The recipe is simple, start with a (not)required +validator, add a type validator, and then chain as many specialized validators as you want. + +```dart +// In this example, we build a validator composing a required, with a numeric and then a max. +// The logic result is: required && numeric && max(70) + +final validator = ValidatorBuilder.required(and: >[ + ValidatorBuilder.numeric( + errorText: 'La edad debe ser numérica.', + and: >[ + ValidatorBuilder.max(70), + ]) + ]).validate; +``` + +I needed to change a little bit the approach. Instead of composing directly the validators as +FormFieldValidator's, one level of indirection was necessary, using a ValidatorBuilder instead. +Thus, we first build the validator and then create the validation method calling validate. + +I implemented some examples that are related to some examples from example/main.dart. The new +API examples are implemented in example/api_refactoring_main.dart. I recorded a video showing the +execution of the examples and explaining the new api ideas. + +Please, give me the necessary feedback for me to continue the work. From ddd9cbd658943f12ffd4ff565fd7707744a3d1e1 Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Tue, 11 Feb 2025 20:00:13 -0300 Subject: [PATCH 03/27] update string session --- README-updated.md | 65 ++++---------- lib/src/form_builder_validators.dart | 104 +++++++++++----------- lib/src/validators/string_validators.dart | 4 +- 3 files changed, 71 insertions(+), 102 deletions(-) diff --git a/README-updated.md b/README-updated.md index 6d33646a..389580c9 100644 --- a/README-updated.md +++ b/README-updated.md @@ -60,16 +60,12 @@ Also it includes the `l10n` / `i18n` of error text messages to multiple language ## Validators -This package comes with several most common `FormFieldValidator`s such as required, numeric, mail, +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. ### Bool validators -- `FormBuilderValidators.hasLowercaseChars()` - requires the field's to contain a specified number of lowercase characters. -- `FormBuilderValidators.hasNumericChars()` - requires the field's to contain a specified number of numeric characters. -- `FormBuilderValidators.hasSpecialChars()` - requires the field's to contain a specified number of special characters. -- `FormBuilderValidators.hasUppercaseChars()` - requires the field's to contain a specified number of uppercase characters. - `FormBuilderValidators.isFalse()` - requires the field's to be false. - `FormBuilderValidators.isTrue()` - requires the field's to be true. @@ -163,17 +159,22 @@ URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit ca ### 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. +- `Validators.contains(substring)` - Checks if the field contains the `substring`. +- TODO `FormBuilderValidators.endsWith()` - requires the substring 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.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. +- `Validators.hasMinLowercaseChars(min:min)` - Checks if the field has a minimum number of lowercase chars. +- `Validators.hasMinNumericChars(min:min)` - Checks if the field has a minimum number of numeric chars. +- `Validators.hasMinSpecialChars(min:min)` - Checks if the field has a minimum number of special chars. +- `Validators.match(regExp)` - Checks if the field matches with the regular expression `regExp`. +- TODO `FormBuilderValidators.matchNot()` - requires the field's value to not match the provided regex pattern. +- `Validators.uuid()` - Checks if the field is a valid Universally Unique Identifier (UUID). +- TODO `FormBuilderValidators.alphabetical()` - requires the field's to contain only alphabetical characters. +- 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. +- TODO `FormBuilderValidators.singleLine()` - requires the field's string to be a single line of text. ### Use-case validators @@ -484,35 +485,3 @@ Take a look at [our fantastic ecosystem](https://github.com/flutter-form-builder [All contributors](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/graphs/contributors) -# API changes draft -During the process of exploration of new possibilities for the new API, I realized that there are -basically three layers of validators: required layer, type layer and the specialized layer. Instead of -repeating the computations for required and type layer for each validator composition, it is possible -to decouple them, avoiding this redundancy and taking benefits from the Dart compiler. - -During the exploration, I implemented some elementary validators that would make it possible, by -composition, to create more sophisticated validators. The recipe is simple, start with a (not)required -validator, add a type validator, and then chain as many specialized validators as you want. - -```dart -// In this example, we build a validator composing a required, with a numeric and then a max. -// The logic result is: required && numeric && max(70) - -final validator = ValidatorBuilder.required(and: >[ - ValidatorBuilder.numeric( - errorText: 'La edad debe ser numérica.', - and: >[ - ValidatorBuilder.max(70), - ]) - ]).validate; -``` - -I needed to change a little bit the approach. Instead of composing directly the validators as -FormFieldValidator's, one level of indirection was necessary, using a ValidatorBuilder instead. -Thus, we first build the validator and then create the validation method calling validate. - -I implemented some examples that are related to some examples from example/main.dart. The new -API examples are implemented in example/api_refactoring_main.dart. I recorded a video showing the -execution of the examples and explaining the new api ideas. - -Please, give me the necessary feedback for me to continue the work. diff --git a/lib/src/form_builder_validators.dart b/lib/src/form_builder_validators.dart index d24dcbba..b2c48c2f 100644 --- a/lib/src/form_builder_validators.dart +++ b/lib/src/form_builder_validators.dart @@ -2659,56 +2659,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. @@ -3054,10 +3004,60 @@ 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_min_length} 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; From 74dbe1afedd751265691430b8730efc17577b746 Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Tue, 11 Feb 2025 20:21:42 -0300 Subject: [PATCH 04/27] add user information, network, and finance validators --- README-updated.md | 34 ++++++----- lib/src/form_builder_validators.dart | 89 ++++++++++++++-------------- 2 files changed, 64 insertions(+), 59 deletions(-) diff --git a/README-updated.md b/README-updated.md index 389580c9..803c0e27 100644 --- a/README-updated.md +++ b/README-updated.md @@ -112,11 +112,11 @@ URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit ca ### Finance validators -- `FormBuilderValidators.bic()` - requires the field's to be a valid BIC. -- `FormBuilderValidators.creditCardCVC()` - requires the field's value to be a valid credit card CVC number. -- `FormBuilderValidators.creditCardExpirationDate()` - requires the field's value to be a valid credit card expiration date and can check if not expired yet. -- `FormBuilderValidators.creditCard()` - requires the field's value to be a valid credit card number. -- `FormBuilderValidators.iban()` - requires the field's to be a valid IBAN. +- TODO `FormBuilderValidators.bic()` - requires the field's to be a valid BIC. +- TODO `FormBuilderValidators.creditCardCVC()` - requires the field's value to be a valid credit card CVC number. +- TODO `FormBuilderValidators.creditCardExpirationDate()` - requires the field's value to be a valid credit card expiration date and can check if not expired yet. +- `Validators.creditCard()`: Checks if the field contains a valid credit card number. +- TODO `FormBuilderValidators.iban()` - requires the field's to be a valid IBAN. ### Identity validators @@ -125,7 +125,6 @@ URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit ca - `FormBuilderValidators.firstName()` - requires the field's value to be a valid first name. - `FormBuilderValidators.lastName()` - requires the field's value to be a valid last name. - `FormBuilderValidators.passportNumber()` - requires the field's value to be a valid passport number. -- `FormBuilderValidators.password()` - requires the field's to be a valid password that matched required conditions. - `FormBuilderValidators.ssn()` - requires the field's to be a valid SSN (Social Security Number). - `FormBuilderValidators.state()` - requires the field's value to be a valid state name. - `FormBuilderValidators.street()` - requires the field's value to be a valid street name. @@ -134,14 +133,13 @@ URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit ca ### Network validators -- `FormBuilderValidators.email()` - requires the field's value to be a valid email address. -- `FormBuilderValidators.ip()` - requires the field's value to be a valid IP address. -- `FormBuilderValidators.latitude()` - requires the field's to be a valid latitude. -- `FormBuilderValidators.longitude()` - requires the field's to be a valid longitude. -- `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. +- `Validators.ip()`: Checks if the field contains a properly formatted `Internet Protocol` (IP) address. +- `Validators.url()`: Checks if the field contains a properly formatted `Uniform Resource Locators` (URL). +- TODO `FormBuilderValidators.email()` - requires the field's value to be a valid email address. +- TODO `FormBuilderValidators.latitude()` - requires the field's to be a valid latitude. - `FormBuilderValidators.longitude()` - requires the field's to be a valid longitude. +- TODO `FormBuilderValidators.macAddress()` - requires the field's to be a valid MAC address. +- TODO `FormBuilderValidators.phoneNumber()` - requires the field's value to be a valid phone number. +- TODO `FormBuilderValidators.portNumber()` - requires the field's to be a valid port number. ### Numeric validators @@ -176,6 +174,13 @@ URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit ca - TODO `FormBuilderValidators.minWordsCount()` - requires the word count of the field's value to be greater than or equal to the provided minimum count. - TODO `FormBuilderValidators.singleLine()` - requires the field's string to be a single line of text. +### 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. + ### Use-case validators - `FormBuilderValidators.base64()` - requires the field's to be a valid base64 string. @@ -185,7 +190,6 @@ URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit ca - `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 diff --git a/lib/src/form_builder_validators.dart b/lib/src/form_builder_validators.dart index b2c48c2f..73b4d53f 100644 --- a/lib/src/form_builder_validators.dart +++ b/lib/src/form_builder_validators.dart @@ -3853,6 +3853,51 @@ final class Validators { ); // 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 + /// - `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); + /// {@template validator_password} /// Creates a composite validator for password validation that enforces multiple /// password strength requirements simultaneously. @@ -3959,50 +4004,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 From d5c5ceb496832e01ea0bd81ed131902a026660d4 Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Wed, 12 Feb 2025 11:11:51 -0300 Subject: [PATCH 05/27] add other sections: collection, datetime, generic types, numeric, --- README-updated.md | 63 ++-- .../lib/forms_with_validate_granularlly.dart | 5 +- lib/src/form_builder_validators.dart | 288 +++++++++--------- .../validators/generic_type_validators.dart | 8 +- ...st.dart => is_in_list_validator_test.dart} | 17 +- 5 files changed, 192 insertions(+), 189 deletions(-) rename test/src/validators/generic_type_validators/{contains_element_validator_test.dart => is_in_list_validator_test.dart} (82%) diff --git a/README-updated.md b/README-updated.md index 803c0e27..798ac69f 100644 --- a/README-updated.md +++ b/README-updated.md @@ -19,7 +19,6 @@ Also it includes the `l10n` / `i18n` of error text messages to multiple language - [Features](#features) - [Validators](#validators) - - [Bool validators](#bool-validators) - [Collection validators](#collection-validators) - [Core validators](#core-validators) - [Datetime validators](#datetime-validators) @@ -64,19 +63,13 @@ This package comes with several most common `FormFieldValidator`s such as requir URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit card, etc., with default `errorText` messages. -### Bool validators - -- `FormBuilderValidators.isFalse()` - requires the field's to be false. -- `FormBuilderValidators.isTrue()` - requires the field's to be true. ### Collection validators -- `FormBuilderValidators.containsElement()` - requires the field's to be in the provided list. -- `FormBuilderValidators.equalLength()` - requires the length of the field's value to be equal to the provided minimum length. -- `FormBuilderValidators.maxLength()` - requires the length of the field's value to be less than or equal to the provided maximum size. -- `FormBuilderValidators.minLength()` - requires the length of the field's value to be greater than or equal to the provided minimum length. -- `FormBuilderValidators.range()` - requires the field's to be within a range. -- `FormBuilderValidators.unique()` - requires the field's to be unique in the provided list. +- `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 @@ -94,13 +87,12 @@ URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit ca ### Datetime validators -- `FormBuilderValidators.dateFuture()` - requires the field's value to be in the future. -- `FormBuilderValidators.datePast()` - requires the field's value to be a in the past. -- `FormBuilderValidators.dateRange()` - requires the field's value to be a within a date range. -- `FormBuilderValidators.dateTime()` - requires the field's value to be a valid date time. -- `FormBuilderValidators.date()` - requires the field's value to be a valid date string. -- `FormBuilderValidators.time()` - requires the field's value to be a valid time string. -- `FormBuilderValidators.timeZone()` - requires the field's value to be a valid time zone. +- `Validators.isAfter(reference)`: Checks if the field contains a `DateTime` that is after `reference`. +- `Validators.isBefore(reference)`: Checks if the field contains a `DateTime` that is before `reference`. +- `Validators.isDateTimeBetween(minReference, maxReference)`: Checks if the field contains a `DateTime` that is after `minReference` and before `maxReference`. +- TODO `FormBuilderValidators.date()` - requires the field's value to be a valid date string. +- TODO `FormBuilderValidators.time()` - requires the field's value to be a valid time string. +- TODO `FormBuilderValidators.timeZone()` - requires the field's value to be a valid time zone. ### File validators @@ -118,6 +110,13 @@ URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit ca - `Validators.creditCard()`: Checks if the field contains a valid credit card number. - TODO `FormBuilderValidators.iban()` - requires the field's to be a valid IBAN. +### Generic Type Validators +Validators that check a generic type user input. + +- `Validators.isInList(values)`: Checks if the field contains 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. + ### Identity validators - `FormBuilderValidators.city()` - requires the field's value to be a valid city name. @@ -143,17 +142,20 @@ URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit ca ### 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. +- `Validators.between(min, max)`: Checks if the field contains a number that is in the 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`. +- TODO `FormBuilderValidators.evenNumber()` - requires the field's to be an even number. +- TODO `FormBuilderValidators.negativeNumber()` - requires the field's to be a negative number. +- TODO `FormBuilderValidators.notZeroNumber()` - requires the field's to be not a number zero. +- TODO `FormBuilderValidators.oddNumber()` - requires the field's to be an odd number. +- TODO `FormBuilderValidators.positiveNumber()` - requires the field's to be a positive number. +- TODO `FormBuilderValidators.prime()` - requires the field's to be a prime number. + +### Path Validators +- `Validators.matchesAllowedExtensions(extensions)`: Checks if the field contains a `String` that is in the list `extensions`. ### String validators @@ -174,6 +176,9 @@ URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit ca - TODO `FormBuilderValidators.minWordsCount()` - requires the word count of the field's value to be greater than or equal to the provided minimum count. - TODO `FormBuilderValidators.singleLine()` - requires the field's string to be a single line of text. +### Type Validators +- `Validators. TODO checkpoint` + ### User Information validators - `Validators.email()`: Checks if the field contains a valid email. diff --git a/example/lib/forms_with_validate_granularlly.dart b/example/lib/forms_with_validate_granularlly.dart index dad155fe..bab4c191 100644 --- a/example/lib/forms_with_validate_granularlly.dart +++ b/example/lib/forms_with_validate_granularlly.dart @@ -181,9 +181,8 @@ class _BodyState extends State<_Body> { child: Text('Invalid option 2'), ), ]).toList(), - validator: V.isRequired(V.containsElement( - validBloodTypeOptions, - containsElementMsg: (_, List v) => + validator: V.isRequired(V.isInList(validBloodTypeOptions, + isInListMsg: (_, List v) => 'The option must be one of: ${v.join(', ')}.')), onChanged: (String? value) { setState(() { diff --git a/lib/src/form_builder_validators.dart b/lib/src/form_builder_validators.dart index 73b4d53f..d35338da 100644 --- a/lib/src/form_builder_validators.dart +++ b/lib/src/form_builder_validators.dart @@ -3060,6 +3060,72 @@ final class Validators { 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(int expectedLength, + {String Function(T input, 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 @@ -3250,72 +3316,6 @@ final class Validators { }) => 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} /// Creates a [DateTime] validator that checks if an input date occurs after @@ -3475,7 +3475,7 @@ final class Validators { maxInclusive: rightInclusive); // Generic type validators - /// {@template validator_contains_element} + /// {@template validator_is_in_list} /// Creates a validator function that verifies if a given input is in `values`. /// /// ## Type Parameters @@ -3485,7 +3485,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 + /// - `isInListMsg` (`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 +3502,9 @@ final class Validators { /// ## Examples /// ```dart /// // Creating a validator with a custom error message generator - /// final countryValidator = containsElement( + /// final countryValidator = isInList( /// ['USA', 'Canada', 'Mexico'], - /// containsElementMsg: (input, values) => + /// isInListMsg: (input, values) => /// 'Country $input is not in allowed list: ${values.join(", ")}', /// ); /// @@ -3513,11 +3513,11 @@ final class Validators { /// final valid = countryValidator('USA'); // Returns null (valid) /// ``` /// {@endtemplate} - static Validator containsElement( + static Validator isInList( List values, { - String Function(T input, List values)? containsElementMsg, + String Function(T input, List values)? isInListMsg, }) => - val.containsElement(values, containsElementMsg: containsElementMsg); + val.isInList(values, isInListMsg: isInListMsg); /// {@template validator_is_true} /// Creates a validator function that checks if a given input represents a `true` @@ -3624,6 +3624,77 @@ final class Validators { 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, + {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, + ); + /// {@template validator_greater_than} /// Creates a validator function that checks if a numeric input exceeds `reference`. /// @@ -3781,77 +3852,6 @@ final class Validators { 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 - /// - /// ## 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, - {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, - ); - // User information validators /// {@template validator_email} diff --git a/lib/src/validators/generic_type_validators.dart b/lib/src/validators/generic_type_validators.dart index 8234c1de..04a97e35 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_is_in_list} +Validator isInList( List values, { - String Function(T input, List values)? containsElementMsg, + String Function(T input, List values)? isInListMsg, }) { if (values.isEmpty) { throw ArgumentError.value( @@ -14,7 +14,7 @@ Validator containsElement( return (T input) { return setOfValues.contains(input) ? null - : containsElementMsg?.call(input, values) ?? + : isInListMsg?.call(input, values) ?? FormBuilderLocalizations.current.containsElementErrorText; }; } diff --git a/test/src/validators/generic_type_validators/contains_element_validator_test.dart b/test/src/validators/generic_type_validators/is_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/is_in_list_validator_test.dart index 29e92351..0129b8b3 100644 --- a/test/src/validators/generic_type_validators/contains_element_validator_test.dart +++ b/test/src/validators/generic_type_validators/is_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 = isInList([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 = isInList([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 = isInList([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(() => isInList([]), 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 = + isInList([1, 2, 3], isInListMsg: (_, __) => 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 = isInList(elements); expect(v(12), isNull); expect(v(15), isNull); From 72e0c712289cc050fc4d65e283d89f4c9cc556ba Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Thu, 13 Feb 2025 12:50:54 -0300 Subject: [PATCH 06/27] add type validator to readme --- README-updated.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README-updated.md b/README-updated.md index 798ac69f..b6ac0370 100644 --- a/README-updated.md +++ b/README-updated.md @@ -177,7 +177,12 @@ Validators that check a generic type user input. - TODO `FormBuilderValidators.singleLine()` - requires the field's string to be a single line of text. ### Type Validators -- `Validators. TODO checkpoint` +- `Validators.isString(next)`: Checks if the field contains a valid `String` and passes the input as `String` to the `next` validator. +- `Validators.isInt(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.isDouble(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.isNum(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.isBool(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.isDateTime(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 From a8c391bb54b7e4c7590fe73b327057394c56cee1 Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Thu, 13 Feb 2025 14:38:42 -0300 Subject: [PATCH 07/27] add core validators section to readme. --- README-updated.md | 113 ++++++++++++++------------- lib/src/form_builder_validators.dart | 94 +++++++++++----------- 2 files changed, 105 insertions(+), 102 deletions(-) diff --git a/README-updated.md b/README-updated.md index b6ac0370..9deb3578 100644 --- a/README-updated.md +++ b/README-updated.md @@ -73,22 +73,40 @@ URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit ca ### Core validators -- `FormBuilderValidators.aggregate()` - runs the validators in parallel, collecting all errors. -- `FormBuilderValidators.compose()` - runs each validator against the value provided. -- `FormBuilderValidators.conditional()` - conditionally runs a validator against the value provided. -- `FormBuilderValidators.defaultValue()` - runs the validator using the default value when the provided value is null. -- `FormBuilderValidators.equal()` - requires the field's value to be equal to the provided object. -- `FormBuilderValidators.log()` - runs the validator and logs the value at a specific point in the validation chain. -- `FormBuilderValidators.notEqual()` - requires the field's value to be not equal to the provided object. -- `FormBuilderValidators.or()` - runs each validator against the value provided and passes when any works. -- `FormBuilderValidators.required()` - requires the field to have a non-empty value. -- `FormBuilderValidators.skipWhen()` - runs the validator and skips the validation when a certain condition is met. -- `FormBuilderValidators.transform()` - transforms the value before running the validator. +#### Composition validators + +- `Validators.and(validators)`: Validates the field by requiring it to pass all validators in the provided list of validators: `validators`. +- `Validators.or(validators)`: Validates the field by requiring it to pass at least one of the validators in the provided list of validators: `validators`. + +#### 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 + +TODO remove every verb `is` from the name of the validators. +- `Validators.isEqual(value)`: Checks if the field contains an input that is equal to `value` (==). +- `Validators.isNotEqual(value)`: Checks if the field contains an input that is not equal to `value` (!=). + +#### Required validators + +- `Validators.isRequired(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.isOptional(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.isAfter(reference)`: Checks if the field contains a `DateTime` that is after `reference`. - `Validators.isBefore(reference)`: Checks if the field contains a `DateTime` that is before `reference`. +TODO replace isDateTimeBetween with betweenDateTime - `Validators.isDateTimeBetween(minReference, maxReference)`: Checks if the field contains a `DateTime` that is after `minReference` and before `maxReference`. - TODO `FormBuilderValidators.date()` - requires the field's value to be a valid date string. - TODO `FormBuilderValidators.time()` - requires the field's value to be a valid time string. @@ -96,11 +114,11 @@ URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit ca ### File validators -- `FormBuilderValidators.fileExtension()` - requires the field's value to a valid file extension. -- `FormBuilderValidators.fileName()` - requires the field's to be a valid file name. -- `FormBuilderValidators.fileSize()` - requires the field's to be less than the max size. -- `FormBuilderValidators.mimeType()` - requires the field's value to a valid MIME type. -- `FormBuilderValidators.path()` - requires the field's to be a valid file or folder path. +- TODO `FormBuilderValidators.fileExtension()` - requires the field's value to a valid file extension. +- TODO `FormBuilderValidators.fileName()` - requires the field's to be a valid file name. +- TODO `FormBuilderValidators.fileSize()` - requires the field's to be less than the max size. +- TODO `FormBuilderValidators.mimeType()` - requires the field's value to a valid MIME type. +- TODO `FormBuilderValidators.path()` - requires the field's to be a valid file or folder path. ### Finance validators @@ -117,19 +135,17 @@ Validators that check a generic type user input. - `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. -### Identity validators - -- `FormBuilderValidators.city()` - requires the field's value to be a valid city name. -- `FormBuilderValidators.country()` - requires the field's value to be a valid country name. -- `FormBuilderValidators.firstName()` - requires the field's value to be a valid first name. -- `FormBuilderValidators.lastName()` - requires the field's value to be a valid last name. -- `FormBuilderValidators.passportNumber()` - requires the field's value to be a valid passport number. -- `FormBuilderValidators.ssn()` - requires the field's to be a valid SSN (Social Security Number). -- `FormBuilderValidators.state()` - requires the field's value to be a valid state name. -- `FormBuilderValidators.street()` - requires the field's value to be a valid street name. -- `FormBuilderValidators.username()` - requires the field's to be a valid username that matched required conditions. -- `FormBuilderValidators.zipCode()` - requires the field's to be a valid zip code. - +### Miscellaneous validators + +- TODO `FormBuilderValidators.base64()` - requires the field's to be a valid base64 string. +- TODO `FormBuilderValidators.colorCode()` - requires the field's value to be a valid color code. +- TODO `FormBuilderValidators.duns()` - requires the field's value to be a valid DUNS. +- TODO `FormBuilderValidators.isbn()` - requires the field's to be a valid ISBN. +- TODO `FormBuilderValidators.json()` - requires the field's to be a valid json string. +- TODO `FormBuilderValidators.languageCode()` - requires the field's to be a valid language code. +- TODO `FormBuilderValidators.licensePlate()` - requires the field's to be a valid license plate. +- TODO `FormBuilderValidators.vin()` - requires the field's to be a valid VIN number. + ### Network validators - `Validators.ip()`: Checks if the field contains a properly formatted `Internet Protocol` (IP) address. @@ -190,30 +206,16 @@ Validators that check a generic type user input. - `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. - -### 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.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. +- TODO `FormBuilderValidators.city()` - requires the field's value to be a valid city name. +- TODO `FormBuilderValidators.country()` - requires the field's value to be a valid country name. +- TODO `FormBuilderValidators.firstName()` - requires the field's value to be a valid first name. +- TODO `FormBuilderValidators.lastName()` - requires the field's value to be a valid last name. +- TODO `FormBuilderValidators.passportNumber()` - requires the field's value to be a valid passport number. +- TODO `FormBuilderValidators.ssn()` - requires the field's to be a valid SSN (Social Security Number). +- TODO `FormBuilderValidators.state()` - requires the field's value to be a valid state name. +- TODO `FormBuilderValidators.street()` - requires the field's value to be a valid street name. +- TODO `FormBuilderValidators.username()` - requires the field's to be a valid username that matched required conditions. +- TODO `FormBuilderValidators.zipCode()` - requires the field's to be a valid zip code. ## Supported languages @@ -294,11 +296,11 @@ return MaterialApp( ### Basic use -```Dart +```dart TextFormField( decoration: InputDecoration(labelText: 'Name'), autovalidateMode: AutovalidateMode.always, - validator: FormBuilderValidators.required(), + validator: Validators.isRequired(), ), ``` @@ -314,6 +316,7 @@ On validation, each validator is run, and if any validator returns a non-null va Example: +TODO update this example (checkpoint) ```Dart TextFormField( decoration: InputDecoration(labelText: 'Age'), diff --git a/lib/src/form_builder_validators.dart b/lib/src/form_builder_validators.dart index d35338da..e35788ee 100644 --- a/lib/src/form_builder_validators.dart +++ b/lib/src/form_builder_validators.dart @@ -2075,53 +2075,6 @@ final class Validators { ]) => 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); - /// {@template validator_is_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 @@ -2179,6 +2132,53 @@ final class Validators { ]) => val.isOptional(next, isOptionalMsg); + /// {@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); + // Transform Validator /// {@template validator_transform_and_validate} From 411dda190ed8eac1cf553f65baedb3caeb33f0a0 Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Mon, 17 Feb 2025 20:35:03 -0300 Subject: [PATCH 08/27] fix the contents section --- README-updated.md | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/README-updated.md b/README-updated.md index 9deb3578..09417aba 100644 --- a/README-updated.md +++ b/README-updated.md @@ -21,15 +21,23 @@ Also it includes the `l10n` / `i18n` of error text messages to multiple language - [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) - - [Identity validators](#identity-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) - - [Use-case validators](#use-case-validators) - - [Extension method validators](#extension-method-validators) + - [Type validators](#type-validators) + - [User information validators](#user-information-validators) - [Supported languages](#supported-languages) - [Use](#use) - [Setup](#setup) @@ -40,7 +48,7 @@ Also it includes the `l10n` / `i18n` of error text messages to multiple language - [Migrations](#migrations) - [v7 to v8](#v7-to-v8) - [v10 to 11](#v10-to-v11) -- [How does this package works](#how-this-package-works) +- [How this package works](#how-this-package-works) - [Support](#support) - [Contribute](#contribute) - [Add new supported language](#add-new-supported-language) @@ -62,6 +70,14 @@ Also it includes the `l10n` / `i18n` of error text messages to multiple language 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. Required validators: makes a field optional or required. +2. Type validators: checks the type of the field. +3. Other validators: makes other types of checking. + +Generally, we build a validator composing those three types in the following way: +((())) + ### Collection validators From dec4504c39725e82317a3f9df95e052dc6780678 Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Mon, 17 Feb 2025 20:48:49 -0300 Subject: [PATCH 09/27] remove the verb 'is' from equality validators --- example/lib/basic_examples.dart | 4 +-- example/lib/generic_examples.dart | 2 +- lib/src/form_builder_validators.dart | 35 ++++++++++--------- .../core_validators/equality_validators.dart | 16 ++++----- ...or_test.dart => equal_validator_test.dart} | 6 ++-- ...est.dart => not_equal_validator_test.dart} | 6 ++-- 6 files changed, 36 insertions(+), 33 deletions(-) rename test/src/validators/core_validators/equality_validators/{is_equal_validator_test.dart => equal_validator_test.dart} (94%) rename test/src/validators/core_validators/equality_validators/{is_not_equal_validator_test.dart => not_equal_validator_test.dart} (94%) diff --git a/example/lib/basic_examples.dart b/example/lib/basic_examples.dart index f746a3b0..a221bb5a 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, diff --git a/example/lib/generic_examples.dart b/example/lib/generic_examples.dart index f527db19..4e2247a3 100644 --- a/example/lib/generic_examples.dart +++ b/example/lib/generic_examples.dart @@ -138,7 +138,7 @@ 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, ), diff --git a/lib/src/form_builder_validators.dart b/lib/src/form_builder_validators.dart index e35788ee..9b9ed8c0 100644 --- a/lib/src/form_builder_validators.dart +++ b/lib/src/form_builder_validators.dart @@ -1738,6 +1738,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 { @@ -1930,7 +1932,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 +1940,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 +1957,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 +1974,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 +2003,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,13 +2020,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 + // TODO remove verb 'is' from validators /// {@template validator_is_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 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/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); From 70c7594d5ad940c447ae33d4ee3ffc860925071b Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Thu, 20 Feb 2025 11:25:04 -0300 Subject: [PATCH 10/27] remove the verb 'is' from required validators --- example/lib/basic_examples.dart | 2 +- .../lib/forms_with_validate_granularlly.dart | 15 +++++---- example/lib/generic_examples.dart | 20 ++++++------ lib/src/form_builder_validators.dart | 31 +++++++++---------- .../core_validators/required_validators.dart | 17 +++++----- ...test.dart => optional_validator_test.dart} | 11 +++---- ...test.dart => required_validator_test.dart} | 10 +++--- 7 files changed, 51 insertions(+), 55 deletions(-) rename test/src/validators/core_validators/required_validators/{is_optional_validator_test.dart => optional_validator_test.dart} (80%) rename test/src/validators/core_validators/required_validators/{is_required_validator_test.dart => required_validator_test.dart} (83%) diff --git a/example/lib/basic_examples.dart b/example/lib/basic_examples.dart index a221bb5a..c9277308 100644 --- a/example/lib/basic_examples.dart +++ b/example/lib/basic_examples.dart @@ -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 bab4c191..443b9e8a 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,8 +100,7 @@ class _BodyState extends State<_Body> { hintText: 'YYYY-MM-DD', prefixIcon: Icon(Icons.calendar_today), ), - validator: V.isRequired(V.isDateTime(V.isBefore( - DateTime.now(), + validator: V.required(V.isDateTime(V.isBefore(DateTime.now(), isBeforeMsg: (_, __) => 'Date must be in the past.'))), keyboardType: TextInputType.datetime, textInputAction: TextInputAction.next, @@ -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.isNum(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.isNum(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,7 +180,7 @@ class _BodyState extends State<_Body> { child: Text('Invalid option 2'), ), ]).toList(), - validator: V.isRequired(V.isInList(validBloodTypeOptions, + validator: V.required(V.isInList(validBloodTypeOptions, isInListMsg: (_, List v) => 'The option must be one of: ${v.join(', ')}.')), onChanged: (String? value) { diff --git a/example/lib/generic_examples.dart b/example/lib/generic_examples.dart index 4e2247a3..718c4773 100644 --- a/example/lib/generic_examples.dart +++ b/example/lib/generic_examples.dart @@ -24,7 +24,7 @@ class GenericExamplesPage extends StatelessWidget { decoration: const InputDecoration(labelText: 'Age'), keyboardType: TextInputType.number, autovalidateMode: AutovalidateMode.always, - validator: V.isRequired(V.and(>[ + validator: V.required(V.and(>[ V.isNum(V.lessThan(70), (_) => 'La edad debe ser numérica.'), /// Include your own custom `FormFieldValidator` function, if you want @@ -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,7 +59,7 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.numbers), ), keyboardType: TextInputType.number, - validator: V.isRequired(V.isNum()), + validator: V.required(V.isNum()), autofillHints: const [AutofillHints.oneTimeCode], textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, @@ -96,7 +96,7 @@ class GenericExamplesPage extends StatelessWidget { 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 +106,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 +117,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.isNum(V.greaterThan(10))), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), @@ -128,7 +128,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.isNum(V.lessThan(100))), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), @@ -161,7 +161,7 @@ class GenericExamplesPage extends StatelessWidget { 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, ), @@ -220,7 +220,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,7 +232,7 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.calendar_today), ), keyboardType: TextInputType.number, - validator: V.isRequired( + validator: V.required( V.isNum(V.and(>[V.between(0, 120)]))), textInputAction: TextInputAction.done, autovalidateMode: AutovalidateMode.always, diff --git a/lib/src/form_builder_validators.dart b/lib/src/form_builder_validators.dart index 9b9ed8c0..f6fc14f5 100644 --- a/lib/src/form_builder_validators.dart +++ b/lib/src/form_builder_validators.dart @@ -2027,8 +2027,7 @@ final class Validators { val.notEqual(value, notEqualMsg: notEqualMsg); // Required validators - // TODO remove verb 'is' from 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. @@ -2043,7 +2042,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. /// @@ -2056,13 +2055,13 @@ final class Validators { /// ## Examples /// ```dart /// // Basic required field validation - /// final validator = isRequired(); + /// final validator = Validators.required(); /// print(validator(null)); // Returns localized error message /// print(validator('')); // Returns localized error message /// print(validator('value')); // 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' /// ); @@ -2072,13 +2071,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); + 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. @@ -2097,7 +2096,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. /// @@ -2111,10 +2110,10 @@ 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', /// ); @@ -2129,11 +2128,11 @@ final class Validators { /// - 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, @@ -2167,7 +2166,7 @@ final class Validators { /// /// // Wrap it with a default value of 'N/A' /// final defaultValue = 'default value'; - /// final validator = validateWithDefault('N/A', minLength); + /// final validator = Validators.validateWithDefault('N/A', minLength); /// /// print(validator(null)); // Returns null (valid) /// print(validator('ab')); // Returns 'Must be at least 3 characters' 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/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)); From a5922330b15bb7bd2c1e3e2108ec412fbf403ce2 Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Thu, 20 Feb 2025 11:46:02 -0300 Subject: [PATCH 11/27] remove the verb 'is' from the type validators --- .../lib/forms_with_validate_granularlly.dart | 6 +- example/lib/generic_examples.dart | 10 +- lib/src/form_builder_validators.dart | 232 +++++++++--------- .../core_validators/type_validators.dart | 24 +- .../core_validators/type_validators_test.dart | 16 +- 5 files changed, 146 insertions(+), 142 deletions(-) diff --git a/example/lib/forms_with_validate_granularlly.dart b/example/lib/forms_with_validate_granularlly.dart index 443b9e8a..1e311420 100644 --- a/example/lib/forms_with_validate_granularlly.dart +++ b/example/lib/forms_with_validate_granularlly.dart @@ -100,7 +100,7 @@ class _BodyState extends State<_Body> { hintText: 'YYYY-MM-DD', prefixIcon: Icon(Icons.calendar_today), ), - validator: V.required(V.isDateTime(V.isBefore(DateTime.now(), + validator: V.required(V.dateTime(V.isBefore(DateTime.now(), isBeforeMsg: (_, __) => 'Date must be in the past.'))), keyboardType: TextInputType.datetime, textInputAction: TextInputAction.next, @@ -126,7 +126,7 @@ class _BodyState extends State<_Body> { prefixIcon: Icon(Icons.height), suffixText: 'm', ), - validator: V.required(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, @@ -141,7 +141,7 @@ class _BodyState extends State<_Body> { prefixIcon: Icon(Icons.monitor_weight), suffixText: 'kg', ), - validator: V.optional(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, diff --git a/example/lib/generic_examples.dart b/example/lib/generic_examples.dart index 718c4773..e819444f 100644 --- a/example/lib/generic_examples.dart +++ b/example/lib/generic_examples.dart @@ -25,7 +25,7 @@ class GenericExamplesPage extends StatelessWidget { keyboardType: TextInputType.number, autovalidateMode: AutovalidateMode.always, validator: V.required(V.and(>[ - V.isNum(V.lessThan(70), (_) => 'La edad debe ser numérica.'), + 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 @@ -59,7 +59,7 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.numbers), ), keyboardType: TextInputType.number, - validator: V.required(V.isNum()), + validator: V.required(V.num()), autofillHints: const [AutofillHints.oneTimeCode], textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, @@ -117,7 +117,7 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.exposure_neg_1), ), keyboardType: TextInputType.number, - validator: V.required(V.isNum(V.greaterThan(10))), + validator: V.required(V.num(V.greaterThan(10))), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), @@ -128,7 +128,7 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.exposure_plus_1), ), keyboardType: TextInputType.number, - validator: V.required(V.isNum(V.lessThan(100))), + validator: V.required(V.num(V.lessThan(100))), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), @@ -233,7 +233,7 @@ class GenericExamplesPage extends StatelessWidget { ), keyboardType: TextInputType.number, validator: V.required( - V.isNum(V.and(>[V.between(0, 120)]))), + V.num(V.and(>[V.between(0, 120)]))), textInputAction: TextInputAction.done, autovalidateMode: AutovalidateMode.always, ), diff --git a/lib/src/form_builder_validators.dart b/lib/src/form_builder_validators.dart index f6fc14f5..cd99530b 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'; @@ -1790,7 +1793,7 @@ final class Validators { String prefix = '', String suffix = '', String? separator, - bool printErrorAsSoonAsPossible = true, + c.bool printErrorAsSoonAsPossible = true, }) => val.and(validators, prefix: prefix, @@ -1870,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} @@ -1900,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 @@ -2247,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. @@ -2259,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. /// @@ -2272,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' @@ -2295,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. @@ -2317,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 /// @@ -2331,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' /// ``` @@ -2349,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. @@ -2371,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 /// @@ -2385,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) @@ -2393,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' /// ``` @@ -2405,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. @@ -2427,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 /// @@ -2441,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) @@ -2449,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' /// ``` @@ -2461,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. @@ -2483,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. @@ -2503,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) @@ -2511,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' /// ``` @@ -2532,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. @@ -2555,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 /// @@ -2569,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' /// ``` @@ -2593,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} @@ -2651,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, @@ -2704,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( @@ -2764,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)? customUppercaseCounter, + String Function(String input, c.int min)? hasMinUppercaseCharsMsg, }) => val.hasMinUppercaseChars( min: min, @@ -2825,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)? customLowercaseCounter, + String Function(String input, c.int min)? hasMinLowercaseCharsMsg, }) => val.hasMinLowercaseChars( min: min, @@ -2891,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)? customNumericCounter, + String Function(String input, c.int min)? hasMinNumericCharsMsg, }) => val.hasMinNumericChars( min: min, @@ -2958,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)? customSpecialCounter, + String Function(String input, c.int min)? hasMinSpecialCharsMsg, }) => val.hasMinSpecialChars( min: min, @@ -3121,8 +3124,8 @@ final class Validators { /// 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}) => + static Validator equalLength(c.int expectedLength, + {String Function(T input, c.int expectedLength)? equalLengthMsg}) => val.equalLength( expectedLength, equalLengthMsg: equalLengthMsg, @@ -3184,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} @@ -3243,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} @@ -3311,9 +3314,9 @@ 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); @@ -3358,7 +3361,7 @@ final class Validators { static Validator isAfter( DateTime reference, { String Function(DateTime input, DateTime reference)? isAfterMsg, - bool inclusive = false, + c.bool inclusive = false, }) => val.isAfter(reference, isAfterMsg: isAfterMsg, inclusive: inclusive); @@ -3402,7 +3405,7 @@ final class Validators { static Validator isBefore( DateTime reference, { String Function(DateTime input, DateTime reference)? isBeforeMsg, - bool inclusive = false, + c.bool inclusive = false, }) => val.isBefore(reference, isBeforeMsg: isBeforeMsg, inclusive: inclusive); @@ -3468,8 +3471,8 @@ final class Validators { String Function( DateTime input, DateTime minReference, DateTime maxReference)? isDateTimeBetweenMsg, - bool leftInclusive = false, - bool rightInclusive = false, + c.bool leftInclusive = false, + c.bool rightInclusive = false, }) => val.isDateTimeBetween(minReference, maxReference, isDateTimeBetweenMsg: isDateTimeBetweenMsg, @@ -3568,8 +3571,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); @@ -3620,8 +3623,8 @@ 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); @@ -3679,15 +3682,15 @@ final class Validators { /// ## Caveats /// - The default behavior uses inclusive bounds (`>=` and `<=`) /// {@endtemplate} - static Validator between(T min, T max, - {bool minInclusive = true, - bool maxInclusive = true, + static Validator between(T min, T max, + {c.bool minInclusive = true, + c.bool maxInclusive = true, String Function( T input, T min, T max, - bool minInclusive, - bool maxInclusive, + c.bool minInclusive, + c.bool maxInclusive, )? betweenMsg}) => val.between( min, @@ -3730,8 +3733,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} @@ -3770,8 +3773,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); @@ -3809,8 +3812,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} @@ -3849,8 +3852,9 @@ 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); @@ -3943,12 +3947,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( @@ -4160,9 +4164,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/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/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); From 38084a8b448b89f2dbfae5f41dbc50535d7f4b02 Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Thu, 20 Feb 2025 11:52:08 -0300 Subject: [PATCH 12/27] remove the verb 'is' from dateTime validators --- .../lib/forms_with_validate_granularlly.dart | 4 +- lib/src/form_builder_validators.dart | 50 +++++++++---------- lib/src/validators/datetime_validators.dart | 24 ++++----- ...or_test.dart => after_validator_test.dart} | 8 +-- ...r_test.dart => before_validator_test.dart} | 8 +-- ...t => datetime_between_validator_test.dart} | 12 ++--- 6 files changed, 53 insertions(+), 53 deletions(-) rename test/src/validators/datetime_validators/{is_after_validator_test.dart => after_validator_test.dart} (94%) rename test/src/validators/datetime_validators/{is_before_validator_test.dart => before_validator_test.dart} (93%) rename test/src/validators/datetime_validators/{is_datetime_between_validator_test.dart => datetime_between_validator_test.dart} (94%) diff --git a/example/lib/forms_with_validate_granularlly.dart b/example/lib/forms_with_validate_granularlly.dart index 1e311420..c9e241a2 100644 --- a/example/lib/forms_with_validate_granularlly.dart +++ b/example/lib/forms_with_validate_granularlly.dart @@ -100,8 +100,8 @@ class _BodyState extends State<_Body> { hintText: 'YYYY-MM-DD', prefixIcon: Icon(Icons.calendar_today), ), - validator: V.required(V.dateTime(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 { diff --git a/lib/src/form_builder_validators.dart b/lib/src/form_builder_validators.dart index cd99530b..dbfd539b 100644 --- a/lib/src/form_builder_validators.dart +++ b/lib/src/form_builder_validators.dart @@ -3322,14 +3322,14 @@ final class Validators { val.betweenLength(min, max, betweenLengthMsg: betweenLengthMsg); // 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 @@ -3343,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, + 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 @@ -3387,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, + 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_date_time_between} /// Creates a [DateTime] validator that checks if an input date falls within a specified /// range defined by `minReference` and `maxReference`. /// @@ -3422,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 + /// - `dateTimeBetweenMsg` (`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 @@ -3443,13 +3443,13 @@ final class Validators { /// ## Examples /// ```dart /// // Basic usage requiring date between 2023 and 2025 - /// final validator = isDateTimeBetween( + /// final validator = Validators.dateTimeBetween( /// DateTime(2023), /// DateTime(2025), /// ); /// /// // Inclusive validation allowing exact matches - /// final inclusiveValidator = isDateTimeBetween( + /// final inclusiveValidator = Validators.dateTimeBetween( /// DateTime(2023), /// DateTime(2025), /// minInclusive: true, @@ -3457,7 +3457,7 @@ final class Validators { /// ); /// /// // Custom error message - /// final customValidator = isDateTimeBetween( + /// final customValidator = Validators.dateTimeBetween( /// DateTime(2023), /// DateTime(2025), /// isDateTimeBetweenMsg: (_, min, max) => @@ -3465,17 +3465,17 @@ final class Validators { /// ); /// ``` /// {@endtemplate} - static Validator isDateTimeBetween( + static Validator dateTimeBetween( DateTime minReference, DateTime maxReference, { String Function( DateTime input, DateTime minReference, DateTime maxReference)? - isDateTimeBetweenMsg, + dateTimeBetweenMsg, c.bool leftInclusive = false, c.bool rightInclusive = false, }) => - val.isDateTimeBetween(minReference, maxReference, - isDateTimeBetweenMsg: isDateTimeBetweenMsg, + val.dateTimeBetween(minReference, maxReference, + dateTimeBetweenMsg: dateTimeBetweenMsg, minInclusive: leftInclusive, maxInclusive: rightInclusive); diff --git a/lib/src/validators/datetime_validators.dart b/lib/src/validators/datetime_validators.dart index 5ff13fd7..5fc09b78 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_date_time_between} +Validator dateTimeBetween( DateTime minReference, DateTime maxReference, { String Function(DateTime input, DateTime minReference, DateTime maxReference)? - isDateTimeBetweenMsg, + dateTimeBetweenMsg, 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) ?? + : dateTimeBetweenMsg?.call(input, minReference, maxReference) ?? FormBuilderLocalizations.current.dateMustBeBetweenErrorText( minReference.toLocal(), maxReference.toLocal()); }; 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..c7693980 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); + dateTimeBetween(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); + dateTimeBetween(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 = dateTimeBetween( leftReference, rightReference, - isDateTimeBetweenMsg: (_, __, ___) => errorMsg); + dateTimeBetweenMsg: (_, __, ___) => 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( + () => dateTimeBetween( DateTime(1990, 12, 23, 20), DateTime(1990, 12, 22, 20)), throwsAssertionError); }); From 8b235d1c0f813d2f55d71bfa97c000a90c54a329 Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Thu, 20 Feb 2025 11:58:41 -0300 Subject: [PATCH 13/27] remove the verb 'is' from generic validators --- example/lib/forms_with_validate_granularlly.dart | 4 ++-- lib/src/form_builder_validators.dart | 12 ++++++------ lib/src/validators/generic_type_validators.dart | 10 +++++----- .../is_in_list_validator_test.dart | 12 ++++++------ 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/example/lib/forms_with_validate_granularlly.dart b/example/lib/forms_with_validate_granularlly.dart index c9e241a2..4848ac89 100644 --- a/example/lib/forms_with_validate_granularlly.dart +++ b/example/lib/forms_with_validate_granularlly.dart @@ -180,8 +180,8 @@ class _BodyState extends State<_Body> { child: Text('Invalid option 2'), ), ]).toList(), - validator: V.required(V.isInList(validBloodTypeOptions, - isInListMsg: (_, 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/lib/src/form_builder_validators.dart b/lib/src/form_builder_validators.dart index dbfd539b..9bbd19bf 100644 --- a/lib/src/form_builder_validators.dart +++ b/lib/src/form_builder_validators.dart @@ -3480,7 +3480,7 @@ final class Validators { maxInclusive: rightInclusive); // Generic type validators - /// {@template validator_is_in_list} + /// {@template validator_in_list} /// Creates a validator function that verifies if a given input is in `values`. /// /// ## Type Parameters @@ -3490,7 +3490,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. - /// - `isInListMsg` (`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. @@ -3507,7 +3507,7 @@ final class Validators { /// ## Examples /// ```dart /// // Creating a validator with a custom error message generator - /// final countryValidator = isInList( + /// final countryValidator = Validators.inList( /// ['USA', 'Canada', 'Mexico'], /// isInListMsg: (input, values) => /// 'Country $input is not in allowed list: ${values.join(", ")}', @@ -3518,11 +3518,11 @@ final class Validators { /// final valid = countryValidator('USA'); // Returns null (valid) /// ``` /// {@endtemplate} - static Validator isInList( + static Validator inList( List values, { - String Function(T input, List values)? isInListMsg, + String Function(T input, List values)? inListMsg, }) => - val.isInList(values, isInListMsg: isInListMsg); + val.inList(values, inListMsg: inListMsg); /// {@template validator_is_true} /// Creates a validator function that checks if a given input represents a `true` diff --git a/lib/src/validators/generic_type_validators.dart b/lib/src/validators/generic_type_validators.dart index 04a97e35..254f358d 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_is_in_list} -Validator isInList( +/// {@macro validator_in_list} +Validator inList( List values, { - String Function(T input, List values)? isInListMsg, + String Function(T input, List values)? inListMsg, }) { if (values.isEmpty) { throw ArgumentError.value( @@ -14,7 +14,7 @@ Validator isInList( return (T input) { return setOfValues.contains(input) ? null - : isInListMsg?.call(input, values) ?? + : inListMsg?.call(input, values) ?? FormBuilderLocalizations.current.containsElementErrorText; }; } @@ -39,7 +39,7 @@ Validator isTrue( }; } -/// {@macro validator_is_false} +/// {@macro validator_false} Validator isFalse( {String Function(T input)? isFalseMsg, bool caseSensitive = false, diff --git a/test/src/validators/generic_type_validators/is_in_list_validator_test.dart b/test/src/validators/generic_type_validators/is_in_list_validator_test.dart index 0129b8b3..cc784320 100644 --- a/test/src/validators/generic_type_validators/is_in_list_validator_test.dart +++ b/test/src/validators/generic_type_validators/is_in_list_validator_test.dart @@ -6,14 +6,14 @@ void main() { group('Validator: isInList', () { group('Validations with default error message', () { test('Should return null when int 0 is provided', () { - final Validator validator = isInList([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 = isInList([0, '2']); + final Validator validator = inList([0, '2']); expect(validator(0), isNull); expect(validator('2'), isNull); @@ -21,7 +21,7 @@ void main() { equals(FormBuilderLocalizations.current.containsElementErrorText)); }); test('Should return null when int 0, int 2, or null is provided', () { - final Validator validator = isInList([0, 2, null]); + final Validator validator = inList([0, 2, null]); expect(validator(0), isNull); expect(validator(2), isNull); @@ -32,14 +32,14 @@ void main() { }); test('Should throw ArgumentError when list input is empty', () { - expect(() => isInList([]), throwsArgumentError); + expect(() => inList([]), throwsArgumentError); }); test('Should return custom error message when invalid input is provided', () { const String customMessage = 'custom message'; final Validator validator = - isInList([1, 2, 3], isInListMsg: (_, __) => customMessage); + inList([1, 2, 3], inListMsg: (_, __) => customMessage); expect(validator(4), equals(customMessage)); }); @@ -47,7 +47,7 @@ void main() { test('should remain immutable when input elements change', () { final List elements = [12, 15, 'hi']; - final Validator v = isInList(elements); + final Validator v = inList(elements); expect(v(12), isNull); expect(v(15), isNull); From 484e639fbca8e6cd390f5c97136b5b97d1452c89 Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Thu, 20 Feb 2025 12:14:36 -0300 Subject: [PATCH 14/27] update readme --- README-updated.md | 68 +++++++++---------- lib/src/form_builder_validators.dart | 20 +++--- lib/src/validators/datetime_validators.dart | 8 +-- .../datetime_between_validator_test.dart | 10 +-- 4 files changed, 51 insertions(+), 55 deletions(-) diff --git a/README-updated.md b/README-updated.md index 09417aba..0ac2e6ff 100644 --- a/README-updated.md +++ b/README-updated.md @@ -104,14 +104,13 @@ Generally, we build a validator composing those three types in the following way #### Equality validators -TODO remove every verb `is` from the name of the validators. -- `Validators.isEqual(value)`: Checks if the field contains an input that is equal to `value` (==). -- `Validators.isNotEqual(value)`: Checks if the field contains an input that is not equal to `value` (!=). +- `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` (!=). #### Required validators -- `Validators.isRequired(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.isOptional(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.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 @@ -120,10 +119,9 @@ TODO remove every verb `is` from the name of the validators. ### Datetime validators -- `Validators.isAfter(reference)`: Checks if the field contains a `DateTime` that is after `reference`. -- `Validators.isBefore(reference)`: Checks if the field contains a `DateTime` that is before `reference`. -TODO replace isDateTimeBetween with betweenDateTime -- `Validators.isDateTimeBetween(minReference, maxReference)`: Checks if the field contains a `DateTime` that is after `minReference` and before `maxReference`. +- `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`. - TODO `FormBuilderValidators.date()` - requires the field's value to be a valid date string. - TODO `FormBuilderValidators.time()` - requires the field's value to be a valid time string. - TODO `FormBuilderValidators.timeZone()` - requires the field's value to be a valid time zone. @@ -147,7 +145,7 @@ TODO replace isDateTimeBetween with betweenDateTime ### Generic Type Validators Validators that check a generic type user input. -- `Validators.isInList(values)`: Checks if the field contains a value that is in the list `values`. +- `Validators.inList(values)`: Checks if the field contains 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. @@ -209,12 +207,12 @@ Validators that check a generic type user input. - TODO `FormBuilderValidators.singleLine()` - requires the field's string to be a single line of text. ### Type Validators -- `Validators.isString(next)`: Checks if the field contains a valid `String` and passes the input as `String` to the `next` validator. -- `Validators.isInt(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.isDouble(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.isNum(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.isBool(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.isDateTime(next)`: Checks if the field contains a valid `DateTime` or parsable `String` to `DateTime` and passes the input as `DateTime` to the `next` validator. +- `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 @@ -316,7 +314,7 @@ return MaterialApp( TextFormField( decoration: InputDecoration(labelText: 'Name'), autovalidateMode: AutovalidateMode.always, - validator: Validators.isRequired(), + validator: Validators.required(), ), ``` @@ -338,25 +336,23 @@ TextFormField( decoration: InputDecoration(labelText: 'Age'), keyboardType: TextInputType.number, autovalidateMode: AutovalidateMode.always, - validator: FormBuilderValidators.compose([ - /// Makes this field required - FormBuilderValidators.required(), - - /// Ensures the value entered is numeric - with a custom error message - FormBuilderValidators.numeric(errorText: 'La edad debe ser numérica.'), - - /// Sets a maximum value of 70 - FormBuilderValidators.max(70), + validator: Validators.required( + Validators.and(>[ + Validators.num(Validators.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 - (val) { - final number = int.tryParse(val); - if (number == null) return null; - if (number < 0) return 'We cannot have a negative age'; + /// 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; - }, - ]), + } + ])) ), ``` @@ -422,14 +418,14 @@ String? Function(int) isEven(){ }; } -String? Function(int?) isRequired(String? Function(int) next){ +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 isRequired(isEven()), does. -final validator = isRequired(isEven()); +// 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: diff --git a/lib/src/form_builder_validators.dart b/lib/src/form_builder_validators.dart index 9bbd19bf..3e1915e1 100644 --- a/lib/src/form_builder_validators.dart +++ b/lib/src/form_builder_validators.dart @@ -3409,7 +3409,7 @@ final class Validators { }) => val.before(reference, beforeMsg: beforeMsg, inclusive: inclusive); - /// {@template validator_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`. /// @@ -3422,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). - /// - `dateTimeBetweenMsg` (`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 @@ -3443,13 +3443,13 @@ final class Validators { /// ## Examples /// ```dart /// // Basic usage requiring date between 2023 and 2025 - /// final validator = Validators.dateTimeBetween( + /// final validator = Validators.betweenDateTime( /// DateTime(2023), /// DateTime(2025), /// ); /// /// // Inclusive validation allowing exact matches - /// final inclusiveValidator = Validators.dateTimeBetween( + /// final inclusiveValidator = Validators.betweenDateTime( /// DateTime(2023), /// DateTime(2025), /// minInclusive: true, @@ -3457,25 +3457,25 @@ final class Validators { /// ); /// /// // Custom error message - /// final customValidator = Validators.dateTimeBetween( + /// 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 dateTimeBetween( + static Validator betweenDateTime( DateTime minReference, DateTime maxReference, { String Function( DateTime input, DateTime minReference, DateTime maxReference)? - dateTimeBetweenMsg, + betweenDateTimeMsg, c.bool leftInclusive = false, c.bool rightInclusive = false, }) => - val.dateTimeBetween(minReference, maxReference, - dateTimeBetweenMsg: dateTimeBetweenMsg, + val.betweenDateTime(minReference, maxReference, + betweenDateTimeMsg: betweenDateTimeMsg, minInclusive: leftInclusive, maxInclusive: rightInclusive); diff --git a/lib/src/validators/datetime_validators.dart b/lib/src/validators/datetime_validators.dart index 5fc09b78..c51952b3 100644 --- a/lib/src/validators/datetime_validators.dart +++ b/lib/src/validators/datetime_validators.dart @@ -33,12 +33,12 @@ Validator before( }; } -/// {@macro validator_date_time_between} -Validator dateTimeBetween( +/// {@macro validator_between_date_time} +Validator betweenDateTime( DateTime minReference, DateTime maxReference, { String Function(DateTime input, DateTime minReference, DateTime maxReference)? - dateTimeBetweenMsg, + betweenDateTimeMsg, bool minInclusive = false, bool maxInclusive = false, }) { @@ -52,7 +52,7 @@ Validator dateTimeBetween( (input.isAfter(minReference) || (minInclusive ? input.isAtSameMomentAs(minReference) : false)) ? null - : dateTimeBetweenMsg?.call(input, minReference, maxReference) ?? + : betweenDateTimeMsg?.call(input, minReference, maxReference) ?? FormBuilderLocalizations.current.dateMustBeBetweenErrorText( minReference.toLocal(), maxReference.toLocal()); }; diff --git a/test/src/validators/datetime_validators/datetime_between_validator_test.dart b/test/src/validators/datetime_validators/datetime_between_validator_test.dart index c7693980..a944dcec 100644 --- a/test/src/validators/datetime_validators/datetime_between_validator_test.dart +++ b/test/src/validators/datetime_validators/datetime_between_validator_test.dart @@ -25,7 +25,7 @@ void main() { final DateTime beforeLeft1Micro = leftReference.subtract(const Duration(microseconds: 1)); final Validator v = - dateTimeBetween(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 = - dateTimeBetween(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 = dateTimeBetween( + final Validator v = betweenDateTime( leftReference, rightReference, - dateTimeBetweenMsg: (_, __, ___) => 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( - () => dateTimeBetween( + () => betweenDateTime( DateTime(1990, 12, 23, 20), DateTime(1990, 12, 22, 20)), throwsAssertionError); }); From ee762d7d71d42d9c4ba54a730d73cb90afd4cd80 Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Thu, 27 Feb 2025 18:06:42 -0300 Subject: [PATCH 15/27] update readme --- README-updated.md | 88 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 8 deletions(-) diff --git a/README-updated.md b/README-updated.md index 0ac2e6ff..81cd5fd4 100644 --- a/README-updated.md +++ b/README-updated.md @@ -73,11 +73,16 @@ URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit ca Generally, the validators are separated in three main groups: 1. Required validators: makes a field optional or required. 2. Type validators: checks the type of the field. -3. Other validators: makes other types of checking. +3. Other validators: make any other kind of check that is not related to null 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 one and then check if +it is greater than 10. +`Validators.required(Validators.num(Validators.greaterThan(10)))` + ### Collection validators @@ -330,7 +335,6 @@ On validation, each validator is run, and if any validator returns a non-null va Example: -TODO update this example (checkpoint) ```Dart TextFormField( decoration: InputDecoration(labelText: 'Age'), @@ -362,6 +366,11 @@ see [override_form_builder_localizations_en](example/lib/override_form_builder_l ## Migrations +### v11 to v12 +- Deprecate `FormBuilderValidators` class with its static methods as validators. +- Instead, you should use `Validators` class. +TODO implement the remaining of breaking changes + ### 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. @@ -373,7 +382,7 @@ see [override_form_builder_localizations_en](example/lib/override_form_builder_l 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 `FormFieldValidator`s such as required, numeric, mail, +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`? @@ -476,11 +485,74 @@ We welcome efforts to internationalize/localize the package by translating the d #### Add new validator -1. Add a new validator to one of the folders in the `src` folder. -2. Implement it using the `BaseValidator` or `TranslatedValidator` class. Override the `validateValue` method and let the base class handle the null check in the `validate` method. -3. When using a `TranslatedValidator, Override the `translatedErrorText` property and return the correct translation from `FormBuilderLocalizations.current.`. -4. Make sure to pass `errorText` and `checkNullOrEmpty` to the base class. -5. Add static method to `form_builder_validators.dart` that uses the 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: From 330caf4126bcf6a6a367b305ed7d0f57142d948f Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Mon, 10 Mar 2025 10:14:00 -0300 Subject: [PATCH 16/27] Add the first migration instructions --- README-updated.md | 150 ++++++++++++++++++++++++++- lib/src/form_builder_validators.dart | 16 +-- 2 files changed, 156 insertions(+), 10 deletions(-) diff --git a/README-updated.md b/README-updated.md index 81cd5fd4..8e498aa9 100644 --- a/README-updated.md +++ b/README-updated.md @@ -365,11 +365,157 @@ TextFormField( 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. -TODO implement the remaining of breaking changes +- Instructions on how to update each old API validator to the new API equivalent: + - **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 parameters, 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: + - `checkNullOrEmpty = true`: Given the old api: `FormBuilderValidators.someValidator(..., checkNullOrEmpty:true)`, the equivalent in the new API is `Validators.required(Validators.someEquivalentValidator(...))`. + - `checkNullOrEmpty = false`: Given the old api: `FormBuilderValidators.someValidator(..., checkNullOrEmpty:false)`, the equivalent in the new API is `Validators.optional(Validators.someEquivalentValidator(...))`. + - Bool validators + - For this group of validators, it is expected to receive a `String` as user input. Thus, if your + form widget does not guarantee a `String` input (it may receive an `Object`), you must wrap the + equivalent validator with the type validator for strings. Thus, instead of + `Validators.hasMinChars(...)`, use `Validators.string(Validators.hasMinChars(...))` + - `FormBuilderValidators.hasLowercaseChars(atLeast: n, regex: reg, errorText: 'some error')` is + equivalent to `Validators.hasMinLowercaseChars(min: n, customLowercaseCounter:(input)=>reg.allMatches(input).length, hasMinLowercaseCharsMsg:(_, __)=>'some error')` + - `FormBuilderValidators.hasNumericChars(atLeast: n, regex: reg, errorText: 'some error')` is + equivalent to `Validators.hasMinNumericChars(min: n, customNumericCounter:(input)=>reg.allMatches(input).length, hasMinNumericCharsMsg:(_, __)=>'some error')` + - `FormBuilderValidators.hasSpecialChars(atLeast: n, regex: reg, errorText: 'some error')` is + equivalent to `Validators.hasMinSpecialChars(min: n, customSpecialCounter:(input)=>reg.allMatches(input).length, hasMinSpecialCharsMsg:(_, __)=>'some error')` + - `FormBuilderValidators.hasUppercaseChars(atLeast: n, regex: reg, errorText: 'some error')` is + equivalent to `Validators.hasMinUppercaseChars(min: n, customUppercaseCounter:(input)=>reg.allMatches(input).length, hasMinUppercaseCharsMsg:(_, __)=>'some error')` + + <<<<<<<<<<<<<<>>>>>>>>>>>>>> + - `FormBuilderValidators.isFalse()` - requires the field's to be false. + - `FormBuilderValidators.isTrue()` - requires the field's to be true. + +### Collection validators + +- `FormBuilderValidators.containsElement()` - requires the field's to be in the provided list. +- `FormBuilderValidators.equalLength()` - requires the length of the field's value to be equal to the provided minimum length. +- `FormBuilderValidators.maxLength()` - requires the length of the field's value to be less than or equal to the provided maximum size. +- `FormBuilderValidators.minLength()` - requires the length of the field's value to be greater than or equal to the provided minimum length. +- `FormBuilderValidators.range()` - requires the field's to be within a range. +- `FormBuilderValidators.unique()` - requires the field's to be unique in the provided list. + +### Core validators + +- `FormBuilderValidators.aggregate()` - runs the validators in parallel, collecting all errors. +- `FormBuilderValidators.compose()` - runs each validator against the value provided. +- `FormBuilderValidators.conditional()` - conditionally runs a validator against the value provided. +- `FormBuilderValidators.defaultValue()` - runs the validator using the default value when the provided value is null. +- `FormBuilderValidators.equal()` - requires the field's value to be equal to the provided object. +- `FormBuilderValidators.log()` - runs the validator and logs the value at a specific point in the validation chain. +- `FormBuilderValidators.notEqual()` - requires the field's value to be not equal to the provided object. +- `FormBuilderValidators.or()` - runs each validator against the value provided and passes when any works. +- `FormBuilderValidators.required()` - requires the field to have a non-empty value. +- `FormBuilderValidators.skipWhen()` - runs the validator and skips the validation when a certain condition is met. +- `FormBuilderValidators.transform()` - transforms the value before running the validator. + +### Datetime validators + +- `FormBuilderValidators.dateFuture()` - requires the field's value to be in the future. +- `FormBuilderValidators.datePast()` - requires the field's value to be a in the past. +- `FormBuilderValidators.dateRange()` - requires the field's value to be a within a date range. +- `FormBuilderValidators.dateTime()` - requires the field's value to be a valid date time. +- `FormBuilderValidators.date()` - requires the field's value to be a valid date string. +- `FormBuilderValidators.time()` - requires the field's value to be a valid time string. +- `FormBuilderValidators.timeZone()` - requires the field's value to be a valid time zone. + +### File validators + +- `FormBuilderValidators.fileExtension()` - requires the field's value to a valid file extension. +- `FormBuilderValidators.fileName()` - requires the field's to be a valid file name. +- `FormBuilderValidators.fileSize()` - requires the field's to be less than the max size. +- `FormBuilderValidators.mimeType()` - requires the field's value to a valid MIME type. +- `FormBuilderValidators.path()` - requires the field's to be a valid file or folder path. + +### Finance validators + +- `FormBuilderValidators.bic()` - requires the field's to be a valid BIC. +- `FormBuilderValidators.creditCardCVC()` - requires the field's value to be a valid credit card CVC number. +- `FormBuilderValidators.creditCardExpirationDate()` - requires the field's value to be a valid credit card expiration date and can check if not expired yet. +- `FormBuilderValidators.creditCard()` - requires the field's value to be a valid credit card number. +- `FormBuilderValidators.iban()` - requires the field's to be a valid IBAN. + +### Identity validators + +- `FormBuilderValidators.city()` - requires the field's value to be a valid city name. +- `FormBuilderValidators.country()` - requires the field's value to be a valid country name. +- `FormBuilderValidators.firstName()` - requires the field's value to be a valid first name. +- `FormBuilderValidators.lastName()` - requires the field's value to be a valid last name. +- `FormBuilderValidators.passportNumber()` - requires the field's value to be a valid passport number. +- `FormBuilderValidators.password()` - requires the field's to be a valid password that matched required conditions. +- `FormBuilderValidators.ssn()` - requires the field's to be a valid SSN (Social Security Number). +- `FormBuilderValidators.state()` - requires the field's value to be a valid state name. +- `FormBuilderValidators.street()` - requires the field's value to be a valid street name. +- `FormBuilderValidators.username()` - requires the field's to be a valid username that matched required conditions. +- `FormBuilderValidators.zipCode()` - requires the field's to be a valid zip code. + +### Network validators + +- `FormBuilderValidators.email()` - requires the field's value to be a valid email address. +- `FormBuilderValidators.ip()` - requires the field's value to be a valid IP address. +- `FormBuilderValidators.latitude()` - requires the field's to be a valid latitude. +- `FormBuilderValidators.longitude()` - requires the field's to be a valid longitude. +- `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. ### v10 to v11 diff --git a/lib/src/form_builder_validators.dart b/lib/src/form_builder_validators.dart index 3e1915e1..518a8fb6 100644 --- a/lib/src/form_builder_validators.dart +++ b/lib/src/form_builder_validators.dart @@ -2768,7 +2768,7 @@ final class Validators { /// {@endtemplate} static Validator hasMinUppercaseChars({ c.int min = 1, - c.int Function(String)? customUppercaseCounter, + c.int Function(String input)? customUppercaseCounter, String Function(String input, c.int min)? hasMinUppercaseCharsMsg, }) => val.hasMinUppercaseChars( @@ -2789,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. @@ -2829,7 +2829,7 @@ final class Validators { /// {@endtemplate} static Validator hasMinLowercaseChars({ c.int min = 1, - c.int Function(String)? customLowercaseCounter, + c.int Function(String input)? customLowercaseCounter, String Function(String input, c.int min)? hasMinLowercaseCharsMsg, }) => val.hasMinLowercaseChars( @@ -2850,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)?`): @@ -2895,7 +2895,7 @@ final class Validators { /// {@endtemplate} static Validator hasMinNumericChars({ c.int min = 1, - c.int Function(String)? customNumericCounter, + c.int Function(String input)? customNumericCounter, String Function(String input, c.int min)? hasMinNumericCharsMsg, }) => val.hasMinNumericChars( @@ -2962,7 +2962,7 @@ final class Validators { /// {@endtemplate} static Validator hasMinSpecialChars({ c.int min = 1, - c.int Function(String)? customSpecialCounter, + c.int Function(String input)? customSpecialCounter, String Function(String input, c.int min)? hasMinSpecialCharsMsg, }) => val.hasMinSpecialChars( From b223f311bfcf7710c009ea4433e907cb51170378 Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Wed, 19 Mar 2025 18:18:17 -0300 Subject: [PATCH 17/27] add more breaking changes instructions --- README-updated.md | 45 ++++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/README-updated.md b/README-updated.md index 8e498aa9..5285c12a 100644 --- a/README-updated.md +++ b/README-updated.md @@ -376,7 +376,7 @@ see [override_form_builder_localizations_en](example/lib/override_form_builder_l - For this group of validators, it is expected to receive a `String` as user input. Thus, if your form widget does not guarantee a `String` input (it may receive an `Object`), you must wrap the equivalent validator with the type validator for strings. Thus, instead of - `Validators.hasMinChars(...)`, use `Validators.string(Validators.hasMinChars(...))` + `Validators.hasMinChars(...)`, use `Validators.string(Validators.hasMinChars(...))`. - `FormBuilderValidators.hasLowercaseChars(atLeast: n, regex: reg, errorText: 'some error')` is equivalent to `Validators.hasMinLowercaseChars(min: n, customLowercaseCounter:(input)=>reg.allMatches(input).length, hasMinLowercaseCharsMsg:(_, __)=>'some error')` - `FormBuilderValidators.hasNumericChars(atLeast: n, regex: reg, errorText: 'some error')` is @@ -385,20 +385,39 @@ see [override_form_builder_localizations_en](example/lib/override_form_builder_l equivalent to `Validators.hasMinSpecialChars(min: n, customSpecialCounter:(input)=>reg.allMatches(input).length, hasMinSpecialCharsMsg:(_, __)=>'some error')` - `FormBuilderValidators.hasUppercaseChars(atLeast: n, regex: reg, errorText: 'some error')` is equivalent to `Validators.hasMinUppercaseChars(min: n, customUppercaseCounter:(input)=>reg.allMatches(input).length, hasMinUppercaseCharsMsg:(_, __)=>'some error')` - - <<<<<<<<<<<<<<>>>>>>>>>>>>>> - - `FormBuilderValidators.isFalse()` - requires the field's to be false. - - `FormBuilderValidators.isTrue()` - requires the field's to be true. + - `FormBuilderValidators.isFalse(errorText:'some error')` is equivalent to `Validators.isFalse(isFalseMsg: (_)=>'some error')` + - `FormBuilderValidators.isTrue(errorText:'some error')` is equivalent to `Validators.isTrue(isTrueMsg: (_)=>'some error')` + + - Collection validators + - `FormBuilderValidators.containsElement([v1, v2, v3], errorText:'some error')` is + equivalent to `Validators.inList([v1, v2, v3], inListMsg: (_, __)=>'some error')` + - `FormBuilderValidators.equalLength(n, ~~allowEmpty: allowEmpty~~, errorText:'some error')` is equivalent to + `Validators.equalLength(n, equalLengthMsg: (_, __)=>'some error')` + - The parameter `allowEmpty` was removed and additional logic must be provided to handle the + case in which this parameter is true. Probably something like: `or([equalLength(0),equalLength(n)])` + - `FormBuilderValidators.maxLength(n, errorText:'some error')` is equivalent to + `Validators.maxLength(n, maxLengthMsg: (_, __)=>'some error')` + - `FormBuilderValidators.minLength(n, errorText:'some error')` is equivalent to + `Validators.minLength(n, minLengthMsg: (_, __)=>'some error')` + - `FormBuilderValidators.range(minValue, maxValue, inclusive:inclusive, errorText:'some error')` is equivalent to: + - `Validators.betweenLength(minValue, maxValue, betweenLengthMsg: (_)=>'some error')` if the + user input is a collection. This is only for `inclusive:true`, thus if `inclusive` is `false`, the correct + equivalent would be `Validators.betweenLength(minValue + 1, maxValue - 1, betweenLengthMsg: (_)=>'some error')` + - `Validators.between(minValue, maxValue, minInclusive:inclusive, maxInclusive:inclusive, betweenMsg: (_1, _2, _3, _4, _5)=>'some error')` + if the user input is numeric. + - `FormBuilderValidators.unique([v1, v2, v3], errorText:'some error')`: there is no equivalent to this validator, + 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; + }; -### Collection validators - -- `FormBuilderValidators.containsElement()` - requires the field's to be in the provided list. -- `FormBuilderValidators.equalLength()` - requires the length of the field's value to be equal to the provided minimum length. -- `FormBuilderValidators.maxLength()` - requires the length of the field's value to be less than or equal to the provided maximum size. -- `FormBuilderValidators.minLength()` - requires the length of the field's value to be greater than or equal to the provided minimum length. -- `FormBuilderValidators.range()` - requires the field's to be within a range. -- `FormBuilderValidators.unique()` - requires the field's to be unique in the provided list. + } + ``` +// TODO continue from here... ### Core validators - `FormBuilderValidators.aggregate()` - runs the validators in parallel, collecting all errors. From 9d559b989c52bd1421069a0abfe611389a4503c2 Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Fri, 28 Mar 2025 21:55:21 -0300 Subject: [PATCH 18/27] add changelog to core validators --- README-updated.md | 206 +++++++++++++++++++++++++--------------------- 1 file changed, 112 insertions(+), 94 deletions(-) diff --git a/README-updated.md b/README-updated.md index 5285c12a..10f0fd36 100644 --- a/README-updated.md +++ b/README-updated.md @@ -71,18 +71,32 @@ This package comes with several most common `FormFieldValidator`s such as requir 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. Required 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 or type validation. +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 one and then check if +- 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 @@ -96,8 +110,8 @@ it is greater than 10. #### Composition validators -- `Validators.and(validators)`: Validates the field by requiring it to pass all validators in the provided list of validators: `validators`. -- `Validators.or(validators)`: Validates the field by requiring it to pass at least one of the validators in the provided list of validators: `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 @@ -112,7 +126,7 @@ it is greater than 10. - `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` (!=). -#### Required validators +#### 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. @@ -167,17 +181,18 @@ Validators that check a generic type user input. ### Network validators -- `Validators.ip()`: Checks if the field contains a properly formatted `Internet Protocol` (IP) address. +- `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). - TODO `FormBuilderValidators.email()` - requires the field's value to be a valid email address. -- TODO `FormBuilderValidators.latitude()` - requires the field's to be a valid latitude. - `FormBuilderValidators.longitude()` - requires the field's to be a valid longitude. +- TODO `FormBuilderValidators.latitude()` - requires the field's to be a valid latitude. +- TODO `FormBuilderValidators.longitude()` - requires the field's to be a valid longitude. - TODO `FormBuilderValidators.macAddress()` - requires the field's to be a valid MAC address. - TODO `FormBuilderValidators.phoneNumber()` - requires the field's value to be a valid phone number. - TODO `FormBuilderValidators.portNumber()` - requires the field's to be a valid port number. ### Numeric validators -- `Validators.between(min, max)`: Checks if the field contains a number that is in the range [min, max]. +- `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`. @@ -294,8 +309,9 @@ And you can still add your custom error messages. ### 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`. +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: [ @@ -309,12 +325,13 @@ return MaterialApp( localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, - FormBuilderLocalizations.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'), @@ -327,14 +344,13 @@ See [pub.dev example tab](https://pub.dev/packages/form_builder_validators/examp ### Specific uses -#### Composing multiple validators - -The `FormBuilderValidators` class comes with a handy static function named `compose()`, which takes a list of `FormFieldValidator` functions. Composing allows you to create once and reuse validation rules across multiple fields, widgets, or apps. +#### Composing multiple validators with the logical `AND` semantics -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 returned string. +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. -Example: +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'), @@ -344,8 +360,8 @@ TextFormField( Validators.and(>[ Validators.num(Validators.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 `Validators.greaterThanOrEqualTo(0)` instead + /// 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); @@ -368,70 +384,78 @@ see [override_form_builder_localizations_en](example/lib/override_form_builder_l ### v11 to v12 - Deprecate `FormBuilderValidators` class with its static methods as validators. - Instead, you should use `Validators` class. -- Instructions on how to update each old API validator to the new API equivalent: - - **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 parameters, 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: - - `checkNullOrEmpty = true`: Given the old api: `FormBuilderValidators.someValidator(..., checkNullOrEmpty:true)`, the equivalent in the new API is `Validators.required(Validators.someEquivalentValidator(...))`. - - `checkNullOrEmpty = false`: Given the old api: `FormBuilderValidators.someValidator(..., checkNullOrEmpty:false)`, the equivalent in the new API is `Validators.optional(Validators.someEquivalentValidator(...))`. - - Bool validators - - For this group of validators, it is expected to receive a `String` as user input. Thus, if your - form widget does not guarantee a `String` input (it may receive an `Object`), you must wrap the - equivalent validator with the type validator for strings. Thus, instead of - `Validators.hasMinChars(...)`, use `Validators.string(Validators.hasMinChars(...))`. - - `FormBuilderValidators.hasLowercaseChars(atLeast: n, regex: reg, errorText: 'some error')` is - equivalent to `Validators.hasMinLowercaseChars(min: n, customLowercaseCounter:(input)=>reg.allMatches(input).length, hasMinLowercaseCharsMsg:(_, __)=>'some error')` - - `FormBuilderValidators.hasNumericChars(atLeast: n, regex: reg, errorText: 'some error')` is - equivalent to `Validators.hasMinNumericChars(min: n, customNumericCounter:(input)=>reg.allMatches(input).length, hasMinNumericCharsMsg:(_, __)=>'some error')` - - `FormBuilderValidators.hasSpecialChars(atLeast: n, regex: reg, errorText: 'some error')` is - equivalent to `Validators.hasMinSpecialChars(min: n, customSpecialCounter:(input)=>reg.allMatches(input).length, hasMinSpecialCharsMsg:(_, __)=>'some error')` - - `FormBuilderValidators.hasUppercaseChars(atLeast: n, regex: reg, errorText: 'some error')` is - equivalent to `Validators.hasMinUppercaseChars(min: n, customUppercaseCounter:(input)=>reg.allMatches(input).length, hasMinUppercaseCharsMsg:(_, __)=>'some error')` - - `FormBuilderValidators.isFalse(errorText:'some error')` is equivalent to `Validators.isFalse(isFalseMsg: (_)=>'some error')` - - `FormBuilderValidators.isTrue(errorText:'some error')` is equivalent to `Validators.isTrue(isTrueMsg: (_)=>'some error')` - - - Collection validators - - `FormBuilderValidators.containsElement([v1, v2, v3], errorText:'some error')` is - equivalent to `Validators.inList([v1, v2, v3], inListMsg: (_, __)=>'some error')` - - `FormBuilderValidators.equalLength(n, ~~allowEmpty: allowEmpty~~, errorText:'some error')` is equivalent to - `Validators.equalLength(n, equalLengthMsg: (_, __)=>'some error')` - - The parameter `allowEmpty` was removed and additional logic must be provided to handle the - case in which this parameter is true. Probably something like: `or([equalLength(0),equalLength(n)])` - - `FormBuilderValidators.maxLength(n, errorText:'some error')` is equivalent to - `Validators.maxLength(n, maxLengthMsg: (_, __)=>'some error')` - - `FormBuilderValidators.minLength(n, errorText:'some error')` is equivalent to - `Validators.minLength(n, minLengthMsg: (_, __)=>'some error')` - - `FormBuilderValidators.range(minValue, maxValue, inclusive:inclusive, errorText:'some error')` is equivalent to: - - `Validators.betweenLength(minValue, maxValue, betweenLengthMsg: (_)=>'some error')` if the - user input is a collection. This is only for `inclusive:true`, thus if `inclusive` is `false`, the correct - equivalent would be `Validators.betweenLength(minValue + 1, maxValue - 1, betweenLengthMsg: (_)=>'some error')` - - `Validators.between(minValue, maxValue, minInclusive:inclusive, maxInclusive:inclusive, betweenMsg: (_1, _2, _3, _4, _5)=>'some error')` - if the user input is numeric. - - `FormBuilderValidators.unique([v1, v2, v3], errorText:'some error')`: there is no equivalent to this validator, - 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; - }; +- 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 parameters, 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); -// TODO continue from here... -### Core validators +// New API +Validators.required(Validators.someEquivalentValidator(...)); -- `FormBuilderValidators.aggregate()` - runs the validators in parallel, collecting all errors. -- `FormBuilderValidators.compose()` - runs each validator against the value provided. -- `FormBuilderValidators.conditional()` - conditionally runs a validator against the value provided. -- `FormBuilderValidators.defaultValue()` - runs the validator using the default value when the provided value is null. -- `FormBuilderValidators.equal()` - requires the field's value to be equal to the provided object. -- `FormBuilderValidators.log()` - runs the validator and logs the value at a specific point in the validation chain. -- `FormBuilderValidators.notEqual()` - requires the field's value to be not equal to the provided object. -- `FormBuilderValidators.or()` - runs each validator against the value provided and passes when any works. -- `FormBuilderValidators.required()` - requires the field to have a non-empty value. -- `FormBuilderValidators.skipWhen()` - runs the validator and skips the validation when a certain condition is met. -- `FormBuilderValidators.transform()` - transforms the value before running the validator. +``` +- `checkNullOrEmpty = false`: Given the old api: `FormBuilderValidators.someValidator(..., checkNullOrEmpty:false)`, the equivalent in the new API is `Validators.optional(Validators.someEquivalentValidator(...))`. + +#### Bool validators + +- For the following group of validators, it is expected to receive a `String` as user input. Thus, if your form widget does not guarantee a `String` input (it may receive an `Object`), you must wrap the equivalent validator with the type validator for strings. Thus, instead of `Validators.hasMinChars(...)`, use `Validators.string(Validators.hasMinChars(...))`. Now, check the specific case for each old validator: + - `FormBuilderValidators.hasLowercaseChars(atLeast: n, regex: reg, errorText: 'some error')` is + equivalent to `Validators.hasMinLowercaseChars(min: n, customLowercaseCounter:(input)=>reg.allMatches(input).length, hasMinLowercaseCharsMsg:(_, __)=>'some error')` + - `FormBuilderValidators.hasNumericChars(atLeast: n, regex: reg, errorText: 'some error')` is + equivalent to `Validators.hasMinNumericChars(min: n, customNumericCounter:(input)=>reg.allMatches(input).length, hasMinNumericCharsMsg:(_, __)=>'some error')` + - `FormBuilderValidators.hasSpecialChars(atLeast: n, regex: reg, errorText: 'some error')` is + equivalent to `Validators.hasMinSpecialChars(min: n, customSpecialCounter:(input)=>reg.allMatches(input).length, hasMinSpecialCharsMsg:(_, __)=>'some error')` + - `FormBuilderValidators.hasUppercaseChars(atLeast: n, regex: reg, errorText: 'some error')` is + equivalent to `Validators.hasMinUppercaseChars(min: n, customUppercaseCounter:(input)=>reg.allMatches(input).length, hasMinUppercaseCharsMsg:(_, __)=>'some error')` +- `FormBuilderValidators.isFalse(errorText:'some error')` is equivalent to `Validators.isFalse(isFalseMsg: (_)=>'some error')` +- `FormBuilderValidators.isTrue(errorText:'some error')` is equivalent to `Validators.isTrue(isTrueMsg: (_)=>'some error')` + +#### Collection validators +- `FormBuilderValidators.containsElement([v1, v2, v3], errorText:'some error')` is +equivalent to `Validators.inList([v1, v2, v3], inListMsg: (_, __)=>'some error')` +- `FormBuilderValidators.equalLength(n, `~~allowEmpty: allowEmpty~~`, errorText:'some error')` is equivalent to `Validators.equalLength(n, equalLengthMsg: (_, __)=>'some error')` + - 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. Probably something like: `or([equalLength(0),equalLength(n)])` +- `FormBuilderValidators.maxLength(n, errorText:'some error')` is equivalent to + `Validators.maxLength(n, maxLengthMsg: (_, __)=>'some error')` +- `FormBuilderValidators.minLength(n, errorText:'some error')` is equivalent to + `Validators.minLength(n, minLengthMsg: (_, __)=>'some error')` +- `FormBuilderValidators.range(minValue, maxValue, inclusive:inclusive, errorText:'some error')` is equivalent to: + - `Validators.betweenLength(minValue, maxValue, betweenLengthMsg: (_)=>'some error')` if the + user input is a collection. This is only for `inclusive:true`, thus if `inclusive` is `false`, the correct + equivalent would be `Validators.betweenLength(minValue + 1, maxValue - 1, betweenLengthMsg: (_)=>'some error')` + - `Validators.between(minValue, maxValue, minInclusive:inclusive, maxInclusive:inclusive, betweenMsg: (_1, _2, _3, _4, _5)=>'some error')` + if the user input is numeric. +- `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(validators)` is equivalent to `Validators.and(validators, separator:'\n', printErrorAsSoonAsPossible:false)` +- `FormBuilderValidators.compose()` is equivalent to `Validators.and(validators)` +- `FormBuilderValidators.conditional(condition, validator)` is equivalent to `Validators.validateIf(condition, validator)` +- `FormBuilderValidators.defaultValue(defaultValue, validator)` is equivalent to `Validators.validateWithDefault(defaultValue, validator)` +- `FormBuilderValidators.equal(value, 'error text')` is equivalent to `Validators.equal(value, (_, __)=>'error text')` +- `FormBuilderValidators.log(log, 'error text')` is equivalent to `Validators.debugPrintValidator((input)=>log?.call(input) ?? 'error text')` +- `FormBuilderValidators.notEqual(value, 'error text')` is equivalent to `Validators.notEqual(value, (_, __)=>'error text')` +- `FormBuilderValidators.or(validators)` is close to `Validators.or(validators)` +- `FormBuilderValidators.required('error text')` is equivalent to `Validators.required(null, 'error text')` +- `FormBuilderValidators.skipWhen(condition, validator)` is equivalent to `Validators.skipIf(condition, validator)` +- `FormBuilderValidators.transform(transformer, validator)` is equivalent to `Validators.transformAndValidate(transformer, next:validator)` + +TODO continue from here.... ### Datetime validators - `FormBuilderValidators.dateFuture()` - requires the field's value to be in the future. @@ -564,17 +588,11 @@ String? isEven(int? userInput) { ``` 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. - 1. Code duplication: When composing validators, the same null-checking logic must be repeated - across multiple validators, violating the DRY principle. - 1. 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. - 1. 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. - 1. 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: + 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) { @@ -604,9 +622,9 @@ final validator = required(isEven()); By introducing this level of indirection, we achieve: 1. Clean separation between null checks and validation logic - 1. More composable validators - 1. Specific error messages for missing vs invalid input - 1. Type-safe validator chains + 2. More composable validators + 3. Specific error messages for missing vs invalid input + 4. Type-safe validator chains ## Support From a5bd74b39e9a2b167602237f7aa20f56c760ba30 Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Sat, 29 Mar 2025 19:43:36 -0300 Subject: [PATCH 19/27] refactor breaking changes and add more items to it --- README-updated.md | 619 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 512 insertions(+), 107 deletions(-) diff --git a/README-updated.md b/README-updated.md index 10f0fd36..97dbba47 100644 --- a/README-updated.md +++ b/README-updated.md @@ -141,25 +141,15 @@ TextFormField( - `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`. -- TODO `FormBuilderValidators.date()` - requires the field's value to be a valid date string. -- TODO `FormBuilderValidators.time()` - requires the field's value to be a valid time string. -- TODO `FormBuilderValidators.timeZone()` - requires the field's value to be a valid time zone. ### File validators -- TODO `FormBuilderValidators.fileExtension()` - requires the field's value to a valid file extension. -- TODO `FormBuilderValidators.fileName()` - requires the field's to be a valid file name. -- TODO `FormBuilderValidators.fileSize()` - requires the field's to be less than the max size. -- TODO `FormBuilderValidators.mimeType()` - requires the field's value to a valid MIME type. -- TODO `FormBuilderValidators.path()` - requires the field's to be a valid file or folder path. +- TODO [ ] `FormBuilderValidators.fileSize()` - requires the field's to be less than the max size. ### Finance validators -- TODO `FormBuilderValidators.bic()` - requires the field's to be a valid BIC. -- TODO `FormBuilderValidators.creditCardCVC()` - requires the field's value to be a valid credit card CVC number. -- TODO `FormBuilderValidators.creditCardExpirationDate()` - requires the field's value to be a valid credit card expiration date and can check if not expired yet. -- `Validators.creditCard()`: Checks if the field contains a valid credit card number. -- TODO `FormBuilderValidators.iban()` - requires the field's to be a valid IBAN. +- TODO [ ] `FormBuilderValidators.bic()` - requires the field's to be a valid BIC. +- TODO [ ] `FormBuilderValidators.iban()` - requires the field's to be a valid IBAN. ### Generic Type Validators Validators that check a generic type user input. @@ -170,25 +160,15 @@ Validators that check a generic type user input. ### Miscellaneous validators -- TODO `FormBuilderValidators.base64()` - requires the field's to be a valid base64 string. -- TODO `FormBuilderValidators.colorCode()` - requires the field's value to be a valid color code. -- TODO `FormBuilderValidators.duns()` - requires the field's value to be a valid DUNS. +- 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. -- TODO `FormBuilderValidators.json()` - requires the field's to be a valid json string. -- TODO `FormBuilderValidators.languageCode()` - requires the field's to be a valid language code. -- TODO `FormBuilderValidators.licensePlate()` - requires the field's to be a valid license plate. -- TODO `FormBuilderValidators.vin()` - requires the field's to be a valid VIN number. ### 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). -- TODO `FormBuilderValidators.email()` - requires the field's value to be a valid email address. -- TODO `FormBuilderValidators.latitude()` - requires the field's to be a valid latitude. -- TODO `FormBuilderValidators.longitude()` - requires the field's to be a valid longitude. -- TODO `FormBuilderValidators.macAddress()` - requires the field's to be a valid MAC address. -- TODO `FormBuilderValidators.phoneNumber()` - requires the field's value to be a valid phone number. -- TODO `FormBuilderValidators.portNumber()` - requires the field's to be a valid port number. +- TODO [ ] `FormBuilderValidators.email()` - requires the field's value to be a valid email address. +- TODO [ ] `FormBuilderValidators.macAddress()` - requires the field's to be a valid MAC address. ### Numeric validators @@ -197,12 +177,6 @@ Validators that check a generic type user input. - `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`. -- TODO `FormBuilderValidators.evenNumber()` - requires the field's to be an even number. -- TODO `FormBuilderValidators.negativeNumber()` - requires the field's to be a negative number. -- TODO `FormBuilderValidators.notZeroNumber()` - requires the field's to be not a number zero. -- TODO `FormBuilderValidators.oddNumber()` - requires the field's to be an odd number. -- TODO `FormBuilderValidators.positiveNumber()` - requires the field's to be a positive number. -- TODO `FormBuilderValidators.prime()` - requires the field's to be a prime number. ### Path Validators - `Validators.matchesAllowedExtensions(extensions)`: Checks if the field contains a `String` that is in the list `extensions`. @@ -210,21 +184,18 @@ Validators that check a generic type user input. ### String validators - `Validators.contains(substring)` - Checks if the field contains the `substring`. -- TODO `FormBuilderValidators.endsWith()` - requires the substring 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.lowercase()` - requires the field's value to be lowercase. -- TODO `FormBuilderValidators.uppercase()` - requires the field's value to be uppercase. +- TODO [ ] `FormBuilderValidators.endsWith()` - requires the substring 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.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. - `Validators.hasMinLowercaseChars(min:min)` - Checks if the field has a minimum number of lowercase chars. - `Validators.hasMinNumericChars(min:min)` - Checks if the field has a minimum number of numeric chars. - `Validators.hasMinSpecialChars(min:min)` - Checks if the field has a minimum number of special chars. - `Validators.match(regExp)` - Checks if the field matches with the regular expression `regExp`. -- TODO `FormBuilderValidators.matchNot()` - requires the field's value to not match the provided regex pattern. - `Validators.uuid()` - Checks if the field is a valid Universally Unique Identifier (UUID). -- TODO `FormBuilderValidators.alphabetical()` - requires the field's to contain only alphabetical characters. -- 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. -- TODO `FormBuilderValidators.singleLine()` - requires the field's string to be a single line of text. +- 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. @@ -240,16 +211,6 @@ Validators that check a generic type user input. - `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. -- TODO `FormBuilderValidators.city()` - requires the field's value to be a valid city name. -- TODO `FormBuilderValidators.country()` - requires the field's value to be a valid country name. -- TODO `FormBuilderValidators.firstName()` - requires the field's value to be a valid first name. -- TODO `FormBuilderValidators.lastName()` - requires the field's value to be a valid last name. -- TODO `FormBuilderValidators.passportNumber()` - requires the field's value to be a valid passport number. -- TODO `FormBuilderValidators.ssn()` - requires the field's to be a valid SSN (Social Security Number). -- TODO `FormBuilderValidators.state()` - requires the field's value to be a valid state name. -- TODO `FormBuilderValidators.street()` - requires the field's value to be a valid street name. -- TODO `FormBuilderValidators.username()` - requires the field's to be a valid username that matched required conditions. -- TODO `FormBuilderValidators.zipCode()` - requires the field's to be a valid zip code. ## Supported languages @@ -398,77 +359,521 @@ FormBuilderValidators.someValidator(..., checkNullOrEmpty:true); Validators.required(Validators.someEquivalentValidator(...)); ``` -- `checkNullOrEmpty = false`: Given the old api: `FormBuilderValidators.someValidator(..., checkNullOrEmpty:false)`, the equivalent in the new API is `Validators.optional(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, it is expected to receive a `String` as user input. Thus, if your form widget does not guarantee a `String` input (it may receive an `Object`), you must wrap the equivalent validator with the type validator for strings. Thus, instead of `Validators.hasMinChars(...)`, use `Validators.string(Validators.hasMinChars(...))`. Now, check the specific case for each old validator: - - `FormBuilderValidators.hasLowercaseChars(atLeast: n, regex: reg, errorText: 'some error')` is - equivalent to `Validators.hasMinLowercaseChars(min: n, customLowercaseCounter:(input)=>reg.allMatches(input).length, hasMinLowercaseCharsMsg:(_, __)=>'some error')` - - `FormBuilderValidators.hasNumericChars(atLeast: n, regex: reg, errorText: 'some error')` is - equivalent to `Validators.hasMinNumericChars(min: n, customNumericCounter:(input)=>reg.allMatches(input).length, hasMinNumericCharsMsg:(_, __)=>'some error')` - - `FormBuilderValidators.hasSpecialChars(atLeast: n, regex: reg, errorText: 'some error')` is - equivalent to `Validators.hasMinSpecialChars(min: n, customSpecialCounter:(input)=>reg.allMatches(input).length, hasMinSpecialCharsMsg:(_, __)=>'some error')` - - `FormBuilderValidators.hasUppercaseChars(atLeast: n, regex: reg, errorText: 'some error')` is - equivalent to `Validators.hasMinUppercaseChars(min: n, customUppercaseCounter:(input)=>reg.allMatches(input).length, hasMinUppercaseCharsMsg:(_, __)=>'some error')` -- `FormBuilderValidators.isFalse(errorText:'some error')` is equivalent to `Validators.isFalse(isFalseMsg: (_)=>'some error')` -- `FormBuilderValidators.isTrue(errorText:'some error')` is equivalent to `Validators.isTrue(isTrueMsg: (_)=>'some error')` +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 +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([v1, v2, v3], errorText:'some error')` is -equivalent to `Validators.inList([v1, v2, v3], inListMsg: (_, __)=>'some error')` -- `FormBuilderValidators.equalLength(n, `~~allowEmpty: allowEmpty~~`, errorText:'some error')` is equivalent to `Validators.equalLength(n, equalLengthMsg: (_, __)=>'some error')` - - 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. Probably something like: `or([equalLength(0),equalLength(n)])` -- `FormBuilderValidators.maxLength(n, errorText:'some error')` is equivalent to - `Validators.maxLength(n, maxLengthMsg: (_, __)=>'some error')` -- `FormBuilderValidators.minLength(n, errorText:'some error')` is equivalent to - `Validators.minLength(n, minLengthMsg: (_, __)=>'some error')` -- `FormBuilderValidators.range(minValue, maxValue, inclusive:inclusive, errorText:'some error')` is equivalent to: - - `Validators.betweenLength(minValue, maxValue, betweenLengthMsg: (_)=>'some error')` if the - user input is a collection. This is only for `inclusive:true`, thus if `inclusive` is `false`, the correct - equivalent would be `Validators.betweenLength(minValue + 1, maxValue - 1, betweenLengthMsg: (_)=>'some error')` - - `Validators.between(minValue, maxValue, minInclusive:inclusive, maxInclusive:inclusive, betweenMsg: (_1, _2, _3, _4, _5)=>'some error')` - if the user input is numeric. -- `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. +- `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; - }; - } - ``` +```dart +Validator unique(List values, {String? errorText}){ + return (input){ + return values.where((element) => element == input).length > 1? errorText:null; + }; + +} +``` ### Core validators -- `FormBuilderValidators.aggregate(validators)` is equivalent to `Validators.and(validators, separator:'\n', printErrorAsSoonAsPossible:false)` -- `FormBuilderValidators.compose()` is equivalent to `Validators.and(validators)` -- `FormBuilderValidators.conditional(condition, validator)` is equivalent to `Validators.validateIf(condition, validator)` -- `FormBuilderValidators.defaultValue(defaultValue, validator)` is equivalent to `Validators.validateWithDefault(defaultValue, validator)` -- `FormBuilderValidators.equal(value, 'error text')` is equivalent to `Validators.equal(value, (_, __)=>'error text')` -- `FormBuilderValidators.log(log, 'error text')` is equivalent to `Validators.debugPrintValidator((input)=>log?.call(input) ?? 'error text')` -- `FormBuilderValidators.notEqual(value, 'error text')` is equivalent to `Validators.notEqual(value, (_, __)=>'error text')` -- `FormBuilderValidators.or(validators)` is close to `Validators.or(validators)` -- `FormBuilderValidators.required('error text')` is equivalent to `Validators.required(null, 'error text')` -- `FormBuilderValidators.skipWhen(condition, validator)` is equivalent to `Validators.skipIf(condition, validator)` -- `FormBuilderValidators.transform(transformer, validator)` is equivalent to `Validators.transformAndValidate(transformer, next:validator)` - -TODO continue from here.... +- `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( + (value) => print('Validating: $value'), + 'Error message' +); + +// New API equivalent +Validators.debugPrintValidator( + (input) => print('Validating: $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()` - requires the field's value to be in the future. -- `FormBuilderValidators.datePast()` - requires the field's value to be a in the past. -- `FormBuilderValidators.dateRange()` - requires the field's value to be a within a date range. -- `FormBuilderValidators.dateTime()` - requires the field's value to be a valid date time. -- `FormBuilderValidators.date()` - requires the field's value to be a valid date string. -- `FormBuilderValidators.time()` - requires the field's value to be a valid time string. -- `FormBuilderValidators.timeZone()` - requires the field's value to be a valid time zone. +- `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()` - requires the field's value to a valid file extension. +- `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, +); +``` +TODO continue from here!!! - `FormBuilderValidators.fileName()` - requires the field's to be a valid file name. - `FormBuilderValidators.fileSize()` - requires the field's to be less than the max size. - `FormBuilderValidators.mimeType()` - requires the field's value to a valid MIME type. From e12df529745827a7e996c5617fd8a36f28e212a7 Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Wed, 2 Apr 2025 22:27:33 -0300 Subject: [PATCH 20/27] implement max file size validator --- README-updated.md | 623 +--------------- doc/migration.md | 674 ++++++++++++++++++ lib/src/form_builder_validators.dart | 42 ++ lib/src/validators/file_validators.dart | 120 ++++ lib/src/validators/validators.dart | 1 + .../max_file_size_validator_test.dart | 268 +++++++ 6 files changed, 1108 insertions(+), 620 deletions(-) create mode 100644 doc/migration.md create mode 100644 lib/src/validators/file_validators.dart create mode 100644 test/src/validators/file_validators/max_file_size_validator_test.dart diff --git a/README-updated.md b/README-updated.md index 97dbba47..b9e8dc85 100644 --- a/README-updated.md +++ b/README-updated.md @@ -144,7 +144,8 @@ TextFormField( ### File validators -- TODO [ ] `FormBuilderValidators.fileSize()` - requires the field's to be less than the max size. +- `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 @@ -345,625 +346,7 @@ see [override_form_builder_localizations_en](example/lib/override_form_builder_l ### v11 to v12 - Deprecate `FormBuilderValidators` class with its static methods as validators. - Instead, you should use `Validators` class. -- 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 parameters, 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 -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( - (value) => print('Validating: $value'), - 'Error message' -); - -// New API equivalent -Validators.debugPrintValidator( - (input) => print('Validating: $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, -); -``` -TODO continue from here!!! -- `FormBuilderValidators.fileName()` - requires the field's to be a valid file name. -- `FormBuilderValidators.fileSize()` - requires the field's to be less than the max size. -- `FormBuilderValidators.mimeType()` - requires the field's value to a valid MIME type. -- `FormBuilderValidators.path()` - requires the field's to be a valid file or folder path. - -### Finance validators - -- `FormBuilderValidators.bic()` - requires the field's to be a valid BIC. -- `FormBuilderValidators.creditCardCVC()` - requires the field's value to be a valid credit card CVC number. -- `FormBuilderValidators.creditCardExpirationDate()` - requires the field's value to be a valid credit card expiration date and can check if not expired yet. -- `FormBuilderValidators.creditCard()` - requires the field's value to be a valid credit card number. -- `FormBuilderValidators.iban()` - requires the field's to be a valid IBAN. - -### Identity validators - -- `FormBuilderValidators.city()` - requires the field's value to be a valid city name. -- `FormBuilderValidators.country()` - requires the field's value to be a valid country name. -- `FormBuilderValidators.firstName()` - requires the field's value to be a valid first name. -- `FormBuilderValidators.lastName()` - requires the field's value to be a valid last name. -- `FormBuilderValidators.passportNumber()` - requires the field's value to be a valid passport number. -- `FormBuilderValidators.password()` - requires the field's to be a valid password that matched required conditions. -- `FormBuilderValidators.ssn()` - requires the field's to be a valid SSN (Social Security Number). -- `FormBuilderValidators.state()` - requires the field's value to be a valid state name. -- `FormBuilderValidators.street()` - requires the field's value to be a valid street name. -- `FormBuilderValidators.username()` - requires the field's to be a valid username that matched required conditions. -- `FormBuilderValidators.zipCode()` - requires the field's to be a valid zip code. - -### Network validators - -- `FormBuilderValidators.email()` - requires the field's value to be a valid email address. -- `FormBuilderValidators.ip()` - requires the field's value to be a valid IP address. -- `FormBuilderValidators.latitude()` - requires the field's to be a valid latitude. -- `FormBuilderValidators.longitude()` - requires the field's to be a valid longitude. -- `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. +- Check the file [migrations](./doc/migration.md) for detailed instructions. ### v10 to v11 diff --git a/doc/migration.md b/doc/migration.md new file mode 100644 index 00000000..78431809 --- /dev/null +++ b/doc/migration.md @@ -0,0 +1,674 @@ +## 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', +); +``` +TODO continue from here!!! +- `FormBuilderValidators.mimeType()` - requires the field's value to a valid MIME type. +- `FormBuilderValidators.path()` - requires the field's to be a valid file or folder path. + +### Finance validators + +- `FormBuilderValidators.bic()` - requires the field's to be a valid BIC. +- `FormBuilderValidators.creditCardCVC()` - requires the field's value to be a valid credit card CVC number. +- `FormBuilderValidators.creditCardExpirationDate()` - requires the field's value to be a valid credit card expiration date and can check if not expired yet. +- `FormBuilderValidators.creditCard()` - requires the field's value to be a valid credit card number. +- `FormBuilderValidators.iban()` - requires the field's to be a valid IBAN. + +### Identity validators + +- `FormBuilderValidators.city()` - requires the field's value to be a valid city name. +- `FormBuilderValidators.country()` - requires the field's value to be a valid country name. +- `FormBuilderValidators.firstName()` - requires the field's value to be a valid first name. +- `FormBuilderValidators.lastName()` - requires the field's value to be a valid last name. +- `FormBuilderValidators.passportNumber()` - requires the field's value to be a valid passport number. +- `FormBuilderValidators.password()` - requires the field's to be a valid password that matched required conditions. +- `FormBuilderValidators.ssn()` - requires the field's to be a valid SSN (Social Security Number). +- `FormBuilderValidators.state()` - requires the field's value to be a valid state name. +- `FormBuilderValidators.street()` - requires the field's value to be a valid street name. +- `FormBuilderValidators.username()` - requires the field's to be a valid username that matched required conditions. +- `FormBuilderValidators.zipCode()` - requires the field's to be a valid zip code. + +### Network validators + +- `FormBuilderValidators.email()` - requires the field's value to be a valid email address. +- `FormBuilderValidators.ip()` - requires the field's value to be a valid IP address. +- `FormBuilderValidators.latitude()` - requires the field's to be a valid latitude. +- `FormBuilderValidators.longitude()` - requires the field's to be a valid longitude. +- `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/lib/src/form_builder_validators.dart b/lib/src/form_builder_validators.dart index 518a8fb6..f87f1b5e 100644 --- a/lib/src/form_builder_validators.dart +++ b/lib/src/form_builder_validators.dart @@ -3479,6 +3479,48 @@ final class Validators { 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_in_list} /// Creates a validator function that verifies if a given input is in `values`. 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/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/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..78b2cd6d --- /dev/null +++ b/test/src/validators/file_validators/max_file_size_validator_test.dart @@ -0,0 +1,268 @@ +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()); + }); + } + }); +} From 684d899d3872466751b29ff78a50b1e1678e5eed Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Thu, 3 Apr 2025 11:20:58 -0300 Subject: [PATCH 21/27] add some migration items and the validator bic --- README-updated.md | 2 +- doc/migration.md | 52 +++++++++++- lib/src/form_builder_validators.dart | 82 +++++++++++++++---- lib/src/validators/finance_validators.dart | 31 +++++++ .../bic_validator_test.dart | 50 +++++++++++ 5 files changed, 196 insertions(+), 21 deletions(-) create mode 100644 test/src/validators/finance_validators/bic_validator_test.dart diff --git a/README-updated.md b/README-updated.md index b9e8dc85..d8f85ae0 100644 --- a/README-updated.md +++ b/README-updated.md @@ -149,7 +149,7 @@ TextFormField( ### Finance validators -- TODO [ ] `FormBuilderValidators.bic()` - requires the field's to be a valid BIC. +- `FormBuilderValidators.bic()`: Checks if the field contains a valid BIC (Bank Identifier Code). - TODO [ ] `FormBuilderValidators.iban()` - requires the field's to be a valid IBAN. ### Generic Type Validators diff --git a/doc/migration.md b/doc/migration.md index 78431809..f75276bd 100644 --- a/doc/migration.md +++ b/doc/migration.md @@ -583,13 +583,57 @@ Validators.maxFileSize( maxFileSizeMsg: (_, __, ___)=>'error text', ); ``` -TODO continue from here!!! -- `FormBuilderValidators.mimeType()` - requires the field's value to a valid MIME type. -- `FormBuilderValidators.path()` - requires the field's to be a valid file or folder path. +- `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()` - requires the field's to be a valid BIC. +- `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)); +``` +TODO continue from here!!! - `FormBuilderValidators.creditCardCVC()` - requires the field's value to be a valid credit card CVC number. - `FormBuilderValidators.creditCardExpirationDate()` - requires the field's value to be a valid credit card expiration date and can check if not expired yet. - `FormBuilderValidators.creditCard()` - requires the field's value to be a valid credit card number. diff --git a/lib/src/form_builder_validators.dart b/lib/src/form_builder_validators.dart index f87f1b5e..902ac5f4 100644 --- a/lib/src/form_builder_validators.dart +++ b/lib/src/form_builder_validators.dart @@ -2059,9 +2059,9 @@ final class Validators { /// ```dart /// // Basic required field validation /// final validator = Validators.required(); - /// print(validator(null)); // Returns localized error message - /// print(validator('')); // Returns localized error message - /// print(validator('value')); // Returns null (validation passed) + /// 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 = Validators.required( @@ -2122,9 +2122,9 @@ final class Validators { /// ); /// /// // 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 @@ -2171,13 +2171,13 @@ final class Validators { /// final defaultValue = 'default value'; /// final validator = Validators.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) + /// 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: - /// 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) + /// 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( @@ -4100,11 +4100,61 @@ 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_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} @@ -4131,8 +4181,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( @@ -4188,14 +4238,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( diff --git a/lib/src/validators/finance_validators.dart b/lib/src/validators/finance_validators.dart index 1fc11f0b..6b07ab52 100644 --- a/lib/src/validators/finance_validators.dart +++ b/lib/src/validators/finance_validators.dart @@ -18,6 +18,19 @@ 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); + }; +} + //****************************************************************************** //* Aux functions * //****************************************************************************** @@ -51,3 +64,21 @@ 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); +} 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)); + }); + }); +} From 5cd2eb885c69054e81025f8fd5a9054b078789d1 Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Wed, 9 Apr 2025 21:55:57 -0300 Subject: [PATCH 22/27] add some examples and add more migration items --- doc/migration.md | 33 +++++++++++++++++++++++++++---- example/lib/generic_examples.dart | 33 ++++++++++++++++++++----------- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/doc/migration.md b/doc/migration.md index f75276bd..ca9718f1 100644 --- a/doc/migration.md +++ b/doc/migration.md @@ -633,10 +633,35 @@ FormBuilderValidators.bic(regex: someRegex); // New API: Validators.bic(isBic: (input)=>someRegex.hasMatch(input)); ``` -TODO continue from here!!! -- `FormBuilderValidators.creditCardCVC()` - requires the field's value to be a valid credit card CVC number. -- `FormBuilderValidators.creditCardExpirationDate()` - requires the field's value to be a valid credit card expiration date and can check if not expired yet. -- `FormBuilderValidators.creditCard()` - requires the field's value to be a valid credit card number. +- `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', +); + +``` +TODO continue from here!!!! + - `FormBuilderValidators.iban()` - requires the field's to be a valid IBAN. ### Identity validators diff --git a/example/lib/generic_examples.dart b/example/lib/generic_examples.dart index e819444f..e678b66a 100644 --- a/example/lib/generic_examples.dart +++ b/example/lib/generic_examples.dart @@ -64,7 +64,6 @@ class GenericExamplesPage extends StatelessWidget { 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,12 +83,11 @@ 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( @@ -142,19 +140,17 @@ class GenericExamplesPage extends StatelessWidget { 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( @@ -165,7 +161,6 @@ class GenericExamplesPage extends StatelessWidget { 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( @@ -237,6 +233,21 @@ class GenericExamplesPage extends StatelessWidget { 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, + ), ], ), ), From 51e1d846eb0919a3c8a22108934199f1fbd071df Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Sat, 12 Apr 2025 21:12:15 -0300 Subject: [PATCH 23/27] implement iban validators for the new api --- README-updated.md | 4 +- doc/migration.md | 11 +- lib/src/form_builder_validators.dart | 48 ++++++++ lib/src/validators/finance_validators.dart | 42 +++++++ .../iban_validator_test.dart | 105 ++++++++++++++++++ 5 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 test/src/validators/finance_validators/iban_validator_test.dart diff --git a/README-updated.md b/README-updated.md index d8f85ae0..4ecd5755 100644 --- a/README-updated.md +++ b/README-updated.md @@ -149,8 +149,8 @@ TextFormField( ### Finance validators -- `FormBuilderValidators.bic()`: Checks if the field contains a valid BIC (Bank Identifier Code). -- TODO [ ] `FormBuilderValidators.iban()` - requires the field's to be a valid IBAN. +- `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. diff --git a/doc/migration.md b/doc/migration.md index ca9718f1..926ef251 100644 --- a/doc/migration.md +++ b/doc/migration.md @@ -660,9 +660,16 @@ Validators.creditCard( ); ``` -TODO continue from here!!!! -- `FormBuilderValidators.iban()` - requires the field's to be a valid IBAN. +- `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 diff --git a/lib/src/form_builder_validators.dart b/lib/src/form_builder_validators.dart index 902ac5f4..838fcba4 100644 --- a/lib/src/form_builder_validators.dart +++ b/lib/src/form_builder_validators.dart @@ -4106,6 +4106,54 @@ final class Validators { }) => 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). /// diff --git a/lib/src/validators/finance_validators.dart b/lib/src/validators/finance_validators.dart index 6b07ab52..3f924ce2 100644 --- a/lib/src/validators/finance_validators.dart +++ b/lib/src/validators/finance_validators.dart @@ -31,6 +31,19 @@ Validator bic({ }; } +/// {@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 * //****************************************************************************** @@ -82,3 +95,32 @@ bool _isBIC(String value) { 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/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); + }); + }); +} From ef2ff201333472fb49026940058d5dc43b80edc3 Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Sat, 12 Apr 2025 22:17:16 -0300 Subject: [PATCH 24/27] implement notInList validators and add more migration guide items. --- README-updated.md | 1 + doc/migration.md | 52 ++++++++++- example/lib/generic_examples.dart | 27 +++++- lib/l10n/intl_ar.arb | 1 + lib/l10n/intl_bg.arb | 1 + lib/l10n/intl_bn.arb | 1 + lib/l10n/intl_bs.arb | 1 + lib/l10n/intl_ca.arb | 1 + lib/l10n/intl_cs.arb | 1 + lib/l10n/intl_da.arb | 1 + lib/l10n/intl_de.arb | 1 + lib/l10n/intl_el.arb | 1 + lib/l10n/intl_en.arb | 1 + lib/l10n/intl_es.arb | 1 + lib/l10n/intl_et.arb | 1 + lib/l10n/intl_fa.arb | 1 + lib/l10n/intl_fi.arb | 1 + lib/l10n/intl_fr.arb | 1 + lib/l10n/intl_he.arb | 1 + lib/l10n/intl_hi.arb | 1 + lib/l10n/intl_hr.arb | 1 + lib/l10n/intl_hu.arb | 1 + lib/l10n/intl_id.arb | 1 + lib/l10n/intl_it.arb | 1 + lib/l10n/intl_ja.arb | 1 + lib/l10n/intl_km.arb | 1 + lib/l10n/intl_ko.arb | 1 + lib/l10n/intl_ku.arb | 1 + lib/l10n/intl_lo.arb | 1 + lib/l10n/intl_lv.arb | 1 + lib/l10n/intl_mn.arb | 1 + lib/l10n/intl_ms.arb | 1 + lib/l10n/intl_ne.arb | 1 + lib/l10n/intl_nl.arb | 1 + lib/l10n/intl_no.arb | 1 + lib/l10n/intl_pl.arb | 1 + lib/l10n/intl_pt.arb | 1 + lib/l10n/intl_ro.arb | 1 + lib/l10n/intl_ru.arb | 1 + lib/l10n/intl_sk.arb | 1 + lib/l10n/intl_sl.arb | 1 + lib/l10n/intl_sq.arb | 1 + lib/l10n/intl_sv.arb | 1 + lib/l10n/intl_sw.arb | 1 + lib/l10n/intl_ta.arb | 1 + lib/l10n/intl_th.arb | 1 + lib/l10n/intl_tr.arb | 1 + lib/l10n/intl_uk.arb | 1 + lib/l10n/intl_vi.arb | 1 + lib/l10n/intl_zh.arb | 1 + lib/l10n/intl_zh_Hant.arb | 1 + lib/src/form_builder_validators.dart | 47 +++++++++- .../validators/generic_type_validators.dart | 18 ++++ .../max_file_size_validator_test.dart | 3 +- ..._test.dart => in_list_validator_test.dart} | 0 .../not_in_list_validator_test.dart | 91 +++++++++++++++++++ 56 files changed, 282 insertions(+), 5 deletions(-) rename test/src/validators/generic_type_validators/{is_in_list_validator_test.dart => in_list_validator_test.dart} (100%) create mode 100644 test/src/validators/generic_type_validators/not_in_list_validator_test.dart diff --git a/README-updated.md b/README-updated.md index 4ecd5755..756b418e 100644 --- a/README-updated.md +++ b/README-updated.md @@ -156,6 +156,7 @@ TextFormField( Validators that check a generic type user input. - `Validators.inList(values)`: Checks if the field contains a value that is in the list `values`. +- TODO [ ] `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. diff --git a/doc/migration.md b/doc/migration.md index 926ef251..82db28f0 100644 --- a/doc/migration.md +++ b/doc/migration.md @@ -673,8 +673,56 @@ Validators.iban(ibanMsg: (_)=>'invalid iban'); ### Identity validators -- `FormBuilderValidators.city()` - requires the field's value to be a valid city name. -- `FormBuilderValidators.country()` - requires the field's value to be a valid country name. +- `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', + ), +]); +``` + +TODO continue from here... - `FormBuilderValidators.firstName()` - requires the field's value to be a valid first name. - `FormBuilderValidators.lastName()` - requires the field's value to be a valid last name. - `FormBuilderValidators.passportNumber()` - requires the field's value to be a valid passport number. diff --git a/example/lib/generic_examples.dart b/example/lib/generic_examples.dart index e678b66a..66f6a13c 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; @@ -248,6 +248,31 @@ class GenericExamplesPage extends StatelessWidget { 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, + ), ], ), ), 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 838fcba4..ff73b1a7 100644 --- a/lib/src/form_builder_validators.dart +++ b/lib/src/form_builder_validators.dart @@ -3551,7 +3551,7 @@ final class Validators { /// // Creating a validator with a custom error message generator /// final countryValidator = Validators.inList( /// ['USA', 'Canada', 'Mexico'], - /// isInListMsg: (input, values) => + /// inListMsg: (input, values) => /// 'Country $input is not in allowed list: ${values.join(", ")}', /// ); /// @@ -3566,6 +3566,51 @@ final class Validators { }) => 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` /// boolean value, either as a direct boolean or as a string that can be parsed diff --git a/lib/src/validators/generic_type_validators.dart b/lib/src/validators/generic_type_validators.dart index 254f358d..e08f83a8 100644 --- a/lib/src/validators/generic_type_validators.dart +++ b/lib/src/validators/generic_type_validators.dart @@ -19,6 +19,24 @@ Validator inList( }; } +/// {@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, 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 index 78b2cd6d..39c5ef0d 100644 --- a/test/src/validators/file_validators/max_file_size_validator_test.dart +++ b/test/src/validators/file_validators/max_file_size_validator_test.dart @@ -104,7 +104,8 @@ void main() { maxFileSize(max, base: base)(input), expected == null ? isNull - : stringContainsInOrder([expected.max, expected.input])); + : stringContainsInOrder( + [expected.max, expected.input])); }); } diff --git a/test/src/validators/generic_type_validators/is_in_list_validator_test.dart b/test/src/validators/generic_type_validators/in_list_validator_test.dart similarity index 100% rename from test/src/validators/generic_type_validators/is_in_list_validator_test.dart rename to test/src/validators/generic_type_validators/in_list_validator_test.dart 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); + }); + }); +} From 553672409a4695d7d600278dd5063f410aaf02af Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Mon, 14 Apr 2025 21:49:40 -0300 Subject: [PATCH 25/27] update readme --- README-updated.md | 13 ++- doc/migration.md | 197 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 195 insertions(+), 15 deletions(-) diff --git a/README-updated.md b/README-updated.md index 756b418e..57bead9f 100644 --- a/README-updated.md +++ b/README-updated.md @@ -156,14 +156,14 @@ TextFormField( Validators that check a generic type user input. - `Validators.inList(values)`: Checks if the field contains a value that is in the list `values`. -- TODO [ ] `Validators.notInList(values)`: Checks if the field DOES NOT contain 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. +- TODO [ ] `FormBuilderValidators.isbn()` - requires the field's to be a valid ISBN. ### Network validators @@ -185,16 +185,25 @@ Validators that check a generic type user input. ### 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. diff --git a/doc/migration.md b/doc/migration.md index 82db28f0..98cfe252 100644 --- a/doc/migration.md +++ b/doc/migration.md @@ -675,7 +675,7 @@ Validators.iban(ibanMsg: (_)=>'invalid iban'); - `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: +// Old API: FormBuilderValidators.city( regex: RegExp(r'^[A-Z][a-zA-Z\s]+$'), citiesWhitelist: ['CityA', 'CityB', 'CityC'], @@ -683,7 +683,7 @@ FormBuilderValidators.city( errorText: 'invalid city', ); -/// New API (expects input as String): +// New API (expects input as String): Validators.and([ Validators.match( RegExp(r'^[A-Z][a-zA-Z\s]+$'), @@ -702,14 +702,14 @@ Validators.and([ - `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: +// Old API: FormBuilderValidators.country( countryWhitelist: ['CountryA', 'CountryB', 'CountryC'], countryBlacklist: ['CountryD', 'CountryE'], errorText: 'invalid country', ); -/// New API (expects input as String): +// New API (expects input as String): Validators.and([ Validators.inList( ['CountryA', 'CountryB', 'CountryC'], @@ -722,15 +722,186 @@ Validators.and([ ]); ``` -TODO continue from here... -- `FormBuilderValidators.firstName()` - requires the field's value to be a valid first name. -- `FormBuilderValidators.lastName()` - requires the field's value to be a valid last name. -- `FormBuilderValidators.passportNumber()` - requires the field's value to be a valid passport number. -- `FormBuilderValidators.password()` - requires the field's to be a valid password that matched required conditions. -- `FormBuilderValidators.ssn()` - requires the field's to be a valid SSN (Social Security Number). -- `FormBuilderValidators.state()` - requires the field's value to be a valid state name. -- `FormBuilderValidators.street()` - requires the field's value to be a valid street name. -- `FormBuilderValidators.username()` - requires the field's to be a valid username that matched required conditions. +- `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()` - requires the field's to be a valid zip code. ### Network validators From db85c06b2438c21a481014a5bff8578c0bfe881a Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Thu, 24 Apr 2025 21:49:54 -0300 Subject: [PATCH 26/27] add some migrations --- README-updated.md | 2 +- doc/migration.md | 63 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/README-updated.md b/README-updated.md index 57bead9f..3c3468bb 100644 --- a/README-updated.md +++ b/README-updated.md @@ -169,7 +169,7 @@ Validators that check a generic type user input. - `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). -- TODO [ ] `FormBuilderValidators.email()` - requires the field's value to be a valid email address. +- `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 diff --git a/doc/migration.md b/doc/migration.md index 98cfe252..eaf20c9b 100644 --- a/doc/migration.md +++ b/doc/migration.md @@ -902,12 +902,69 @@ Validators.and([ ``` -- `FormBuilderValidators.zipCode()` - requires the field's to be a valid zip code. +- `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()` - requires the field's value to be a valid email address. -- `FormBuilderValidators.ip()` - requires the field's value to be a valid IP address. +- `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()` - requires the field's to be a valid latitude. - `FormBuilderValidators.longitude()` - requires the field's to be a valid longitude. - `FormBuilderValidators.macAddress()` - requires the field's to be a valid MAC address. From 359c8d2d8050bff1726b2f369d72ca45a367a1af Mon Sep 17 00:00:00 2001 From: Artur Assis Alves Date: Thu, 24 Apr 2025 22:09:19 -0300 Subject: [PATCH 27/27] add examples and more migrations --- doc/migration.md | 29 +++++++++++++++++++++++++++-- example/lib/generic_examples.dart | 22 ++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/doc/migration.md b/doc/migration.md index eaf20c9b..c1530e8b 100644 --- a/doc/migration.md +++ b/doc/migration.md @@ -965,8 +965,33 @@ Validators.ip( ); ``` -- `FormBuilderValidators.latitude()` - requires the field's to be a valid latitude. -- `FormBuilderValidators.longitude()` - requires the field's to be a valid longitude. +- `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. diff --git a/example/lib/generic_examples.dart b/example/lib/generic_examples.dart index 66f6a13c..c107c985 100644 --- a/example/lib/generic_examples.dart +++ b/example/lib/generic_examples.dart @@ -273,6 +273,28 @@ class GenericExamplesPage extends StatelessWidget { 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, + ), ], ), ),