diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e12a4d1..da0431bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 12.0.0 +- Deprecated `FormBuilderValidators` class with its static methods as validators. +- Created a new API for the validators. + - Removed the `checkNullOrEmpty` parameter. + - Restructured validators (maintained some intact, removed others, split some into elementary ones) + - Decoupled null check and type check from the remaining validators. + ## 11.2.0 - Fix password validator when not showing default error message diff --git a/README.md b/README.md index 82a22a88..42957731 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Form Builder Validators -Form Builder Validators 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. +Form Builder Validators offer 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 included is the `l10n` / `i18n` of error text messages to multiple languages. +It also 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) @@ -11,36 +11,45 @@ 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. +> We are looking for maintainers to contribute to the development and maintenance of Flutter Form Builder Ecosystem. It 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) + - [Composition validators](#composition-validators) + - [Conditional validators](#conditional-validators) + - [Debug print validator](#debug-print-validator) + - [Equality validators](#equality-validators) + - [Field requirement validators](#field-requirement-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) - [Basic use](#basic-use) - [Specific uses](#specific-uses) - - [Composing multiple validators](#composing-multiple-validators) + - [Composing multiple validators](#composing-multiple-validators-with-the-logical-and-semantics) - [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) + - [v11 to 12](#v11-to-v12) - [v10 to 11](#v10-to-v11) + - [v7 to v8](#v7-to-v8) +- [How this package works](#how-this-package-works) - [Support](#support) - [Contribute](#contribute) - [Add new supported language](#add-new-supported-language) @@ -59,144 +68,160 @@ Also included is 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 of the 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 +Generally, the validators are separated in three main groups: +1. Field Requirement Validators: makes a field optional or required. +2. Type Validators: checks the type of the field. +3. Other validators: make any other kind of check that is not related to null/emptiness or type validation. + +Generally, we build a validator composing those three types in the following way: +`((()))` + +For example: +- Make the field required, check if it is of type `num` or a `String` parsable to num and then check if +it is greater than 10. +`Validators.required(Validators.num(Validators.greaterThan(10)))` + +As easy as that! This validator is meant to be used with form fields like in the following example: +```dart +// Example from the folder `examples` +TextFormField( + decoration: const InputDecoration( + labelText: 'Min Value Field', + prefixIcon: Icon(Icons.exposure_neg_1), + ), + keyboardType: TextInputType.number, + validator: Validators.required(Validators.num(Validators.greaterThan(10))), + textInputAction: TextInputAction.next, + autovalidateMode: AutovalidateMode.always, +); +``` -- `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. +- `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 -- `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 `validators` list. +- `Validators.or(validators)`: Validates the field by requiring it to pass at least one of the validators in the `validators` list. + +#### Conditional validators + +- `Validators.validateIf(condition, v)`: Validates the field with validator `v` only if `condition` is `true`. +- `Validators.skipIf(condition, v)`: Validates the field with validator `v` only if `condition` is `false`. + +#### Debug print validator +- `Validators.debugPrintValidator()`: Print, for debug purposes, the user input value. + +#### Equality validators + +- `Validators.equal(value)`: Checks if the field contains an input that is equal to `value` (==). +- `Validators.notEqual(value)`: Checks if the field contains an input that is not equal to `value` (!=). + +#### Field Requirement Validators + +- `Validators.required(next)`: Makes the field required by checking if it contains a non-null and non-empty value, passing it to the `next` validator as a not-nullable type. +- `Validators.optional(next)`: Makes the field optional by passing it to the `next` validator if it contains a non-null and non-empty value. If the field is null or empty, null is returned. +- `Validators.validateWithDefault(defaultValue, next)`: Validates the field with `next` validator. If the input is null, it uses the `defaultValue` instead. + +#### Transform validators + +- `Validators.transformAndValidate(transformFunction, next:next)`: Transforms an input from `IN` type to `OUT` type through the function `transformFunction` and pass it to the `next` validator. ### Datetime validators -- `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.after(reference)`: Checks if the field contains a `DateTime` that is after `reference`. +- `Validators.before(reference)`: Checks if the field contains a `DateTime` that is before `reference`. +- `Validators.betweenDateTime(minReference, maxReference)`: Checks if the field contains a `DateTime` that is after `minReference` and before `maxReference`. ### File validators -- `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. +- `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 -- `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. +- `Validators.bic()`: Checks if the field contains a valid BIC (Bank Identifier Code). +- `Validators.iban()` - Checks if the field contains a valid IBAN (International Bank Account Number). + +### Generic Type Validators +Validators that check a generic type user input. +- `Validators.inList(values)`: Checks if the field contains a value that is in the list `values`. +- `Validators.notInList(values)`: Checks if the field DOES NOT contain a value that is in the list `values`. +- `Validators.isTrue()`: Checks if the field contains a boolean or a parsable `String` of the `true` value. +- `Validators.isFalse()`: Checks if the field contains a boolean or a parsable `String` of the `false` value. +- `Validators.satisfy(condition)`: Checks if the field satisfies the `condition`. + +### Miscellaneous validators + +- `Validators.colorCode()`: checks if the field contains a valid color. +- `Validators.isbn()`: checks if the field contains a valid ISBN 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. +- `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). +- `Validators.macAddress()`: Checks if the field is a valid MAC address. ### 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 inclusive range [min, max]. +- `Validators.greaterThan(reference)`: Checks if the field contains a number that is greater than `reference`. +- `Validators.greaterThanOrEqualTo(reference)`: Checks if the field contains a number that is greater than or equal to `reference`. +- `Validators.lessThan(reference)`: Checks if the field contains a number that is less than `reference`. +- `Validators.lessThanOrEqualTo(reference)`: Checks if the field contains a number that is less than or equal to `reference`. + +### Path Validators +- `Validators.matchesAllowedExtensions(extensions)`: Checks if the field contains a `String` that is in the list `extensions`. ### String validators -- `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. +- `Validators.contains(substring)` - Checks if the field contains the `substring`. +- TODO [ ] `Validators.notContains(substring)` - Checks if the field does not contain the `substring`. +- TODO [ ] `FormBuilderValidators.endsWith()` - requires the substring to be the end of the field's value. +- TODO [ ] `FormBuilderValidators.notEndsWith()` - requires the substring not to be the end of the field's value. +- TODO [ ] `FormBuilderValidators.startsWith()` - requires the substring to be the start of the field's value. +- TODO [ ] `FormBuilderValidators.notStartsWith()` - requires the substring not to be the start of the field's value. +- TODO [ ] `FormBuilderValidators.lowercase()` - requires the field's value to be lowercase. +- TODO [ ] `FormBuilderValidators.uppercase()` - requires the field's value to be uppercase. +- `Validators.hasMinUppercaseChars(min:min)` - Checks if the field has a minimum number of uppercase chars. +- TODO [ ] `Validators.hasMaxUppercaseChars(max:max)` - Checks if the field has a maximum number of uppercase chars. +- `Validators.hasMinLowercaseChars(min:min)` - Checks if the field has a minimum number of lowercase chars. +- TODO [ ] `Validators.hasMaxLowercaseChars(max:max)` - Checks if the field has a maximum number of lowercase chars. +- `Validators.hasMinNumericChars(min:min)` - Checks if the field has a minimum number of numeric chars. +- TODO [ ] `Validators.hasMaxNumericChars(max:max)` - Checks if the field has a maximum number of numeric chars. +- `Validators.hasMinSpecialChars(min:min)` - Checks if the field has a minimum number of special chars. +- TODO [ ] `Validators.hasMaxSpecialChars(max:max)` - Checks if the field has a maximum number of special chars. +- `Validators.match(regExp)` - Checks if the field matches with the regular expression `regExp`. +- TODO [ ] `Validators.notMatch(regExp)` - Checks if the field does not match with the regular expression `regExp`. +- `Validators.uuid()` - Checks if the field is a valid Universally Unique Identifier (UUID). +- TODO [ ] `FormBuilderValidators.maxWordsCount()` - requires the word count of the field's value to be less than or equal to the provided maximum count. +- TODO [ ] `FormBuilderValidators.minWordsCount()` - requires the word count of the field's value to be greater than or equal to the provided minimum count. + +### Type Validators +- `Validators.string(next)`: Checks if the field contains a valid `String` and passes the input as `String` to the `next` validator. +- `Validators.int(next)`: Checks if the field contains a valid `int` or parsable `String` to `int` and passes the input as `int` to the `next` validator. +- `Validators.double(next)`: Checks if the field contains a valid `double` or parsable `String` to `double` and passes the input as `double` to the `next` validator. +- `Validators.num(next)`: Checks if the field contains a valid `num` or parsable `String` to `num` and passes the input as `num` to the `next` validator. +- `Validators.bool(next)`: Checks if the field contains a valid `bool` or parsable `String` to `bool` and passes the input as `bool` to the `next` validator. +- `Validators.dateTime(next)`: Checks if the field contains a valid `DateTime` or parsable `String` to `DateTime` and passes the input as `DateTime` to the `next` validator. + +### User Information validators + +- `Validators.email()`: Checks if the field contains a valid email. +- `Validators.password()`: Checks if the field contains a valid password. A password may require some +conditions to be met in order to be considered as valid. +- `Validators.phoneNumber()`: Checks if the field contains a valid phone number. ## Supported languages @@ -256,8 +281,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: [ @@ -271,17 +297,18 @@ return MaterialApp( localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, - FormBuilderLocalizations.delegate, + FormBuilderLocalizations.delegate, // here ], ``` ### Basic use -```Dart +In the following example, we have a required field for the `Name` of the user: +```dart TextFormField( decoration: InputDecoration(labelText: 'Name'), autovalidateMode: AutovalidateMode.always, - validator: FormBuilderValidators.required(), + validator: Validators.required(), ), ``` @@ -289,38 +316,35 @@ See [pub.dev example tab](https://pub.dev/packages/form_builder_validators/examp ### Specific uses -#### Composing multiple validators +#### Composing multiple validators with the logical `AND` semantics -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. +The `Validators` class comes with a handy static function named `and()`, which takes a list of `Validator` functions. Composing allows you to create once and reuse validation rules across multiple fields, widgets, or apps. -On validation, each validator is run, and if any validator returns a non-null value (i.e., a String), validation fails, and the `errorText` for the field is set as the returned string. - -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'), 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'; + validator: Validators.required( + Validators.and(>[ + Validators.num(Validators.lessThan(70), (_) => 'La edad debe ser numérica.'), + + /// Include your own custom `Validator` function, if you want. + /// Ensures positive values only. We could also have used `Validators.greaterThanOrEqualTo(0)` instead. + (String? val) { + if (val != null) { + final int? number = int.tryParse(val); + // todo bug here: if it is not int, it accepts negative + // numbers + if (number == null) return null; + if (number < 0) return 'We cannot have a negative age'; + } return null; - }, - ]), + } + ])) ), ``` @@ -329,6 +353,10 @@ 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. +- Check the file [migrations](./doc/migration.md) for detailed instructions. ### v10 to v11 @@ -340,6 +368,62 @@ 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 of the most common `Validator`s and `FormFieldValidator`s such as required, numeric, mail, +URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit card, etc., with default `errorText` messages. + +- But what is a `FormFieldValidator`? + It is a function that takes user input of any nullable type and returns either null (for valid input) or + an error message string (for invalid input). The input parameter can be `String?`, `int?`, `num?`, or any + other nullable type that represents user-provided data. The input must be nullable since form fields may + be empty, resulting in null values. + +For example, here's a `FormFieldValidator` that checks if a number is even: +```dart +String? isEven(int? userInput) { + return (userInput != null && userInput % 2 == 0) ? null : 'This field must be even'; +} +``` +The challenge with the previous approach is that we must handle null checks in every validator +implementation. This leads to: + 1. Tangled validator logic: Each validator must handle both validation rules and null checking,making the code harder to understand and maintain. + 2. Code duplication: When composing validators, the same null-checking logic must be repeated across multiple validators, violating the DRY principle. + 3. Poor separation of concerns: A validator named `isEven` should focus solely on checking if a number is even. It shouldn't need to handle null cases or type validation - those are separate responsibilities that deserve their own focused validators. + 4. Verbose implementations: The combination of null checks, type validation, and business logic in each validator results in unnecessarily lengthy code that's harder to test and maintain. + 5. Potential problems with null safety: imagine an unsafe version of isEven that simply uses the `!` operator to throw an error during runtime if the user input is null: +```dart +/// `userInput` may not be null. +String? isEven(int? userInput) { + return (userInput! % 2 == 0) ? null : 'This field must be even'; +} +``` + +This package introduces a more precise approach that separates null-value handling from the actual +validation logic. Instead of the previous isEven implementation, we can compose two focused validators: + +```dart +String? Function(int) isEven(){ + return (int userInput){ + return (userInput % 2 == 0) ? null:'This field must be even'; + }; +} + +String? Function(int?) required(String? Function(int) next){ + return (int? userInput) { + return (userInput != null) ? next(userInput):'This field is required'; + }; +} + +// Important: isEven() does not return a FormFieldValidator, but the composition required(isEven()), does. +final validator = required(isEven()); +``` + +By introducing this level of indirection, we achieve: +1. Clean separation between null checks and validation logic +2. More composable validators +3. Specific error messages for missing vs invalid input +4. Type-safe validator chains + ## Support ### Contribute @@ -382,11 +466,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: @@ -420,35 +567,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/doc/migration.md b/doc/migration.md new file mode 100644 index 00000000..08eb281c --- /dev/null +++ b/doc/migration.md @@ -0,0 +1,1600 @@ +## Migrations +### v11 to v12 +The following items in 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 in 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`), they are 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.num( + Validators.between( + 1, + 100, + minInclusive: true, + maxInclusive: true, + betweenMsg: (_, __, ___, ____, _____) => 'Value must be between 1 and 100', + ), + (_)=>'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; + }; +} +// or +Validators.satisfy( + (input)=>values.where((element) => element == input).length <= 1, + satisfyMsg: (_)=>errorText +); +``` + +### 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.string( + Validators.match( + RegExp(r'^[a-zA-Z0-9_\-\.]+$'), + matchMsg: (_)=>'invalid file name', + ), + (_)=>'invalid file name', +); +``` + +- `FormBuilderValidators.fileSize()` +```dart +// base1024Conversion:true +// Old API: +FormBuilderValidators.fileSize( + 12 * 1024, + base1024Conversion:true, + errorText:'error text', +); + +// New API: +Validators.maxFileSize( + 12 * 1024, + base: Base.b1024, + maxFileSizeMsg: (_, __, ___)=>'error text', +); +//-------------------------------------------------------------------------- +// base1024Conversion:false +// Old API: +FormBuilderValidators.fileSize( + 12 * 1000, + base1024Conversion:false, + errorText:'error text', +); + +// New API: +Validators.maxFileSize( + 12 * 1000, + base: Base.b1000, + maxFileSizeMsg: (_, __, ___)=>'error text', +); +``` +- `FormBuilderValidators.mimeType()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/file/mime_type_validator.dart#L47). Something close would be: +```dart +// Old API: +FormBuilderValidators.mimeType( + // This regex is the same as the default + regex: RegExp(r'^[a-zA-Z0-9!#$&^_-]+\/[a-zA-Z0-9!#$&^_-]+$'), + errorText: 'error text', +); + +// New API: +Validators.string( + Validators.match( + RegExp(r'^[a-zA-Z0-9!#$&^_-]+\/[a-zA-Z0-9!#$&^_-]+$'), + matchMsg: (_)=>'error text' + ), + (_)=>'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.string( + Validators.match( + RegExp(r'^((\/|\\|[a-zA-Z]:\/)?([^<>:"|?*]+(\/|\\)?)+)$'), + matchMsg: (_)=>'error text' + ), + (_)=>'error text', +); +``` + +### Finance validators + +- `FormBuilderValidators.bic()` +```dart +// Old API (no regex): +FormBuilderValidators.bic( + errorText: 'error text', +); + +// New API: +Validators.bic( + bicMsg: (_)=>'error text' +); + +//------------------------------------------------------------------------------ +// Old API (with regex): +FormBuilderValidators.bic(regex: someRegex); + +// New API: +Validators.bic(isBic: (input)=>someRegex.hasMatch(input)); +``` +- `FormBuilderValidators.creditCardCVC()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/finance/credit_card_cvc_validator.dart#L29). Something close would be: +```dart +// Old API: +FormBuilderValidators.creditCardCVC( + errorText: 'invalid CVC number' +); + +// New API: +Validators.and([ + Validators.int(null, (_)=>'invalid CVC number'), + Validators.equalLength(3, equalLengthMsg: (_, __)=>'invalid CVC number'), +]); +``` +- `FormBuilderValidators.creditCardExpirationDate()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/finance/credit_card_expiration_date_validator.dart#L52). +- `FormBuilderValidators.creditCard()` +```dart +// Old API: +FormBuilderValidators.creditCard( + errorText: 'invalid credit card', +); + +// New API: +Validators.creditCard( + creditCardMsg: 'invalid credit card', +); + +``` + +- `FormBuilderValidators.iban()` +```dart +// Old API: +// OBS.: There is a bug in the regex parameter. It is not used at all. +FormBuilderValidators.iban(errorText: 'invalid iban'); + +// New API: +Validators.iban(ibanMsg: (_)=>'invalid iban'); +``` + +### Identity validators + +- `FormBuilderValidators.city()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/identity/city_validator.dart#L53). Something close would be: +```dart +// Old API: +FormBuilderValidators.city( + regex: RegExp(r'^[A-Z][a-zA-Z\s]+$'), + citiesWhitelist: ['CityA', 'CityB', 'CityC'], + citiesBlacklist: ['CityD', 'CityE'], + errorText: 'invalid city', +); + +// New API (expects input as String): +Validators.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', + ), + ]), + (_)=>'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.string( + Validators.and([ + Validators.inList( + ['CountryA', 'CountryB', 'CountryC'], + inListMsg: (_, __) => 'invalid country', + ), + Validators.notInList( + ['CountryD', 'CountryE'], + notInListMsg: (_, __) => 'invalid country', + ), + ]), + (_)=>'invalid country', + +); +``` + +- `FormBuilderValidators.firstName()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/identity/firstname_validator.dart#L53). Something close would be: +```dart +// Old API: +FormBuilderValidators.firstName( + regex: RegExp(r'^[A-Z][a-zA-Z]+$'), + firstNameWhitelist: ['NameA', 'NameB', 'NameC'], + firstNameBlacklist: ['NameD', 'NameE'], + errorText: 'invalid name', +); + +// New API (expects input as String): +Validators.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', + ), + ]), + (_)=>'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.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', + ), + ]), + (_)=>'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.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', + ), + ]), + (_)=>'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.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', + ), + ]), + (_)=>'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.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', + ), + ]), + (_)=>'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.string( + Validators.and([ + Validators.minLength(4, minLengthMsg: (_, __)=>'invalid username'), + Validators.maxLength(10, minLengthMsg: (_, __)=>'invalid username'), + Validators.hasMaxNumericChars(10, minLengthMsg: (_, __)=>'invalid username'), + + ]), + (_)=>'invalid username', +); + + +``` +- `FormBuilderValidators.zipCode()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/identity/zip_code_validator.dart#L43). Something close would be: +```dart +// Old API: +FormBuilderValidators.zipCode( + errorText: 'error text', +); + +// New API: +Validators.string( + Validators.match( + RegExp(r'^\d{5}(?:[-\s]\d{4})?$'), + matchMsg: (_)=>'error text' + ), + (_)=>'error text', +); +``` + + +### Network validators + +- `FormBuilderValidators.email()` +```dart +// Old API: +FormBuilderValidators.email( + regex: emailRegex, + errorText: 'invalid email', +); + +// New API: +Validators.email( + regex: emailRegex, + emailMsg: (_)=>'invalid email', +); +``` + +- `FormBuilderValidators.ip()` +```dart +// For IPv4 +// Old API: +FormBuilderValidators.ip( + regex: ipRegex, + errorText: 'invalid ipV4', +); + +// New API: +Validators.ip( + regex: ipRegex, + ipMsg: (_)=>'invalid ipV4', +); + +// For IPv6 +// Old API: +FormBuilderValidators.ip( + version: 6 + regex: ipRegex, + errorText: 'invalid ipV4', +); + +// New API: +Validators.ip( + version: IpVersion.iPv6, + regex: ipRegex, + emailMsg: (_)=>'invalid ipV4', +); +``` + +- `FormBuilderValidators.latitude()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/network/latitude_validator.dart#L40). But something close would be: +```dart +// Old API: +FormBuilderValidators.latitude(); + +// New API: +Validators.transformAndValidate( + (String input) => input.replaceAll(',', '.'), + next: Validators.double(Validators.between(-90, 90)), +); + +``` + +- `FormBuilderValidators.longitude()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/17e982bb849dc68365f8fbc93d5a2323ee891c89/lib/src/network/longitude_validator.dart#L40). But something close would be: +```dart +// Old API: +FormBuilderValidators.longitude(); + +// New API: +Validators.transformAndValidate( + (String input) => input.replaceAll(',', '.'), + next: Validators.double(Validators.between(-180, 180)), +); + +``` + +- `FormBuilderValidators.macAddress()` +```dart +// Old API +FormBuilderValidators.macAddress( + regex: regex, + errorText: 'error text' +); + +// New API equivalent +Validators.macAddress( + isMacAddress: (input) => regex.hasMatch(input), + macAddressMsg: (_) => 'error text' +); +``` + +- `FormBuilderValidators.phoneNumber()` +```dart +// Old API +FormBuilderValidators.phoneNumber(); + +// New API +Validators.phoneNumber(); +``` + +- `FormBuilderValidators.portNumber()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/eafb7662827fe938034be6d2081c9d2844a46c10/lib/src/network/port_number_validator.dart#L40). But, something close would be: +```dart +// Old API +FormBuilderValidators.portNumber( + min: min, + max: max, + errorText: 'error text' +); + + +// New API: +Validators.int( + Validators.between(min, max, betweenMsg: (_, __, ___, ____, _____)=>'error text'), + (_)=>'error text', +); +``` + +- `FormBuilderValidators.url()` +```dart +// Old API +FormBuilderValidators.url(); + +// New API equivalent +Validators.url(); +``` + + +### Numeric validators + +- `FormBuilderValidators.between()` +```dart +// Old API +FormBuilderValidators.between(min, max, errorText: 'error msg'); + +// New API equivalent +Validators.num( + Validators.between(min, max, betweenMsg:(_, __, ___, ____, _____)=>'error msg'), + (_)=>'error msg', +); +``` + +- `FormBuilderValidators.evenNumber()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/eafb7662827fe938034be6d2081c9d2844a46c10/lib/src/numeric/even_number_validator.dart#L29). But, something close would be: +```dart +// Old API +FormBuilderValidators.evenNumber(errorText: 'error text'); + + +// New API: +Validators.int( + Validators.satisfy((input)=>input % 2 == 0, satisfyMsg: (_)=>'error text'), + (_)=>'error text', +); +``` +- `FormBuilderValidators.integer()`: the radix is not supported. +```dart +// Old API +FormBuilderValidators.integer(errorText: 'error text'); + +// New API (close): +Validators.int(null,(_)=> 'error text'); +``` + +- `FormBuilderValidators.max()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/eafb7662827fe938034be6d2081c9d2844a46c10/lib/src/numeric/max_validator.dart#L40). But, something close would be: +```dart +// Old API (inclusive: true) +FormBuilderValidators.max(n, inclusive:true, errorText:'error msg'); + +// New API (close): +Validators.num( + Validators.lessThanOrEqualTo(n, lessThanOrEqualMsg: (_, __)=>'error msg'), + (_)=>'error msg', +); + +//------------------------------------------------------------------------------ + +// Old API (inclusive: false) +FormBuilderValidators.max(n, inclusive:false, errorText:'error msg'); + +// New API (close): +Validators.num( + Validators.lessThan(n, lessThanMsg: (_, __)=>'error msg'), + (_)=>'error msg', +); +``` + +- `FormBuilderValidators.min()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/eafb7662827fe938034be6d2081c9d2844a46c10/lib/src/numeric/min_validator.dart#L40). But, something close would be: +```dart +// Old API (inclusive: true) +FormBuilderValidators.min(n, inclusive:true, errorText:'error msg'); + +// New API (close): +Validators.num( + Validators.greaterThanOrEqualTo(n, greaterThanOrEqualMsg: (_, __)=>'error msg'), + (_)=>'error msg', +); + +//------------------------------------------------------------------------------ + +// Old API (inclusive: false) +FormBuilderValidators.min(n, inclusive:false, errorText:'error msg'); + +// New API (close): +Validators.num( + Validators.greaterThan(n, greaterThanMsg: (_, __)=>'error msg'), + (_)=>'error msg', +); +``` + +- `FormBuilderValidators.negativeNumber()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/eafb7662827fe938034be6d2081c9d2844a46c10/lib/src/numeric/negative_number_validator.dart#L29). But, something close would be: +```dart +// Old API: +FormBuilderValidators.negativeNumber(errorText:'error text'); + +// New API (close): +Validators.num( + Validators.lessThan(0, lessThanMsg:(_, __)=>'error text'), + (_)=>'error msg', +); +``` + + +- `FormBuilderValidators.notZeroNumber()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/eafb7662827fe938034be6d2081c9d2844a46c10/lib/src/numeric/not_zero_number_validator.dart#L29). But, something close would be: +```dart +// Old API: +FormBuilderValidators.notZeroNumber(errorText:'error text'); + +// New API (close): +Validators.num( + Validators.notEqual(0, notEqualMsg: (_, __)=>'error text'), + (_)=>'error text', +); +``` + +- `FormBuilderValidators.numeric()`: +```dart +// Old API +FormBuilderValidators.numeric(errorText: 'error text'); + +// New API: +Validators.num(null, (_)=>'error text'); +``` + +- `FormBuilderValidators.oddNumber()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/eafb7662827fe938034be6d2081c9d2844a46c10/lib/src/numeric/odd_number_validator.dart#L29). But, something close would be: +```dart +// Old API +FormBuilderValidators.oddNumber(errorText: 'error text'); + + +// New API: +Validators.int( + Validators.satisfy((input)=>input % 2 == 1, satisfyMsg: (_)=>'error text'), + (_)=>'error text', +); + +``` +- `FormBuilderValidators.positiveNumber()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/eafb7662827fe938034be6d2081c9d2844a46c10/lib/src/numeric/positive_number_validator.dart#L29). But, something close would be: +```dart +// Old API: +FormBuilderValidators.positiveNumber(errorText:'error text'); + +// New API (close): +Validators.num( + Validators.greaterThan(0, greaterThanMsg:(_, __)=>'error text'), + (_)=>'error msg', +); +``` +- `FormBuilderValidators.primeNumber()`: there is no equivalent to [this validator](). But, something close would be: +```dart +// Old API +FormBuilderValidators.primeNumber(errorText: 'error text'); + + +// New API: +bool isPrime(int number) { + if (number <= 1) return false; + for (int i = 2; i * i <= number; i++) { + if (number % i == 0) return false; + } + return true; +} + +Validators.int( + Validators.satisfy((input)=>isPrime(input), satisfyMsg: (_)=>'error text'), + (_)=>'error text', +); +``` + +### String 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 (e.g. it may receive an `Object`), you must wrap the equivalent validator with the type validator for strings (`Validators.string`). + +- `FormBuilderValidators.alphabetical()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/eafb7662827fe938034be6d2081c9d2844a46c10/lib/src/string/alphabetical_validator.dart#L45). But, something close would be: +```dart +// Old API: +FormBuilderValidators.alphabetical(errorText:'error text'); + +// New API (close): +Validators.match( + RegExp(r'^[a-zA-Z]+$'), + matchMsg: (_)=>'error text' +); +``` + +- `FormBuilderValidators.contains()` +```dart +// Old API: +FormBuilderValidator.contains( + 'substring', + caseSensitive: false, + errorText:'error text', +); + +// New API: +Validators.contains( + 'substring', + caseSensitive:false, + containsMsg: (_, __)=>'error text', +); +``` +- `FormBuilderValidators.endsWith()` +```dart +// Old API: +FormBuilderValidator.endsWith( + 'suffix', + errorText:'error text', +); + +// New API: +Validators.endsWith( + 'suffix', + endsWithMsg: (_, __)=>'error text', +); +``` + +- `FormBuilderValidators.lowercase()` +```dart +// Old API: +FormBuilderValidator.lowercase( + errorText:'error text', +); + +// New API: +Validators.lowercase( + lowercaseMsg: (_)=>'error text', +); +``` + +- `FormBuilderValidators.matchNot()` +```dart +// Old API: +FormBuilderValidator.matchNot( + regex, + errorText:'error text', +); + +// New API: +Validators.notMatch( + regex, + notMatchMsg: (_)=>'error text', +); +``` + + +- `FormBuilderValidators.match()` +```dart +// Old API: +FormBuilderValidator.match( + regex, + errorText:'error text', +); + +// New API: +Validators.match( + regex, + matchMsg: (_)=>'error text', +); +``` + +- `FormBuilderValidators.maxWordsCount()` +```dart +// Old API: +FormBuilderValidator.maxWordsCount( + 10, + errorText:'error text', +); + +// New API: +Validators.maxWordCount( + 10, + maxWordCountMsg: (_, __)=>'error text', +); +``` + +- `FormBuilderValidators.minWordsCount()` +```dart +// Old API: +FormBuilderValidator.minWordsCount( + 10, + errorText:'error text', +); + +// New API: +Validators.minWordCount( + 10, + minWordCountMsg: (_, __)=>'error text', +); +``` + +- `FormBuilderValidators.singleLine()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/eafb7662827fe938034be6d2081c9d2844a46c10/lib/src/string/single_line_validator.dart#L29). But, something close would be: +```dart +// Old API: +FormBuilderValidators.singleLine(errorText:'error text'); + +// New API: +Validators.satisfy( + (input)=> !input.contains('\n') && !input.contains('\r'), + satisfyMsg: (_)=> 'error text', +); +``` + + +- `FormBuilderValidators.startsWith()` +```dart +// Old API: +FormBuilderValidator.startsWith( + 'suffix', + errorText:'error text', +); + +// New API: +Validators.startsWith( + 'suffix', + startsWithMsg: (_, __)=>'error text', +); +``` + +- `FormBuilderValidators.uppercase()` +```dart +// Old API: +FormBuilderValidator.uppercase( + errorText:'error text', +); + +// New API: +Validators.uppercase( + uppercaseMsg: (_)=>'error text', +); +``` + + +### Use-case validators + +- `FormBuilderValidators.base64()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/eafb7662827fe938034be6d2081c9d2844a46c10/lib/src/usecase/base64_validator.dart#L31). Something close would be: +```dart +// Old API: +FormBuilderValidator.base64( + errorText: 'error text', +); + +// New API (equivalent): +bool isBase64(String value) { + try { + base64Decode(value); + return true; + } catch (e) { + return false; + } +} + +Validators.satisfy( + (input)=>isBase64(input), satisfyMsg: (_)=>'error text', +); +``` +- `FormBuilderValidators.colorCode()` +```dart +// Old API: +FormBuilderValidator.colorCode(); + +// New API: +Validators.colorCode(); +``` + +- `FormBuilderValidators.duns()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/eafb7662827fe938034be6d2081c9d2844a46c10/lib/src/usecase/duns_validator.dart#L41). The equivalent would be: +```dart +// Old API: +FormBuilderValidator.duns( + errorText: 'error text', +); + +// New API: +Validators.match( + RegExp(r'^\d{9}$'), + matchMsg: (_)=>'error text', +); +``` + +- `FormBuilderValidators.isbn()` +```dart +// Old API: +FormBuilderValidator.isbn( + errorText: 'error text', +); + +// New API: +Validators.isbn( + isbnMsg: (_)=> 'error text', +); +``` +- `FormBuilderValidators.json()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/eafb7662827fe938034be6d2081c9d2844a46c10/lib/src/usecase/json_validator.dart#L31) +```dart +// Old API: +FormBuilderValidator.json( + errorText: 'error text', +); + +// New API (equivalent): +bool isJson(String value) { + try { + jsonDecode(value); + return true; + } catch (e) { + return false; + } +} + +Validators.satisfy( + (input)=>isJson(input), satisfyMsg: (_)=>'error text', +); +``` +- `FormBuilderValidators.languageCode()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/eafb7662827fe938034be6d2081c9d2844a46c10/lib/src/usecase/language_code_validator.dart#L51). +```dart +// Old API: +FormBuilderValidators.languageCode( + regex: regex, + languageCodeWhitelist: ['val1', 'val2', 'val3'], + languageCodeBlacklist: ['val4', 'val5'], + errorText: 'invalid language code', +); + +// New API (expects input as String): +Validators.and([ + Validators.match( + regex, + matchMsg: (_)=>'invalid language code' + ), + Validators.inList( + ['val1', 'val2', 'val3'], + inListMsg: (_, __) => 'invalid language code', + ), + Validators.notInList( + ['val1', 'val2', 'val3'], + notInListMsg: (_, __) => 'invalid language code', + ), +]); +``` + +- `FormBuilderValidators.licensePlate()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/eafb7662827fe938034be6d2081c9d2844a46c10/lib/src/usecase/licenseplate_validator.dart#L52). +```dart +// Old API: +FormBuilderValidators.licensePlate( + regex: regex, + licensePlateWhitelist: ['val1', 'val2', 'val3'], + licensePlateBlacklist: ['val4', 'val5'], + errorText: 'invalid license plate', +); + +// New API (expects input as String): +Validators.and([ + Validators.match( + regex, + matchMsg: (_)=>'invalid license plate' + ), + Validators.inList( + ['val1', 'val2', 'val3'], + inListMsg: (_, __) => 'invalid license plate', + ), + Validators.notInList( + ['val1', 'val2', 'val3'], + notInListMsg: (_, __) => 'invalid license plate', + ), +]); +``` +- `FormBuilderValidators.uuid()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/eafb7662827fe938034be6d2081c9d2844a46c10/lib/src/usecase/uuid_validator.dart#L47). +```dart +// Old API: +FormBuilderValidator.uuid( + errorText: 'error text', +); + +// New API: +Validators.match( + RegExp( + r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$', + ), + matchMsg: (_)=>'error text', +); +``` + +- `FormBuilderValidators.vin()`: there is no equivalent to [this validator](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/blob/eafb7662827fe938034be6d2081c9d2844a46c10/lib/src/usecase/vin_validator.dart#L52). +```dart +// Old API: +FormBuilderValidators.vin( + regex: regex, + licensePlateWhitelist: ['val1', 'val2', 'val3'], + licensePlateBlacklist: ['val4', 'val5'], + errorText: 'invalid vin', +); + +// New API (expects input as String): +Validators.and([ + Validators.match( + regex, + matchMsg: (_)=>'invalid vin' + ), + Validators.inList( + ['val1', 'val2', 'val3'], + inListMsg: (_, __) => 'invalid vin', + ), + Validators.notInList( + ['val1', 'val2', 'val3'], + notInListMsg: (_, __) => 'invalid vin', + ), +]); +``` + + + +### Extension method validators + +Used for chaining and combining multiple validators. + +- `FormBuilderValidator.and()`: use `Validators.and` instead. +- `FormBuilderValidator.or()`: use `Validators.or` instead. +- `FormBuilderValidator.when()`: use `Validators.validateIf` instead. +- `FormBuilderValidator.unless()`: use `Validators.skipIf` +- `FormBuilderValidator.transform()`: use `Validators.transformAndValidate` instead. +- `FormBuilderValidator.skipWhen()`: use `Validators.skipIf` instead. +- `FormBuilderValidator.log()`: use `Validators.debugPrintValidator` instead +- `FormBuilderValidator.withErrorMessage()`: there is no equivalent in the new API. diff --git a/example/lib/basic_examples.dart b/example/lib/basic_examples.dart index f746a3b0..c9277308 100644 --- a/example/lib/basic_examples.dart +++ b/example/lib/basic_examples.dart @@ -34,13 +34,13 @@ class BasicExamplesPage extends StatelessWidget { labelText: 'Type "I want to delete" to confirm the action.'), autovalidateMode: AutovalidateMode.onUserInteraction, - validator: Validators.isEqual('I want to delete'), + validator: Validators.equal('I want to delete'), ), TextFormField( decoration: const InputDecoration( labelText: 'Username (should not be "RESERVED")'), autovalidateMode: AutovalidateMode.onUserInteraction, - validator: Validators.isNotEqual('RESERVED'), + validator: Validators.notEqual('RESERVED'), ), Align( alignment: Alignment.centerLeft, @@ -54,7 +54,7 @@ class BasicExamplesPage extends StatelessWidget { const InputDecoration(labelText: 'Input must not be null'), autovalidateMode: AutovalidateMode.onUserInteraction, validator: (String? input) { - final String? isRequiredMsg = Validators.isRequired()(input); + final String? isRequiredMsg = Validators.required()(input); return isRequiredMsg ?.toUpperCase() .replaceFirst('OVERRIDE: ', ''); diff --git a/example/lib/forms_with_validate_granularlly.dart b/example/lib/forms_with_validate_granularlly.dart index dad155fe..4848ac89 100644 --- a/example/lib/forms_with_validate_granularlly.dart +++ b/example/lib/forms_with_validate_granularlly.dart @@ -75,7 +75,7 @@ class _BodyState extends State<_Body> { hintText: 'Enter your full name', prefixIcon: Icon(Icons.person), ), - validator: V.isRequired(V.match(RegExp('[A-Z].*'), + validator: V.required(V.match(RegExp('[A-Z].*'), matchMsg: (_) => 'The name must start with uppercase')), textInputAction: TextInputAction.next, ), @@ -87,7 +87,7 @@ class _BodyState extends State<_Body> { hintText: 'Enter your email', prefixIcon: Icon(Icons.email), ), - validator: V.isRequired(V.email()), + validator: V.required(V.email()), keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.next, ), @@ -100,9 +100,8 @@ class _BodyState extends State<_Body> { hintText: 'YYYY-MM-DD', prefixIcon: Icon(Icons.calendar_today), ), - validator: V.isRequired(V.isDateTime(V.isBefore( - DateTime.now(), - isBeforeMsg: (_, __) => 'Date must be in the past.'))), + validator: V.required(V.dateTime(V.before(DateTime.now(), + beforeMsg: (_, __) => 'Date must be in the past.'))), keyboardType: TextInputType.datetime, textInputAction: TextInputAction.next, onTap: () async { @@ -127,7 +126,7 @@ class _BodyState extends State<_Body> { prefixIcon: Icon(Icons.height), suffixText: 'm', ), - validator: V.isRequired(V.isNum(V.between(0.5, 2.5, + validator: V.required(V.num(V.between(0.5, 2.5, betweenMsg: (_, num min, num max, __, ___) => 'Please enter a realistic height [$min-${max}m]'))), keyboardType: TextInputType.number, @@ -142,7 +141,7 @@ class _BodyState extends State<_Body> { prefixIcon: Icon(Icons.monitor_weight), suffixText: 'kg', ), - validator: V.isOptional(V.isNum(V.between(20, 300, + validator: V.optional(V.num(V.between(20, 300, betweenMsg: (_, num min, num max, ____, _____) => 'weight must be in [$min, ${max}kg]'))), keyboardType: TextInputType.number, @@ -156,7 +155,7 @@ class _BodyState extends State<_Body> { hintText: 'Enter your phone number', prefixIcon: Icon(Icons.phone), ), - validator: V.isRequired(), + validator: V.required(), keyboardType: TextInputType.phone, textInputAction: TextInputAction.next, ), @@ -181,9 +180,8 @@ class _BodyState extends State<_Body> { child: Text('Invalid option 2'), ), ]).toList(), - validator: V.isRequired(V.containsElement( - validBloodTypeOptions, - containsElementMsg: (_, List v) => + validator: V.required(V.inList(validBloodTypeOptions, + inListMsg: (_, List v) => 'The option must be one of: ${v.join(', ')}.')), onChanged: (String? value) { setState(() { diff --git a/example/lib/generic_examples.dart b/example/lib/generic_examples.dart index f5e937fb..e85719cb 100644 --- a/example/lib/generic_examples.dart +++ b/example/lib/generic_examples.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:form_builder_validators/form_builder_validators.dart' - show Validators, Validator; + show Validator, Validators; /// alias for Validators class. typedef V = Validators; @@ -24,12 +24,8 @@ class GenericExamplesPage extends StatelessWidget { decoration: const InputDecoration(labelText: 'Age'), keyboardType: TextInputType.number, autovalidateMode: AutovalidateMode.always, - validator: V.isRequired( - V.and(>[ - V.isNum( - V.lessThan(70), - (_) => 'La edad debe ser numérica.', - ), + validator: V.required(V.and(>[ + V.num(V.lessThan(70), (_) => 'La edad debe ser numérica.'), /// Include your own custom `FormFieldValidator` function, if you want /// Ensures positive values only. We could also have used `FormBuilderValidators.min( 0)` instead @@ -52,7 +48,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, @@ -64,12 +60,11 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.numbers), ), keyboardType: TextInputType.number, - validator: V.isRequired(V.isNum()), + validator: V.required(V.num()), autofillHints: const [AutofillHints.oneTimeCode], textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), - /* TODO implement the email and url validator // Email Validator TextFormField( decoration: const InputDecoration( @@ -77,7 +72,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, @@ -89,19 +84,18 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.link), ), keyboardType: TextInputType.url, - validator: v.url(), + validator: V.required(V.url()), autofillHints: const [AutofillHints.url], textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), - */ // Min Length Validator TextFormField( decoration: const InputDecoration( labelText: 'Min Length Field', prefixIcon: Icon(Icons.text_fields), ), - validator: V.isRequired(V.minLength(5)), + validator: V.required(V.minLength(5)), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), @@ -111,7 +105,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, ), @@ -122,7 +116,7 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.exposure_neg_1), ), keyboardType: TextInputType.number, - validator: V.isRequired(V.isNum(V.greaterThan(10))), + validator: V.required(V.num(V.greaterThan(10))), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), @@ -133,7 +127,7 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.exposure_plus_1), ), keyboardType: TextInputType.number, - validator: V.isRequired(V.isNum(V.lessThan(100))), + validator: V.required(V.num(V.lessThan(100))), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), @@ -143,34 +137,31 @@ class GenericExamplesPage extends StatelessWidget { labelText: 'Equal Field', prefixIcon: Icon(Icons.check), ), - validator: V.isEqual('test'), + validator: V.equal('test'), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), - /* TODO implement contains substring // Contains Validator TextFormField( decoration: const InputDecoration( labelText: 'Contains "test"', prefixIcon: Icon(Icons.search), ), - validator: FormBuilderValidators.contains('test'), + validator: V.required(V.contains('test')), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), - */ // Match Validator TextFormField( decoration: const InputDecoration( labelText: 'Match Pattern', prefixIcon: Icon(Icons.pattern), ), - validator: V.isRequired(V.match(RegExp(r'^[a-zA-Z0-9]+$'))), + validator: V.required(V.match(RegExp(r'^[a-zA-Z0-9]+$'))), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), - /* TODO implement id, uuid, credit cart, and phone number validators // IP Validator TextFormField( decoration: const InputDecoration( @@ -178,7 +169,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, ), @@ -189,7 +180,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, ), @@ -200,11 +191,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( @@ -225,7 +217,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, @@ -237,9 +229,70 @@ class GenericExamplesPage extends StatelessWidget { prefixIcon: Icon(Icons.calendar_today), ), keyboardType: TextInputType.number, - validator: V.isRequired( - V.isNum(V.and(>[V.between(0, 120)])), + validator: V.required( + V.num(V.and(>[V.between(0, 120)]))), + textInputAction: TextInputAction.done, + autovalidateMode: AutovalidateMode.always, + ), + TextFormField( + decoration: const InputDecoration( + labelText: 'CVC number validator', + prefixIcon: Icon(Icons.numbers), + ), + keyboardType: TextInputType.number, + validator: V.required( + V.and(>[ + V.int(), + V.equalLength(3), + ]), + ), + textInputAction: TextInputAction.done, + autovalidateMode: AutovalidateMode.always, + ), + TextFormField( + decoration: const InputDecoration( + labelText: 'City', + prefixIcon: Icon(Icons.location_city), + ), + validator: V.required( + V.and(>[ + V.match( + RegExp(r'^[A-Z][a-zA-Z\s]+$'), + matchMsg: (_) => 'invalid city', + ), + V.inList( + ['CityA', 'CityB', 'CityC'], + inListMsg: (_, __) => 'invalid city', + ), + V.notInList( + ['CityD', 'CityE'], + notInListMsg: (_, __) => 'invalid city', + ), + ]), + 'invalid city', + ), + textInputAction: TextInputAction.done, + autovalidateMode: AutovalidateMode.always, + ), + TextFormField( + decoration: const InputDecoration( + labelText: 'Latitude', + prefixIcon: Icon(Icons.add_location), + ), + validator: V.required(V.transformAndValidate( + (String input) => input.replaceAll(',', '.'), + next: V.double(V.between(-90, 90)))), + textInputAction: TextInputAction.done, + autovalidateMode: AutovalidateMode.always, + ), + TextFormField( + decoration: const InputDecoration( + labelText: 'Longitude', + prefixIcon: Icon(Icons.add_location), ), + validator: V.required(V.transformAndValidate( + (String input) => input.replaceAll(',', '.'), + next: V.double(V.between(-180, 180)))), textInputAction: TextInputAction.done, autovalidateMode: AutovalidateMode.always, ), diff --git a/lib/l10n/intl_ar.arb b/lib/l10n/intl_ar.arb index 01462f20..f699e870 100644 --- a/lib/l10n/intl_ar.arb +++ b/lib/l10n/intl_ar.arb @@ -1,5 +1,6 @@ { "@@locale": "ar", + "satisfyErrorText": "القيمة لا تحقق الشرط.", "andSeparator": " و ", "betweenLengthErrorText": "يجب أن يكون طول القيمة بين {min} و {max}، شاملة.", "@betweenLengthErrorText": { @@ -62,6 +63,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..bae5fbfb 100644 --- a/lib/l10n/intl_bg.arb +++ b/lib/l10n/intl_bg.arb @@ -1,5 +1,6 @@ { "@@locale": "bg", + "satisfyErrorText": "Стойността не отговаря на условието.", "andSeparator": " и ", "betweenLengthErrorText": "Стойността трябва да има дължина между {min} и {max}, включително.", "@betweenLengthErrorText": { @@ -64,6 +65,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..49994940 100644 --- a/lib/l10n/intl_bn.arb +++ b/lib/l10n/intl_bn.arb @@ -1,5 +1,6 @@ { "@@locale": "bn", + "satisfyErrorText": "মান শর্ত পূরণ করে না।", "andSeparator": " এবং ", "betweenLengthErrorText": "মানটির দৈর্ঘ্য {min} এবং {max} এর মধ্যে হতে হবে, সহ.", "@betweenLengthErrorText": { @@ -64,6 +65,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..92e4da68 100644 --- a/lib/l10n/intl_bs.arb +++ b/lib/l10n/intl_bs.arb @@ -1,5 +1,6 @@ { "@@locale": "bs", + "satisfyErrorText": "Vrednost ne zadovoljava uslov.", "andSeparator": " i ", "betweenLengthErrorText": "Vrijednost mora imati dužinu između {min} i {max}, uključivo.", "@betweenLengthErrorText": { @@ -64,6 +65,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..2d0f4431 100644 --- a/lib/l10n/intl_ca.arb +++ b/lib/l10n/intl_ca.arb @@ -1,5 +1,6 @@ { "@@locale": "ca", + "satisfyErrorText": "El valor no compleix la condició.", "andSeparator": " i ", "betweenLengthErrorText": "El valor ha de tenir una longitud entre {min} i {max}, inclosos.", "@betweenLengthErrorText": { @@ -64,6 +65,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..a75010a4 100644 --- a/lib/l10n/intl_cs.arb +++ b/lib/l10n/intl_cs.arb @@ -1,5 +1,6 @@ { "@@locale": "cs", + "satisfyErrorText": "Hodnota nesplňuje podmínku.", "andSeparator": " a ", "betweenLengthErrorText": "Hodnota musí mít délku mezi {min} a {max} včetně.", "@betweenLengthErrorText": { @@ -64,6 +65,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..c93a433e 100644 --- a/lib/l10n/intl_da.arb +++ b/lib/l10n/intl_da.arb @@ -1,5 +1,6 @@ { "@@locale": "da", + "satisfyErrorText": "Værdien opfylder ikke betingelsen.", "andSeparator": " og ", "betweenLengthErrorText": "Værdien skal have en længde mellem {min} og {max}, inklusive.", "@betweenLengthErrorText": { @@ -64,6 +65,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..bb90bcc9 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -1,5 +1,6 @@ { "@@locale": "de", + "satisfyErrorText": "Der Wert erfüllt die Bedingung nicht.", "andSeparator": " und ", "betweenLengthErrorText": "Der Wert muss eine Länge zwischen {min} und {max} haben, einschließlich.", "@betweenLengthErrorText": { @@ -64,6 +65,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..d47ab9c7 100644 --- a/lib/l10n/intl_el.arb +++ b/lib/l10n/intl_el.arb @@ -1,5 +1,6 @@ { "@@locale": "el", + "satisfyErrorText": "Η αξία δεν ικανοποιεί τη συνθήκη.", "andSeparator": " και ", "betweenLengthErrorText": "Η τιμή πρέπει να έχει μήκος μεταξύ {min} και {max}, συμπεριλαμβανομένων.", "@betweenLengthErrorText": { @@ -64,6 +65,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..da1a5409 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -339,6 +339,7 @@ } }, "macAddressErrorText": "Value must be a valid MAC address.", + "satisfyErrorText": "Value does not satisfy the condition.", "startsWithErrorText": "Value must start with {value}.", "@startsWithErrorText": { "placeholders": { @@ -380,6 +381,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..b7743880 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -1,5 +1,6 @@ { "@@locale": "es", + "satisfyErrorText": "El valor no cumple la condición.", "andSeparator": " y ", "betweenLengthErrorText": "El valor debe tener una longitud entre {min} y {max}, inclusive.", "@betweenLengthErrorText": { @@ -64,6 +65,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..ec4ca29f 100644 --- a/lib/l10n/intl_et.arb +++ b/lib/l10n/intl_et.arb @@ -1,5 +1,6 @@ { "@@locale": "et", + "satisfyErrorText": "Väärtus ei vasta tingimustele.", "andSeparator": " ja ", "betweenLengthErrorText": "Väärtuse pikkus peab olema vahemikus {min} kuni {max}, kaasa arvatud.", "@betweenLengthErrorText": { @@ -64,6 +65,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..b46d4d78 100644 --- a/lib/l10n/intl_fa.arb +++ b/lib/l10n/intl_fa.arb @@ -1,5 +1,6 @@ { "@@locale": "fa", + "satisfyErrorText": "مقدار شرط را برآورده نمی‌کند.", "andSeparator": " و ", "betweenLengthErrorText": "مقدار باید طولی بین {min} و {max}، به صورت شامل داشته باشد.", "@betweenLengthErrorText": { @@ -64,6 +65,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..81be3f75 100644 --- a/lib/l10n/intl_fi.arb +++ b/lib/l10n/intl_fi.arb @@ -1,5 +1,6 @@ { "@@locale": "fi", + "satisfyErrorText": "Arvo ei täytä ehtoa.", "andSeparator": " ja ", "betweenLengthErrorText": "Arvon pituuden on oltava välillä {min} ja {max}, mukaan lukien.", "@betweenLengthErrorText": { @@ -64,6 +65,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..40673935 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -1,5 +1,6 @@ { "@@locale": "fr", + "satisfyErrorText": "La valeur ne satisfait pas la condition.", "andSeparator": " et ", "betweenLengthErrorText": "La valeur doit avoir une longueur comprise entre {min} et {max}, inclus.", "@betweenLengthErrorText": { @@ -64,6 +65,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..dd7463f2 100644 --- a/lib/l10n/intl_he.arb +++ b/lib/l10n/intl_he.arb @@ -1,5 +1,6 @@ { "@@locale": "he", + "satisfyErrorText": "הערך אינו עומד בתנאי.", "andSeparator": " ו ", "betweenLengthErrorText": "הערך חייב להיות באורך שבין {min} ל-{max}, כולל.", "@betweenLengthErrorText": { @@ -64,6 +65,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..6f005cb9 100644 --- a/lib/l10n/intl_hi.arb +++ b/lib/l10n/intl_hi.arb @@ -1,5 +1,6 @@ { "@@locale": "hi", + "satisfyErrorText": "मान शर्त को पूरा नहीं करता।", "andSeparator": " और ", "betweenLengthErrorText": "मान की लंबाई {min} और {max} के बीच होनी चाहिए, सहित.", "@betweenLengthErrorText": { @@ -64,6 +65,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..7e8fcd11 100644 --- a/lib/l10n/intl_hr.arb +++ b/lib/l10n/intl_hr.arb @@ -1,5 +1,6 @@ { "@@locale": "hr", + "satisfyErrorText": "Vrijednost ne zadovoljava uvjet.", "andSeparator": " i ", "betweenLengthErrorText": "Vrijednost mora imati duljinu između {min} i {max}, uključivo.", "@betweenLengthErrorText": { @@ -64,6 +65,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..49d925b6 100644 --- a/lib/l10n/intl_hu.arb +++ b/lib/l10n/intl_hu.arb @@ -1,5 +1,6 @@ { "@@locale": "hu", + "satisfyErrorText": "Az érték nem teljesíti a feltételt.", "andSeparator": " és ", "betweenLengthErrorText": "Az értéknek {min} és {max} közötti hosszúságúnak kell lennie, beleértve.", "@betweenLengthErrorText": { @@ -64,6 +65,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..96769241 100644 --- a/lib/l10n/intl_id.arb +++ b/lib/l10n/intl_id.arb @@ -1,5 +1,6 @@ { "@@locale": "id", + "satisfyErrorText": "Nilai tidak memenuhi kondisi.", "andSeparator": " dan ", "betweenLengthErrorText": "Nilai harus memiliki panjang antara {min} dan {max}, inklusif.", "@betweenLengthErrorText": { @@ -64,6 +65,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..01885c5d 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -1,5 +1,6 @@ { "@@locale": "it", + "satisfyErrorText": "Il valore non soddisfa la condizione.", "andSeparator": " e ", "betweenLengthErrorText": "Il valore deve avere una lunghezza compresa tra {min} e {max}, inclusi.", "@betweenLengthErrorText": { @@ -64,6 +65,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..390846c3 100644 --- a/lib/l10n/intl_ja.arb +++ b/lib/l10n/intl_ja.arb @@ -1,5 +1,6 @@ { "@@locale": "ja", + "satisfyErrorText": "値が条件を満たしていません。", "andSeparator": " と ", "betweenLengthErrorText": "値の長さは{min}から{max}の間(両端を含む)である必要があります。", "@betweenLengthErrorText": { @@ -64,6 +65,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..b1a34340 100644 --- a/lib/l10n/intl_km.arb +++ b/lib/l10n/intl_km.arb @@ -1,5 +1,6 @@ { "@@locale": "km", + "satisfyErrorText": "តម្លៃមិនបំពេញលក្ខខណ្ឌ។", "andSeparator": " និង ", "betweenLengthErrorText": "តម្លៃត្រូវតែមានប្រវែងរវាង {min} និង {max} រួមបញ្ចូលទាំងពីរ។", "@betweenLengthErrorText": { @@ -64,6 +65,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..d12c4de4 100644 --- a/lib/l10n/intl_ko.arb +++ b/lib/l10n/intl_ko.arb @@ -1,5 +1,6 @@ { "@@locale": "ko", + "satisfyErrorText": "값이 조건을 만족하지 않습니다.", "andSeparator": " 그리고 ", "betweenLengthErrorText": "값의 길이는 {min}에서 {max} 사이여야 합니다(포함).", "@betweenLengthErrorText": { @@ -64,6 +65,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..85fe9f2e 100644 --- a/lib/l10n/intl_ku.arb +++ b/lib/l10n/intl_ku.arb @@ -1,5 +1,6 @@ { "@@locale": "ku", + "satisfyErrorText": "Nirx şert bicîh nake.", "andSeparator": " û ", "betweenLengthErrorText": "Nirx divê dirêjahiya wê di navbera {min} û {max} de be, tevlî wan.", "@betweenLengthErrorText": { @@ -64,6 +65,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..fd5dcf8b 100644 --- a/lib/l10n/intl_lo.arb +++ b/lib/l10n/intl_lo.arb @@ -1,5 +1,6 @@ { "@@locale": "lo", + "satisfyErrorText": "ຄ່າບໍ່ຕອບສະໜອງເງື່ອນໄຂ.", "andSeparator": " ແລະ ", "betweenLengthErrorText": "ຄ່າຕ້ອງມີຄວາມຍາວລະຫວ່າງ {min} ແລະ {max}, ລວມທັງສອງຄ່າ.", "@betweenLengthErrorText": { @@ -64,6 +65,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..ca58c35b 100644 --- a/lib/l10n/intl_lv.arb +++ b/lib/l10n/intl_lv.arb @@ -1,5 +1,6 @@ { "@@locale": "lv", + "satisfyErrorText": "Vērtība neatbilst nosacījumam.", "andSeparator": " un ", "betweenLengthErrorText": "Vērtības garumam jābūt starp {min} un {max}, ieskaitot.", "@betweenLengthErrorText": { @@ -64,6 +65,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..8ad88403 100644 --- a/lib/l10n/intl_mn.arb +++ b/lib/l10n/intl_mn.arb @@ -1,5 +1,6 @@ { "@@locale": "mn", + "satisfyErrorText": "Утга нөхцлийг хангахгүй байна.", "andSeparator": " ба ", "betweenLengthErrorText": "Утгын урт нь {min}-с {max} хүртэл байх ёстой, оруулан тооцно.", "@betweenLengthErrorText": { @@ -64,6 +65,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..5349431a 100644 --- a/lib/l10n/intl_ms.arb +++ b/lib/l10n/intl_ms.arb @@ -1,5 +1,6 @@ { "@@locale": "ms", + "satisfyErrorText": "Nilai tidak memenuhi syarat.", "andSeparator": " dan ", "betweenLengthErrorText": "Nilai mesti mempunyai panjang antara {min} dan {max}, termasuk.", "@betweenLengthErrorText": { @@ -64,6 +65,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..bf8ab522 100644 --- a/lib/l10n/intl_ne.arb +++ b/lib/l10n/intl_ne.arb @@ -1,5 +1,6 @@ { "@@locale": "ne", + "satisfyErrorText": "मूल्यले सर्त पूरा गर्दैन।", "andSeparator": " र ", "betweenLengthErrorText": "मान को लम्बाई {min} र {max} बीचमा हुनुपर्छ, समावेश गरेर।", "@betweenLengthErrorText": { @@ -64,6 +65,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..c52ebb94 100644 --- a/lib/l10n/intl_nl.arb +++ b/lib/l10n/intl_nl.arb @@ -1,5 +1,6 @@ { "@@locale": "nl", + "satisfyErrorText": "Waarde voldoet niet aan de voorwaarde.", "andSeparator": " en ", "betweenLengthErrorText": "De waarde moet een lengte hebben tussen {min} en {max}, inclusief.", "@betweenLengthErrorText": { @@ -64,6 +65,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..d0a36735 100644 --- a/lib/l10n/intl_no.arb +++ b/lib/l10n/intl_no.arb @@ -1,5 +1,6 @@ { "@@locale": "no", + "satisfyErrorText": "Verdi oppfyller ikke betingelsen.", "andSeparator": " og ", "betweenLengthErrorText": "Verdien må ha en lengde mellom {min} og {max}, inkludert.", "@betweenLengthErrorText": { @@ -64,6 +65,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..894b8f53 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -1,5 +1,6 @@ { "@@locale": "pl", + "satisfyErrorText": "Wartość nie spełnia warunku.", "andSeparator": " i ", "betweenLengthErrorText": "Wartość musi mieć długość pomiędzy {min} a {max}, włącznie.", "@betweenLengthErrorText": { @@ -64,6 +65,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..c9e2cc14 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -1,5 +1,6 @@ { "@@locale": "pt", + "satisfyErrorText": "O valor não satisfaz a condição.", "andSeparator": " e ", "betweenLengthErrorText": "O valor deve ter um comprimento entre {min} e {max}, inclusive.", "@betweenLengthErrorText": { @@ -64,6 +65,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..96946bf7 100644 --- a/lib/l10n/intl_ro.arb +++ b/lib/l10n/intl_ro.arb @@ -1,5 +1,6 @@ { "@@locale": "ro", + "satisfyErrorText": "Valoarea nu satisface condiția.", "andSeparator": " și ", "betweenLengthErrorText": "Valoarea trebuie să aibă o lungime între {min} și {max}, inclusiv.", "@betweenLengthErrorText": { @@ -64,6 +65,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..524b7055 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -1,5 +1,6 @@ { "@@locale": "ru", + "satisfyErrorText": "Значение не удовлетворяет условию.", "andSeparator": " и ", "betweenLengthErrorText": "Значение должно иметь длину от {min} до {max} включительно.", "@betweenLengthErrorText": { @@ -64,6 +65,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..cdf723ba 100644 --- a/lib/l10n/intl_sk.arb +++ b/lib/l10n/intl_sk.arb @@ -1,5 +1,6 @@ { "@@locale": "sk", + "satisfyErrorText": "Hodnota nespĺňa podmienku.", "andSeparator": " a ", "betweenLengthErrorText": "Hodnota musí mať dĺžku medzi {min} a {max}, vrátane.", "@betweenLengthErrorText": { @@ -64,6 +65,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..cb9c913d 100644 --- a/lib/l10n/intl_sl.arb +++ b/lib/l10n/intl_sl.arb @@ -1,5 +1,6 @@ { "@@locale": "sl", + "satisfyErrorText": "Vrednost ne izpolnjuje pogoja.", "andSeparator": " in ", "betweenLengthErrorText": "Vrednost mora imeti dolžino med {min} in {max}, vključno.", "@betweenLengthErrorText": { @@ -64,6 +65,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..3c3e5a7d 100644 --- a/lib/l10n/intl_sq.arb +++ b/lib/l10n/intl_sq.arb @@ -1,5 +1,6 @@ { "@@locale": "sq", + "satisfyErrorText": "Vlera nuk plotëson kushtin.", "andSeparator": " dhe ", "betweenLengthErrorText": "Vlera duhet të ketë një gjatësi midis {min} dhe {max}, përfshirë.", "@betweenLengthErrorText": { @@ -64,6 +65,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..fbd9734a 100644 --- a/lib/l10n/intl_sv.arb +++ b/lib/l10n/intl_sv.arb @@ -1,5 +1,6 @@ { "@@locale": "sv", + "satisfyErrorText": "Värdet uppfyller inte villkoret.", "andSeparator": " och ", "betweenLengthErrorText": "Värdet måste ha en längd mellan {min} och {max}, inklusive.", "@betweenLengthErrorText": { @@ -64,6 +65,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..56fb2979 100644 --- a/lib/l10n/intl_sw.arb +++ b/lib/l10n/intl_sw.arb @@ -1,5 +1,6 @@ { "@@locale": "sw", + "satisfyErrorText": "Thamani haifikii sharti.", "andSeparator": " na ", "betweenLengthErrorText": "Thamani lazima iwe na urefu kati ya {min} na {max}, ikijumuisha.", "@betweenLengthErrorText": { @@ -64,6 +65,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..edbb44d6 100644 --- a/lib/l10n/intl_ta.arb +++ b/lib/l10n/intl_ta.arb @@ -1,5 +1,6 @@ { "@@locale": "ta", + "satisfyErrorText": "மதிப்பு நிபந்தனையை பூர்த்தி செய்யவில்லை।", "andSeparator": " மற்றும் ", "betweenLengthErrorText": "மதிப்பு {min} மற்றும் {max} இடையே நீளம் கொண்டிருக்க வேண்டும், உள்ளடங்கலாக.", "@betweenLengthErrorText": { @@ -64,6 +65,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..7902d1a2 100644 --- a/lib/l10n/intl_th.arb +++ b/lib/l10n/intl_th.arb @@ -1,5 +1,6 @@ { "@@locale": "th", + "satisfyErrorText": "ค่าไม่เป็นไปตามเงื่อนไข.", "andSeparator": " และ ", "betweenLengthErrorText": "ค่าต้องมีความยาวระหว่าง {min} และ {max} รวมทั้งสองค่า", "@betweenLengthErrorText": { @@ -64,6 +65,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..8ca5208f 100644 --- a/lib/l10n/intl_tr.arb +++ b/lib/l10n/intl_tr.arb @@ -1,5 +1,6 @@ { "@@locale": "tr", + "satisfyErrorText": "Değer koşulu karşılamıyor.", "andSeparator": " ve ", "betweenLengthErrorText": "Değer, {min} ile {max} arasında bir uzunluğa sahip olmalıdır, dahil.", "@betweenLengthErrorText": { @@ -64,6 +65,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..d07cf378 100644 --- a/lib/l10n/intl_uk.arb +++ b/lib/l10n/intl_uk.arb @@ -1,5 +1,6 @@ { "@@locale": "uk", + "satisfyErrorText": "Значення не задовольняє умову.", "andSeparator": " і ", "betweenLengthErrorText": "Значення повинно мати довжину від {min} до {max} включно.", "@betweenLengthErrorText": { @@ -64,6 +65,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..37cae995 100644 --- a/lib/l10n/intl_vi.arb +++ b/lib/l10n/intl_vi.arb @@ -1,5 +1,6 @@ { "@@locale": "vi", + "satisfyErrorText": "Giá trị không thỏa mãn điều kiện.", "andSeparator": " và ", "betweenLengthErrorText": "Giá trị phải có độ dài trong khoảng từ {min} đến {max}, bao gồm cả hai giá trị.", "@betweenLengthErrorText": { @@ -64,6 +65,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..7ad4f12a 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -1,5 +1,6 @@ { "@@locale": "zh", + "satisfyErrorText": "值不满足条件。", "andSeparator": " 和 ", "betweenLengthErrorText": "值的长度必须在 {min} 和 {max} 之间(包含)。", "@betweenLengthErrorText": { @@ -64,6 +65,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..933badb2 100644 --- a/lib/l10n/intl_zh_Hant.arb +++ b/lib/l10n/intl_zh_Hant.arb @@ -1,5 +1,6 @@ { "@@locale": "zh_Hant", + "satisfyErrorText": "值不滿足條件。", "andSeparator": " 和 ", "betweenLengthErrorText": "值的長度必須在 {min} 和 {max} 之間(包含)。", "@betweenLengthErrorText": { @@ -64,6 +65,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 4b5e037b..f44f0413 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'; @@ -1642,6 +1645,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 { @@ -1692,7 +1697,7 @@ final class Validators { String prefix = '', String suffix = '', String? separator, - bool printErrorAsSoonAsPossible = true, + c.bool printErrorAsSoonAsPossible = true, }) => val.and(validators, prefix: prefix, @@ -1772,7 +1777,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} @@ -1802,7 +1807,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 @@ -1834,7 +1839,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. /// @@ -1842,7 +1847,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. /// @@ -1859,14 +1864,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', /// ); /// ``` /// @@ -1876,20 +1881,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. /// @@ -1905,14 +1910,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', /// ); /// ``` /// @@ -1922,14 +1927,14 @@ final class Validators { /// - The error message uses the string representation of the value via /// `toString()`, which might not be ideal for all types /// {@endtemplate} - static Validator isNotEqual( + static Validator notEqual( T value, { - String Function(T input, T referenceValue)? isNotEqualMsg, + String Function(T input, T referenceValue)? notEqualMsg, }) => - val.isNotEqual(value, isNotEqualMsg: isNotEqualMsg); + val.notEqual(value, notEqualMsg: notEqualMsg); // Required validators - /// {@template validator_is_required} + /// {@template validator_required} /// Generates a validator function that enforces required field validation for /// form inputs. This validator ensures that a field has a non-null, non-empty /// value before any subsequent validation is performed. @@ -1944,7 +1949,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. /// @@ -1957,13 +1962,13 @@ final class Validators { /// ## Examples /// ```dart /// // Basic required field validation - /// final validator = isRequired(); - /// print(validator(null)); // Returns localized error message - /// print(validator('')); // Returns localized error message - /// print(validator('value')); // Returns null (validation passed) + /// final validator = Validators.required(); + /// assert(validator(null) != null); // Returns localized error message + /// assert(validator('') != null); // Returns localized error message + /// assert(validator('value') == null); // Returns null (validation passed) /// /// // Chaining with another validator - /// final complexValidator = isRequired( + /// final complexValidator = Validators.required( /// (value) => value.length < 10 ? 'Too long' : null, /// 'Custom required message' /// ); @@ -1973,60 +1978,13 @@ final class Validators { /// - The validator assumes empty strings/maps/iterables, white strings, and null /// values are equivalent for validation purposes /// {@endtemplate} - static Validator isRequired([ + static Validator required([ Validator? next, - String? isRequiredMsg, + String? requiredMsg, ]) => - val.isRequired(next, isRequiredMsg); - - /// {@template validator_validate_with_default} - /// Creates a validator function that applies a default value before validation, - /// making sure the `next` validator will always receive a non-null input. - /// - /// This function generates a new validator that first replaces null input - /// with a specified default value, then applies `next` validator. - /// - /// ## Type Parameters - /// - `T`: The non-nullable version of the type of the input being validated. - /// It must extend from `Object`. - /// - /// ## Parameters - /// - `defaultValue` (`T`): The fallback non-null value to use when input is null. - /// - `next` (`Validator`): The validation function to apply after the default - /// value has been potentially substituted. - /// - /// ## Returns - /// Returns a new `Validator` function that accepts nullable input and - /// produces validation results based on the combined default value substitution - /// and validation logic. The returned validator is a function that: - /// - Returns null if the value, potentially replaced with the default, passes - /// the `next` validation - /// - Returns an error message string if validation fails - /// - /// ## Examples - /// ```dart - /// // Create a validator that requires a minimum length of 3 - /// final minLength = (String value) => - /// value.length >= 3 ? null : 'Must be at least 3 characters'; - /// - /// // Wrap it with a default value of 'N/A' - /// final defaultValue = 'default value'; - /// final validator = validateWithDefault('N/A', minLength); - /// - /// print(validator(null)); // Returns null (valid) - /// print(validator('ab')); // Returns 'Must be at least 3 characters' - /// print(validator('abc')); // Returns null (valid) - /// // Equivalent to: - /// print(minLength(null ?? defaultValue)); // Returns null (valid) - /// print(minLength('ab' ?? defaultValue)); // Returns 'Must be at least 3 characters' - /// print(minLength('abc' ?? defaultValue)); // Returns null (valid) - /// ``` - /// {@endtemplate} - static Validator validateWithDefault( - T defaultValue, Validator next) => - val.validateWithDefault(defaultValue, next); + val.required(next, requiredMsg); - /// {@template validator_is_optional} + /// {@template validator_optional} /// Creates a validator function that makes a field optional while allowing additional validation /// rules. This validator is particularly useful in form validation scenarios where certain /// fields are not mandatory but still need to conform to specific rules when provided. @@ -2045,7 +2003,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. /// @@ -2059,29 +2017,76 @@ final class Validators { /// ## Examples /// ```dart /// // Basic optional string validator - /// final validator = isOptional(); + /// final validator = Validators.optional(); /// /// // Optional validator with additional email validation - /// final emailValidator = isOptional( + /// final emailValidator = Validators.optional( /// validateEmail, /// (_, error) => 'Invalid email format: $error', /// ); /// /// // Usage with different inputs - /// print(validator(null)); // Returns: null (valid) - /// print(validator('')); // Returns: null (valid) - /// print(emailValidator('invalid@email')); // Returns: error message + /// assert(validator(null) == null); // Returns: null (valid) + /// assert(validator('') == null); // Returns: null (valid) + /// assert(emailValidator('invalid@email') != null); // Returns: error message /// ``` /// /// ## Caveats /// - The validator assumes empty strings/maps/iterables, white strings, and null values are /// equivalent for validation purposes, all them are considered valid. /// {@endtemplate} - static Validator isOptional([ + static Validator optional([ Validator? next, - String Function(T input, String nextErrorMsg)? isOptionalMsg, + String Function(T input, String nextErrorMsg)? optionalMsg, ]) => - val.isOptional(next, isOptionalMsg); + val.optional(next, optionalMsg); + + /// {@template validator_validate_with_default} + /// Creates a validator function that applies a default value before validation, + /// making sure the `next` validator will always receive a non-null input. + /// + /// This function generates a new validator that first replaces null input + /// with a specified default value, then applies `next` validator. + /// + /// ## Type Parameters + /// - `T`: The non-nullable version of the type of the input being validated. + /// It must extend from `Object`. + /// + /// ## Parameters + /// - `defaultValue` (`T`): The fallback non-null value to use when input is null. + /// - `next` (`Validator`): The validation function to apply after the default + /// value has been potentially substituted. + /// + /// ## Returns + /// Returns a new `Validator` function that accepts nullable input and + /// produces validation results based on the combined default value substitution + /// and validation logic. The returned validator is a function that: + /// - Returns null if the value, potentially replaced with the default, passes + /// the `next` validation + /// - Returns an error message string if validation fails + /// + /// ## Examples + /// ```dart + /// // Create a validator that requires a minimum length of 3 + /// final minLength = (String value) => + /// value.length >= 3 ? null : 'Must be at least 3 characters'; + /// + /// // Wrap it with a default value of 'N/A' + /// final defaultValue = 'default value'; + /// final validator = Validators.validateWithDefault('N/A', minLength); + /// + /// assert(validator(null) == null); // Returns null (valid) + /// assert(validator('ab') != null); // Returns 'Must be at least 3 characters' + /// assert(validator('abc') == null); // Returns null (valid) + /// // Equivalent to: + /// assert(minLength(null ?? defaultValue) == null); // Returns null (valid) + /// assert(minLength('ab' ?? defaultValue) != null); // Returns 'Must be at least 3 characters' + /// assert(minLength('abc' ?? defaultValue) == null); // Returns null (valid) + /// ``` + /// {@endtemplate} + static Validator validateWithDefault( + T defaultValue, Validator next) => + val.validateWithDefault(defaultValue, next); // Transform Validator @@ -2149,7 +2154,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. @@ -2161,7 +2166,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. /// @@ -2174,18 +2179,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' @@ -2197,13 +2202,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. @@ -2219,7 +2224,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 /// @@ -2233,17 +2238,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' /// ``` @@ -2251,13 +2256,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. @@ -2273,7 +2278,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 /// @@ -2287,7 +2292,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) @@ -2295,11 +2300,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' /// ``` @@ -2307,13 +2312,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. @@ -2329,7 +2334,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 /// @@ -2343,7 +2348,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) @@ -2351,11 +2356,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' /// ``` @@ -2363,13 +2368,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. @@ -2385,7 +2390,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. @@ -2405,7 +2410,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) @@ -2413,20 +2418,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' /// ``` @@ -2434,14 +2439,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. @@ -2457,7 +2462,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 /// @@ -2471,21 +2476,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' /// ``` @@ -2495,11 +2500,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} @@ -2553,7 +2558,7 @@ final class Validators { static Validator matchesAllowedExtensions( List extensions, { String Function(List)? matchesAllowedExtensionsMsg, - bool caseSensitive = true, + c.bool caseSensitive = true, }) => val.matchesAllowedExtensions( extensions, @@ -2563,56 +2568,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. @@ -2656,7 +2611,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( @@ -2716,9 +2671,9 @@ final class Validators { /// be provided for special language requirements /// {@endtemplate} static Validator hasMinUppercaseChars({ - int min = 1, - int Function(String)? customUppercaseCounter, - String Function(String input, int min)? hasMinUppercaseCharsMsg, + c.int min = 1, + c.int Function(String input)? customUppercaseCounter, + String Function(String input, c.int min)? hasMinUppercaseCharsMsg, }) => val.hasMinUppercaseChars( min: min, @@ -2738,9 +2693,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. @@ -2777,9 +2732,9 @@ final class Validators { /// be provided for special language requirements /// {@endtemplate} static Validator hasMinLowercaseChars({ - int min = 1, - int Function(String)? customLowercaseCounter, - String Function(String input, int min)? hasMinLowercaseCharsMsg, + c.int min = 1, + c.int Function(String input)? customLowercaseCounter, + String Function(String input, c.int min)? hasMinLowercaseCharsMsg, }) => val.hasMinLowercaseChars( min: min, @@ -2799,7 +2754,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)?`): @@ -2843,9 +2798,9 @@ final class Validators { /// function should be provided for special numbering requirements /// {@endtemplate} static Validator hasMinNumericChars({ - int min = 1, - int Function(String)? customNumericCounter, - String Function(String input, int min)? hasMinNumericCharsMsg, + c.int min = 1, + c.int Function(String input)? customNumericCounter, + String Function(String input, c.int min)? hasMinNumericCharsMsg, }) => val.hasMinNumericChars( min: min, @@ -2910,9 +2865,9 @@ final class Validators { /// should be provided for specific character set requirements /// {@endtemplate} static Validator hasMinSpecialChars({ - int min = 1, - int Function(String)? customSpecialCounter, - String Function(String input, int min)? hasMinSpecialCharsMsg, + c.int min = 1, + c.int Function(String input)? customSpecialCounter, + String Function(String input, c.int min)? hasMinSpecialCharsMsg, }) => val.hasMinSpecialChars( min: min, @@ -2958,12 +2913,128 @@ final class Validators { /// or phone number validation /// {@endtemplate} static Validator match( - RegExp regex, { + RegExp regExp, { String Function(String input)? matchMsg, }) => - val.match(regex, matchMsg: matchMsg); + val.match(regExp, matchMsg: matchMsg); + + /// {@template validator_uuid} + /// A validator function that checks if a given string matches the UUID format. + /// + /// Creates a validator that ensures the input string conforms to the standard + /// UUID (Universally Unique Identifier) format, consisting of 32 hexadecimal + /// digits displayed in 5 groups separated by hyphens (8-4-4-4-12). + /// + /// ## Parameters + /// - `regex` (`RegExp?`): Optional custom regular expression pattern to override + /// the default UUID validation pattern. Useful for supporting different UUID + /// formats or adding additional constraints. + /// - `uuidMsg` (`String Function(String input)?`): Optional callback function that + /// generates a custom error message based on the invalid input. If not provided, + /// defaults to the standard form builder localization error text. + /// + /// ## Returns + /// Returns a `Validator` function that accepts a string input and returns: + /// - `null` if the input is valid + /// - An error message string if the input is invalid + /// + /// ## Examples + /// ```dart + /// // Using default UUID validation + /// final validator = uuid(); + /// print(validator('123e4567-e89b-12d3-a456-426614174000')); // null + /// print(validator('invalid-uuid')); // Returns error message + /// + /// // Using custom error message + /// final customValidator = uuid( + /// uuidMsg: (input) => 'Invalid UUID format: $input', + /// ); + /// + /// // Using custom regex pattern + /// final customPatternValidator = uuid( + /// regex: RegExp(r'^[0-9]{8}-[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{12}$'), + /// ); + /// ``` + /// + /// ## Caveats + /// - The default regex pattern accepts both uppercase and lowercase hexadecimal + /// digits (0-9, a-f, A-F) + /// - The validation only checks the format, not the actual UUID version or + /// variant compliance + /// {@endtemplate} + static Validator uuid({ + RegExp? regex, + String Function(String input)? uuidMsg, + }) => + val.uuid(regex: regex, uuidMsg: uuidMsg); // Collection validators + /// {@template validator_equal_length} + /// Creates a validator function that checks if the input collection's length equals + /// the specified length. The validator returns `null` for valid input and an error + /// message for invalid input. + /// + /// If validation fails and no custom error message generator is provided via + /// [equalLengthMsg], returns the default localized error message from + /// `FormBuilderLocalizations.current.equalLengthErrorText(expectedLength)`. + /// + /// ## Type Parameters + /// - `T`: The type of input to validate. Must be a collection, in other words, + /// it must be one of `String`, `Iterable` or `Map`. + /// + /// ## Parameters + /// - `expectedLength` (`int`): The exact length required. Must be non-negative. + /// - `equalLengthMsg` (`String Function(T input, int expectedLength)?`): Optional + /// function to generate custom error messages. Receives the input and the + /// expected length, returning an error message string. + /// + /// ## Return Value + /// A `Validator` function that produces: + /// - `null` for valid inputs (length == expectedLength) + /// - An error message string for invalid inputs (length != expectedLength) + /// + /// ## Throws + /// - `ArgumentError` when: + /// - [expectedLength] is negative + /// - input runtime type is not a collection + /// + /// ## Examples + /// ```dart + /// // String validation + /// final stringValidator = equalLength(3); + /// print(stringValidator('abc')); // Returns null + /// print(stringValidator('ab')); // Returns error message + /// print(stringValidator('abcd')); // Returns error message + /// + /// // List validation + /// final listValidator = equalLength(2); + /// print(listValidator([1, 2])); // Returns null + /// print(listValidator([1])); // Returns error message + /// print(listValidator([1, 2, 3])); // Returns error message + /// + /// // Custom error message + /// final customValidator = equalLength( + /// 5, + /// equalLengthMsg: (_, expectedLength) => + /// 'Text must be exactly $expectedLength chars long!', + /// ); + /// ``` + /// + /// ## Caveats + /// - Type parameter `T` must be restricted to `String`, `Map`, or `Iterable`. + /// While the compiler cannot enforce this restriction, it is the developer's + /// responsibility to maintain this constraint. + /// - The validator treats non-collection inputs as implementation errors rather + /// than validation failures. Validate input types before passing them to + /// this validator. + /// {@endtemplate} + static Validator equalLength(c.int expectedLength, + {String Function(T input, c.int expectedLength)? equalLengthMsg}) => + val.equalLength( + expectedLength, + equalLengthMsg: equalLengthMsg, + ); + /// {@template validator_min_length} /// Creates a validator function that checks if the input collection's length is /// greater than or equal to `min`. The validator returns `null` for valid input @@ -3020,8 +3091,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} @@ -3079,8 +3150,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} @@ -3147,88 +3218,22 @@ final class Validators { /// this validator. /// {@endtemplate} static Validator betweenLength( - int min, - int max, { - String Function(T input, {required int min, required int max})? + c.int min, + c.int max, { + String Function(T input, {required c.int min, required c.int max})? betweenLengthMsg, }) => val.betweenLength(min, max, betweenLengthMsg: betweenLengthMsg); - /// {@template validator_equal_length} - /// Creates a validator function that checks if the input collection's length equals - /// the specified length. The validator returns `null` for valid input and an error - /// message for invalid input. - /// - /// If validation fails and no custom error message generator is provided via - /// [equalLengthMsg], returns the default localized error message from - /// `FormBuilderLocalizations.current.equalLengthErrorText(expectedLength)`. - /// - /// ## Type Parameters - /// - `T`: The type of input to validate. Must be a collection, in other words, - /// it must be one of `String`, `Iterable` or `Map`. - /// - /// ## Parameters - /// - `expectedLength` (`int`): The exact length required. Must be non-negative. - /// - `equalLengthMsg` (`String Function(T input, int expectedLength)?`): Optional - /// function to generate custom error messages. Receives the input and the - /// expected length, returning an error message string. - /// - /// ## Return Value - /// A `Validator` function that produces: - /// - `null` for valid inputs (length == expectedLength) - /// - An error message string for invalid inputs (length != expectedLength) - /// - /// ## Throws - /// - `ArgumentError` when: - /// - [expectedLength] is negative - /// - input runtime type is not a collection - /// - /// ## Examples - /// ```dart - /// // String validation - /// final stringValidator = equalLength(3); - /// print(stringValidator('abc')); // Returns null - /// print(stringValidator('ab')); // Returns error message - /// print(stringValidator('abcd')); // Returns error message - /// - /// // List validation - /// final listValidator = equalLength(2); - /// print(listValidator([1, 2])); // Returns null - /// print(listValidator([1])); // Returns error message - /// print(listValidator([1, 2, 3])); // Returns error message - /// - /// // Custom error message - /// final customValidator = equalLength( - /// 5, - /// equalLengthMsg: (_, expectedLength) => - /// 'Text must be exactly $expectedLength chars long!', - /// ); - /// ``` - /// - /// ## Caveats - /// - Type parameter `T` must be restricted to `String`, `Map`, or `Iterable`. - /// While the compiler cannot enforce this restriction, it is the developer's - /// responsibility to maintain this constraint. - /// - The validator treats non-collection inputs as implementation errors rather - /// than validation failures. Validate input types before passing them to - /// this validator. - /// {@endtemplate} - static Validator equalLength(int expectedLength, - {String Function(T input, int expectedLength)? equalLengthMsg}) => - val.equalLength( - expectedLength, - equalLengthMsg: equalLengthMsg, - ); - // DateTime Validators - /// {@template validator_is_after} + /// {@template validator_after} /// Creates a [DateTime] validator that checks if an input date occurs after /// `reference`. /// /// ## Parameters /// - `reference` (`DateTime`): The baseline date against which the input will be compared. /// This serves as the minimum acceptable date (exclusive by default). - /// - `isAfterMsg` (`String Function(DateTime input, DateTime reference)?`): Optional custom + /// - `afterMsg` (`String Function(DateTime input, DateTime reference)?`): Optional custom /// error message generator. When provided, it receives both the input and reference /// dates to construct a context-aware error message. /// - `inclusive` (`bool`): When set to `true`, allows the input date to exactly match @@ -3242,36 +3247,36 @@ final class Validators { /// ## Examples /// ```dart /// // Basic usage requiring date after January 1st, 2025 - /// final validator = isAfter(DateTime(2025)); + /// final validator = Validators.after(DateTime(2025)); /// /// // Inclusive validation allowing exact match - /// final inclusiveValidator = isAfter( + /// final inclusiveValidator = Validators.after( /// DateTime(2024), /// inclusive: true, /// ); /// /// // Custom error message - /// final customValidator = isAfter( + /// final customValidator = Validators.after( /// DateTime(2024), /// isAfterMsg: (_, ref) => 'Please select a date after ${ref.toString()}', /// ); /// ``` /// {@endtemplate} - static Validator isAfter( + static Validator after( DateTime reference, { - String Function(DateTime input, DateTime reference)? isAfterMsg, - bool inclusive = false, + String Function(DateTime input, DateTime reference)? afterMsg, + c.bool inclusive = false, }) => - val.isAfter(reference, isAfterMsg: isAfterMsg, inclusive: inclusive); + val.after(reference, afterMsg: afterMsg, inclusive: inclusive); - /// {@template validator_is_before} + /// {@template validator_before} /// Creates a [DateTime] validator that checks if an input date occurs before /// `reference`. /// /// ## Parameters /// - `reference` (`DateTime`): The baseline date against which the input will be compared. /// This serves as the maximum acceptable date (exclusive by default). - /// - `isBeforeMsg` (`String Function(DateTime input, DateTime reference)?`): Optional custom + /// - `beforeMsg` (`String Function(DateTime input, DateTime reference)?`): Optional custom /// error message generator. When provided, it receives both the input and reference /// dates to construct a context-aware error message. /// - `inclusive` (`bool`): When set to `true`, allows the input date to exactly match @@ -3286,29 +3291,29 @@ final class Validators { /// ## Examples /// ```dart /// // Basic usage requiring date before January 1st, 2025 - /// final validator = isBefore(DateTime(2025)); + /// final validator = Validators.before(DateTime(2025)); /// /// // Inclusive validation allowing exact match - /// final inclusiveValidator = isBefore( + /// final inclusiveValidator = Validators.before( /// DateTime(2024), /// inclusive: true, /// ); /// /// // Custom error message - /// final customValidator = isBefore( + /// final customValidator = Validators.before( /// DateTime(2024), /// isBeforeMsg: (_, ref) => 'Please select a date before ${ref.toString()}', /// ); /// ``` /// {@endtemplate} - static Validator isBefore( + static Validator before( DateTime reference, { - String Function(DateTime input, DateTime reference)? isBeforeMsg, - bool inclusive = false, + String Function(DateTime input, DateTime reference)? beforeMsg, + c.bool inclusive = false, }) => - val.isBefore(reference, isBeforeMsg: isBeforeMsg, inclusive: inclusive); + val.before(reference, beforeMsg: beforeMsg, inclusive: inclusive); - /// {@template validator_is_date_time_between} + /// {@template validator_between_date_time} /// Creates a [DateTime] validator that checks if an input date falls within a specified /// range defined by `minReference` and `maxReference`. /// @@ -3321,7 +3326,7 @@ final class Validators { /// Input dates must occur after this date (or equal to it if `minInclusive` is true). /// - `maxReference` (`DateTime`): The upper bound of the acceptable date range. /// Input dates must occur before this date (or equal to it if `maxInclusive` is true). - /// - `isDateTimeBetweenMsg` (`String Function(DateTime, DateTime, DateTime)?`): Optional + /// - `betweenDateTimeMsg` (`String Function(DateTime, DateTime, DateTime)?`): Optional /// custom error message generator. When provided, it receives the input date and both /// reference dates to construct a context-aware error message. /// - `minInclusive` (`bool`): When set to `true`, allows the input date to exactly match @@ -3342,13 +3347,13 @@ final class Validators { /// ## Examples /// ```dart /// // Basic usage requiring date between 2023 and 2025 - /// final validator = isDateTimeBetween( + /// final validator = Validators.betweenDateTime( /// DateTime(2023), /// DateTime(2025), /// ); /// /// // Inclusive validation allowing exact matches - /// final inclusiveValidator = isDateTimeBetween( + /// final inclusiveValidator = Validators.betweenDateTime( /// DateTime(2023), /// DateTime(2025), /// minInclusive: true, @@ -3356,30 +3361,72 @@ final class Validators { /// ); /// /// // Custom error message - /// final customValidator = isDateTimeBetween( + /// final customValidator = Validators.betweenDateTime( /// DateTime(2023), /// DateTime(2025), - /// isDateTimeBetweenMsg: (_, min, max) => + /// betweenDateTimeMsg: (_, min, max) => /// 'Please select a date between ${min.toString()} and ${max.toString()}', /// ); /// ``` /// {@endtemplate} - static Validator isDateTimeBetween( + static Validator betweenDateTime( DateTime minReference, DateTime maxReference, { String Function( DateTime input, DateTime minReference, DateTime maxReference)? - isDateTimeBetweenMsg, - bool leftInclusive = false, - bool rightInclusive = false, + betweenDateTimeMsg, + c.bool leftInclusive = false, + c.bool rightInclusive = false, }) => - val.isDateTimeBetween(minReference, maxReference, - isDateTimeBetweenMsg: isDateTimeBetweenMsg, + val.betweenDateTime(minReference, maxReference, + betweenDateTimeMsg: betweenDateTimeMsg, minInclusive: leftInclusive, maxInclusive: rightInclusive); + /// {@template validator_max_file_size} + /// Validates that a file size in bytes is less than or equal to `max`. + /// + /// This validator compares the input integer (representing bytes) against a + /// maximum size threshold (`max`). The comparison can be performed using + /// either 1000-based units (B, kB, MB, etc.) or 1024-based units (B, KiB, + /// MiB, etc.) depending on the selected [base]. + /// + /// ## Parameters + /// - `max` (`int`): The maximum allowed file size in bytes + /// - `base` (`Base`): The base unit system to use for calculations and error messages. + /// Defaults to [Base.b1024] + /// - `maxFileSizeMsg` (`String Function(int input, int max, Base base)?`): + /// Optional custom error message generator that receives the input size, + /// maximum size, and base to produce a tailored error message + /// + /// ## Returns + /// A [Validator] function that returns `null` when the input is valid (less than or equal + /// to the maximum size), or an error message string when validation fails + /// + /// ## Examples + /// ```dart + /// // Create a validator restricting files to 5 MiB using 1024-based units + /// final validator = maxFileSize(5 * 1024 * 1024); + /// + /// // Create a validator restricting files to 5 MB using 1000-based units + /// final validator = maxFileSize(5 * 1000 * 1000, base: Base.b1000); + /// + /// // Using a custom error message + /// final validator = maxFileSize( + /// 10 * 1024 * 1024, + /// maxFileSizeMsg: (input, max, base) => 'File too large: ${formatBytes(input, base)}', + /// ); + /// ``` + /// {@endtemplate} + static Validator maxFileSize( + c.int max, { + val.Base base = val.Base.b1024, + String Function(c.int input, c.int max, val.Base base)? maxFileSizeMsg, + }) => + val.maxFileSize(max, base: base, maxFileSizeMsg: maxFileSizeMsg); + // Generic type validators - /// {@template validator_contains_element} + /// {@template validator_in_list} /// Creates a validator function that verifies if a given input is in `values`. /// /// ## Type Parameters @@ -3389,7 +3436,7 @@ final class Validators { /// ## Parameters /// - `values` (`List`): A non-empty list of valid values to check against. The input /// will be validated against these values. - /// - `containsElementMsg` (`String Function(T input, List values)?`): Optional callback + /// - `inListMsg` (`String Function(T input, List values)?`): Optional callback /// function that generates a custom error message when validation fails. The function /// receives the invalid input and the list of valid values as parameters. If not provided, /// defaults to the localized error text from FormBuilderLocalizations. @@ -3406,9 +3453,9 @@ final class Validators { /// ## Examples /// ```dart /// // Creating a validator with a custom error message generator - /// final countryValidator = containsElement( + /// final countryValidator = Validators.inList( /// ['USA', 'Canada', 'Mexico'], - /// containsElementMsg: (input, values) => + /// inListMsg: (input, values) => /// 'Country $input is not in allowed list: ${values.join(", ")}', /// ); /// @@ -3417,11 +3464,56 @@ final class Validators { /// final valid = countryValidator('USA'); // Returns null (valid) /// ``` /// {@endtemplate} - static Validator containsElement( + static Validator inList( List values, { - String Function(T input, List values)? containsElementMsg, + String Function(T input, List values)? inListMsg, }) => - val.containsElement(values, containsElementMsg: containsElementMsg); + val.inList(values, inListMsg: inListMsg); + + /// {@template validator_not_in_list} + /// Creates a validator function that verifies if a given input is not in + /// `values`. + /// + /// ## Type Parameters + /// - `T`: The type of elements to validate. Must extend Object?, allowing nullable + /// types. + /// + /// ## Parameters + /// - `values` (`List`): A non-empty list of invalid values to check + /// against. The input will be validated against these values. + /// - `notInListMsg` (`String Function(T input, List values)?`): Optional callback + /// function that generates a custom error message when validation fails. The function + /// receives the invalid input and the list of invalid values as parameters. If not provided, + /// defaults to the localized error text from FormBuilderLocalizations. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns null if the input value does not exist in the provided list + /// - Returns a generated error message if the input was found in the list. + /// + /// ## Throws + /// - `AssertionError`: Thrown if the provided values list is empty, which would + /// make any input valid. + /// + /// ## Examples + /// ```dart + /// // Creating a validator with a custom error message generator + /// final countryValidator = Validators.notInList( + /// ['USA', 'Canada', 'Mexico'], + /// notInListMsg: (input, values) => + /// 'Country $input is in the forbidden list: ${values.join(", ")}', + /// ); + /// + /// // Using the validator + /// final result = countryValidator('Brazil'); // Returns null (valid) + /// final valid = countryValidator('USA'); // Returns "Country USA is in the forbidden list: USA, Canada, Mexico" + /// ``` + /// {@endtemplate} + static Validator notInList( + List values, { + String Function(T input, List values)? notInListMsg, + }) => + val.notInList(values, notInListMsg: notInListMsg); /// {@template validator_is_true} /// Creates a validator function that checks if a given input represents a `true` @@ -3470,8 +3562,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); @@ -3522,12 +3614,256 @@ 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); + /// {@template validator_satisfy} + /// Creates a validator function that checks if a given input satisfies a custom + /// boolean condition. This is a general-purpose validator that allows you to + /// define arbitrary validation logic through a predicate function. + /// + /// ## Type Parameters + /// - `T`: The type of input to validate. Can be any type including nullable + /// types. + /// + /// ## Parameters + /// - `condition` (`bool Function(T)`): A predicate function that defines the + /// validation logic. Should return `true` for valid inputs and `false` for + /// invalid inputs. + /// - `satisfyMsg` (`String Function(T input)?`): Optional callback function to + /// generate custom error messages for inputs that fail validation. Receives + /// the invalid input as a parameter. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if the input satisfies the condition + /// - Returns an error message if the input fails validation, either from + /// `satisfyMsg` or the default localized text + /// + /// ## Examples + /// ```dart + /// // Validate that a number is positive + /// final positiveValidator = satisfy( + /// (value) => value > 0, + /// satisfyMsg: (input) => '$input must be a positive number', + /// ); + /// assert(positiveValidator(5) == null); // Valid + /// assert(positiveValidator(-1) != null); // Invalid + /// + /// // Validate string length + /// final lengthValidator = satisfy( + /// (value) => value.length >= 3, + /// satisfyMsg: (input) => 'Text must be at least 3 characters long', + /// ); + /// assert(lengthValidator('hello') == null); // Valid + /// assert(lengthValidator('hi') != null); // Invalid + /// + /// // Complex validation with multiple conditions + /// final emailValidator = satisfy( + /// (email) => email.contains('@') && email.contains('.'), + /// satisfyMsg: (input) => 'Please enter a valid email address', + /// ); + /// ``` + /// {@endtemplate} + static Validator satisfy( + c.bool Function(T) condition, { + String Function(T input)? satisfyMsg, + }) => + val.satisfy(condition, satisfyMsg: satisfyMsg); + + // Miscellaneous validators + /// {@template validator_color_code} + /// Creates a validator function that validates color code strings in specified formats. + /// This validator supports hexadecimal, RGB, and HSL color formats and allows custom + /// validation logic for specialized color code requirements. + /// + /// ## Parameters + /// - `formats` (`Set`): The color formats to accept during validation. + /// Defaults to supporting HEX, RGB, and HSL formats. Cannot be empty. + /// - `customColorCode` (`bool Function(String)?`): Optional custom validation function + /// that overrides the default color code validation logic. Should return `true` for + /// valid color codes and `false` for invalid ones. + /// - `colorCodeMsg` (`String Function(String input)?`): Optional callback function to + /// generate custom error messages for invalid color codes. Receives the invalid + /// input as a parameter. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if the input is a valid color code in any of the specified formats + /// - Returns an error message if the input fails validation, either from `colorCodeMsg` + /// or the default localized text + /// + /// ## Throws + /// - `ArgumentError`: When the `formats` set is empty + /// + /// ## Examples + /// ```dart + /// // Validate HEX colors only + /// final hexValidator = colorCode( + /// formats: {ColorFormat.Hex}, + /// colorCodeMsg: (input) => 'Please enter a valid HEX color', + /// ); + /// assert(hexValidator('#FF5733') == null); // Valid + /// assert(hexValidator('rgb(255, 87, 51)') != null); // Invalid format + /// + /// // Validate multiple formats + /// final multiValidator = colorCode( + /// formats: {ColorFormat.Hex, ColorFormat.Rgb}, + /// ); + /// assert(multiValidator('#FF5733') == null); // Valid HEX + /// assert(multiValidator('rgb(255, 87, 51)') == null); // Valid RGB + /// assert(multiValidator('hsl(14, 100%, 60%)') != null); // Invalid HSL + /// + /// // Custom validation logic + /// final customValidator = colorCode( + /// customColorCode: (value) => value.startsWith('#') && value.length == 7, + /// colorCodeMsg: (input) => 'Only 6-digit HEX colors allowed', + /// ); + /// ``` + /// + /// ## Caveats + /// - RGB values are validated to be within the 0-255 range + /// - HSL hue values must be within 0-360 degrees + /// - HSL saturation and lightness values must be within 0-100 percent + /// - When `customColorCode` is provided, it completely overrides default validation + /// {@endtemplate} + static Validator colorCode({ + Set formats = const { + val.ColorFormat.hex, + val.ColorFormat.rgb, + val.ColorFormat.hsl, + }, + c.bool Function(String)? customColorCode, + String Function(String input)? colorCodeMsg, + }) => + val.colorCode( + formats: formats, + customColorCode: customColorCode, + colorCodeMsg: colorCodeMsg, + ); + + /// {@template validator_isbn} + /// Creates a validator function that validates International Standard Book Number (ISBN) strings. + /// This validator supports both ISBN-10 and ISBN-13 formats and automatically handles + /// common formatting variations including hyphens and spaces. + /// + /// The validator performs comprehensive ISBN validation including checksum verification + /// to ensure the provided ISBN is mathematically valid according to the ISBN standard. + /// It accepts ISBNs with or without separating hyphens and handles the special 'X' + /// check digit used in ISBN-10 format. + /// + /// ## Parameters + /// - `isbnMsg` (`String Function(String input)?`): Optional callback function to + /// generate custom error messages for invalid ISBN strings. Receives the invalid + /// input as a parameter and should return a user-friendly error message. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if the input is a valid ISBN-10 or ISBN-13 + /// - Returns an error message if the input fails validation, either from `isbnMsg` + /// or the default localized text + /// + /// ## Examples + /// ```dart + /// // Basic ISBN validation + /// final isbnValidator = isbn(); + /// assert(isbnValidator('978-0-13-110362-7') == null); // Valid ISBN-13 + /// assert(isbnValidator('0-13-110362-8') == null); // Valid ISBN-10 + /// assert(isbnValidator('invalid-isbn') != null); // Invalid + /// + /// // Custom error message + /// final customValidator = isbn( + /// isbnMsg: (input) => 'Please enter a valid ISBN-10 or ISBN-13', + /// ); + /// + /// // Various valid formats + /// assert(isbnValidator('9780131103627') == null); // No hyphens + /// assert(isbnValidator('978 0 13 110362 7') == null); // Spaces + /// assert(isbnValidator('013110362X') == null); // ISBN-10 with X + /// ``` + /// + /// ## Caveats + /// - Formatting characters (hyphens and spaces) are automatically stripped during validation + /// {@endtemplate} + static Validator isbn({ + String Function(String input)? isbnMsg, + }) => + val.isbn(isbnMsg: isbnMsg); + // Numeric validators + /// {@template validator_between} + /// Creates a validator function that checks if a numeric input falls within a specified + /// range defined by `min` and `max` values. + /// + /// ## Type Parameters + /// - `T`: A numeric type that extends [num], allowing `int`, `double` or + /// `num` validations + /// + /// ## Parameters + /// - `min` (`T`): The lower bound of the valid range + /// - `max` (`T`): The upper bound of the valid range + /// - `minInclusive` (`bool`): Determines if the lower bound is inclusive. Defaults to `true` + /// - `maxInclusive` (`bool`): Determines if the upper bound is inclusive. Defaults to `true` + /// - `betweenMsg` (`String Function(T input, T min, T max, bool minInclusive, bool maxInclusive)?`): + /// Optional custom error message generator that takes the input value, inclusivity flags, + /// and range bounds as parameters + /// + /// + /// ## Returns + /// Returns a [Validator] function that: + /// - Returns `null` if the input falls within the specified range according to the + /// inclusivity settings + /// - Returns an error message string if validation fails, either from the custom + /// `betweenMsg` function or the default localized error text from + /// [FormBuilderLocalizations] + /// + /// ## Throw + /// - `AssertionError`: when `max` is not greater than or equal to `min`. + /// + /// ## Examples + /// ```dart + /// // Basic usage with inclusive bounds + /// final ageValidator = between(18, 65); // [18, 65] + /// + /// // Exclusive upper bound for decimal values + /// final priceValidator = between( // [0.0, 100.0) + /// 0.0, + /// 100.0, + /// maxInclusive: false, + /// ); + /// + /// // Custom error message + /// final scoreValidator = between( // + /// 0.0, + /// 10.0, + /// betweenMsg: (_, min, max, __, ___) => + /// 'Score must be between $min and $max (inclusive)', + /// ); + /// ``` + /// + /// ## Caveats + /// - The default behavior uses inclusive bounds (`>=` and `<=`) + /// {@endtemplate} + static Validator between(T min, T max, + {c.bool minInclusive = true, + c.bool maxInclusive = true, + String Function( + T input, + T min, + T max, + c.bool minInclusive, + c.bool maxInclusive, + )? betweenMsg}) => + val.between( + min, + max, + minInclusive: minInclusive, + maxInclusive: maxInclusive, + betweenMsg: betweenMsg, + ); + /// {@template validator_greater_than} /// Creates a validator function that checks if a numeric input exceeds `reference`. /// @@ -3561,8 +3897,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} @@ -3601,8 +3937,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); @@ -3640,8 +3976,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} @@ -3680,83 +4016,58 @@ final class Validators { /// ## Caveats /// - The validator uses less than or equal to comparison (`<=`) /// {@endtemplate} - static Validator lessThanOrEqualTo(T reference, - {String Function(num input, num reference)? lessThanOrEqualToMsg}) => + static Validator lessThanOrEqualTo(T reference, + {String Function(c.num input, c.num reference)? + lessThanOrEqualToMsg}) => val.lessThanOrEqualTo(reference, lessThanOrEqualToMsg: lessThanOrEqualToMsg); - /// {@template validator_between} - /// Creates a validator function that checks if a numeric input falls within a specified - /// range defined by `min` and `max` values. - /// - /// ## Type Parameters - /// - `T`: A numeric type that extends [num], allowing `int`, `double` or - /// `num` validations + // User information validators + + /// {@template validator_email} + /// A validator function that checks if a given string is a valid email address. + /// Uses either a custom or default RFC 5322 compliant regular expression for validation. /// /// ## Parameters - /// - `min` (`T`): The lower bound of the valid range - /// - `max` (`T`): The upper bound of the valid range - /// - `minInclusive` (`bool`): Determines if the lower bound is inclusive. Defaults to `true` - /// - `maxInclusive` (`bool`): Determines if the upper bound is inclusive. Defaults to `true` - /// - `betweenMsg` (`String Function(T input, T min, T max, bool minInclusive, bool maxInclusive)?`): - /// Optional custom error message generator that takes the input value, inclusivity flags, - /// and range bounds as parameters + /// - `regex` (`RegExp?`): Optional custom regular expression for email validation. + /// If not provided, uses a default RFC 5322 compliant pattern that supports: + /// - ASCII characters + /// - Unicode characters (including IDN domains) + /// - Special characters in local part + /// - Quoted strings + /// - Multiple dots /// + /// - `emailMsg` (`String Function(String input)?`): Optional custom error message + /// generator function that takes the invalid input and returns a custom error + /// message. If not provided, uses the default localized error text. /// /// ## Returns - /// Returns a [Validator] function that: - /// - Returns `null` if the input falls within the specified range according to the - /// inclusivity settings - /// - Returns an error message string if validation fails, either from the custom - /// `betweenMsg` function or the default localized error text from - /// [FormBuilderLocalizations] - /// - /// ## Throw - /// - `AssertionError`: when `max` is not greater than or equal to `min`. + /// Returns a `Validator` function that: + /// - Returns `null` if the email is valid + /// - Returns an error message string if the email is invalid /// /// ## Examples + /// Basic usage with default settings: /// ```dart - /// // Basic usage with inclusive bounds - /// final ageValidator = between(18, 65); // [18, 65] - /// - /// // Exclusive upper bound for decimal values - /// final priceValidator = between( // [0.0, 100.0) - /// 0.0, - /// 100.0, - /// maxInclusive: false, - /// ); + /// final emailValidator = email(); + /// final result = emailValidator('user@example.com'); + /// print(result); // null (valid email) + /// ``` /// - /// // Custom error message - /// final scoreValidator = between( // - /// 0.0, - /// 10.0, - /// betweenMsg: (_, min, max, __, ___) => - /// 'Score must be between $min and $max (inclusive)', + /// Using custom regex and error message: + /// ```dart + /// final customValidator = email( + /// regex: RegExp(r'^[a-zA-Z0-9.]+@company\.com$'), + /// emailMsg: (input) => '$input is not a valid company email', /// ); /// ``` - /// - /// ## Caveats - /// - The default behavior uses inclusive bounds (`>=` and `<=`) /// {@endtemplate} - static Validator between(T min, T max, - {bool minInclusive = true, - bool maxInclusive = true, - String Function( - T input, - T min, - T max, - bool minInclusive, - bool maxInclusive, - )? betweenMsg}) => - val.between( - min, - max, - minInclusive: minInclusive, - maxInclusive: maxInclusive, - betweenMsg: betweenMsg, - ); + static Validator email({ + RegExp? regex, + String Function(String input)? emailMsg, + }) => + val.email(regex: regex, emailMsg: emailMsg); - // User information validators /// {@template validator_password} /// Creates a composite validator for password validation that enforces multiple /// password strength requirements simultaneously. @@ -3800,12 +4111,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( @@ -3863,50 +4174,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 @@ -3955,11 +4222,109 @@ final class Validators { /// ``` /// {@endtemplate} static Validator creditCard({ + // TODO(ArturAssisComp): turn this into a function, becoming more generic. RegExp? regex, String Function(String input)? creditCardMsg, }) => val.creditCard(regex: regex, creditCardMsg: creditCardMsg); + ///{@template validator_iban} + /// A validator function that checks if a string is a valid International Bank + /// Account Number (IBAN). + /// + /// Returns null if the input is a valid IBAN format, otherwise returns an + /// error message. The validator performs standard IBAN validation including + /// length check, character conversion, and checksum calculation according to + /// the ISO 13616 standard. + /// + /// ## Parameters + /// - `isIban` (`bool Function(String input)?`): Optional custom validation + /// function that determines if the input is a valid IBAN. If provided, this + /// function overrides the default validation logic. + /// - `ibanMsg` (`String Function(String input)?`): Optional function that + /// returns a custom error message when validation fails. If not provided, + /// the default localized error message is used. + /// + /// ## Returns + /// A `Validator` function that accepts a string input and returns + /// null for valid IBANs or an error message string for invalid IBANs. + /// + /// ## Examples + /// ```dart + /// // Basic usage with default validation and error message + /// final validator = Validators.iban(); + /// assert(validator('GB82 WEST 1234 5698 7654 32') == null); // Valid IBAN + /// assert(validator('invalid123') != null); // Invalid IBAN + /// + /// // Using custom validation logic + /// final customValidator = FormBuilderValidators.iban( + /// isIban: (input) => input.startsWith('DE'), + /// ibanMsg: (input) => 'Only German IBANs are accepted', + /// ); + /// assert(customValidator('DE89 3704 0044 0532 0130 00') == null); // Valid German IBAN + /// assert(customValidator('GB82 WEST 1234 5698 7654 32') != null); // Not a German IBAN + /// ``` + /// + /// ## Caveats + /// - The validator removes all spaces from the input before validation + /// - The validation is case-insensitive as the input is converted to uppercase + /// - The minimum length check for IBANs is set to 15 characters (after removing spaces) + /// {@endtemplate} + static Validator iban({ + c.bool Function(String input)? isIban, + String Function(String input)? ibanMsg, + }) => + val.iban(isIban: isIban, ibanMsg: ibanMsg); + + ///{@template validator_bic} + /// Creates a validator that checks if a string is a valid BIC (Bank Identifier Code). + /// + /// A BIC validator checks string inputs against standard BIC format regulations. The + /// validator returns `null` for valid BICs and an error message for invalid inputs. + /// + /// BIC codes must consist of 8 or 11 characters: 4 bank code letters, 2 country code + /// letters, 2 location code alphanumeric characters, and optionally 3 branch code + /// alphanumeric characters. + /// + /// ## Parameters + /// - `isBic` (`bool Function(String input)?`): Optional custom function to determine + /// if a string is a valid BIC. If provided, this function overrides the default + /// BIC validation logic. + /// - `bicMsg` (`String Function(String input)?`): Optional custom function to generate + /// error messages for invalid BICs. If provided, this function overrides the default + /// error message. + /// + /// ## Returns + /// A `Validator` function that returns: + /// - `null` if the input is a valid BIC + /// - An error message string if the input is not a valid BIC + /// + /// ## Examples + /// ```dart + /// // Using default validation + /// final validator = bic(); + /// assert(validator('DEUTDEFF') == null); // Valid: 8-character BIC + /// assert(validator('DEUTDEFFXXX') == null); // Valid: 11-character BIC + /// assert(validator('deut deff xxx') == null); // Valid: spaces are removed and case is normalized + /// assert(validator('DEUT123') != null); // Invalid: too short + /// assert(validator('DEUTDEFFXXXX') != null); // Invalid: too long + /// assert(validator('123TDEFF') != null); // Invalid: first 4 chars must be letters + /// + /// // Using custom validation and error message + /// final validator = bic( + /// isBic: (value) => value.startsWith('DEUT'), + /// bicMsg: (value) => 'BIC must start with DEUT, got: $value', + /// ); + /// assert(validator('DEUTDEFF') == null); // Valid: starts with DEUT + /// assert(validator('ABCDDEFF') != null); // Invalid: custom error message + /// ``` + ///{@endtemplate} + static Validator bic({ + c.bool Function(String input)? isBic, + String Function(String input)? bicMsg, + }) => + val.bic(isBic: isBic, bicMsg: bicMsg); + // Network validators /// {@template validator_ip} @@ -3986,8 +4351,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( @@ -4043,14 +4408,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( @@ -4061,9 +4426,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, @@ -4079,4 +4444,50 @@ final class Validators { regex: regex, urlMsg: urlMsg, ); + + /// {@template validator_mac_address} + /// A validator function that checks if a given string represents a valid MAC + /// (Media Access Control) address. This validator supports standard MAC address + /// formats with customizable validation logic. + /// + /// The validator accepts MAC addresses in two common formats: + /// - Colon or hyphen separated: 00:1B:44:11:3A:B7 or 00-1B-44-11-3A-B7 + /// - Dot separated: 0123.4567.89AB + /// + /// ## Parameters + /// - `isMacAddress` (`bool Function(String)?`): Optional custom validation function + /// that overrides the default MAC address format checking. When provided and + /// returns `true`, the string is considered valid + /// - `macAddressMsg` (`String Function(String input)?`): Custom error message + /// generator function that takes the invalid MAC address as input + /// + /// ## Returns + /// Returns `null` if the MAC address is valid according to either the custom + /// validation function or the default regex pattern. Otherwise, returns an error + /// message string, either custom-generated via `macAddressMsg` or the default + /// localized MAC address error text. + /// + /// ## Examples + /// ```dart + /// // Basic MAC address validation + /// final validator = macAddress(); + /// assert(validator('00:1B:44:11:3A:B7') == null); // Returns: null + /// assert(validator('00-1B-44-11-3A-B7') == null); // Returns: null + /// assert(validator('0123.4567.89AB') == null); // Returns: null + /// + /// // With custom validation logic + /// final customValidator = macAddress( + /// isMacAddress: (value) => value.length == 17, + /// macAddressMsg: (input) => 'Invalid MAC: $input' + /// ); + /// ``` + /// {@endtemplate} + static Validator macAddress({ + c.bool Function(String)? isMacAddress, + String Function(String input)? macAddressMsg, + }) => + val.macAddress( + isMacAddress: isMacAddress, + macAddressMsg: macAddressMsg, + ); } diff --git a/lib/src/validators/core_validators/equality_validators.dart b/lib/src/validators/core_validators/equality_validators.dart index 7aedd1a4..289fd42b 100644 --- a/lib/src/validators/core_validators/equality_validators.dart +++ b/lib/src/validators/core_validators/equality_validators.dart @@ -1,29 +1,29 @@ import '../../../localization/l10n.dart'; import '../constants.dart'; -/// {@macro validator_is_equal} -Validator isEqual( +/// {@macro validator_equal} +Validator equal( T referenceValue, { - String Function(T input, T referenceValue)? isEqualMsg, + String Function(T input, T referenceValue)? equalMsg, }) { return (T input) { return referenceValue == input ? null - : isEqualMsg?.call(input, referenceValue) ?? + : equalMsg?.call(input, referenceValue) ?? FormBuilderLocalizations.current .equalErrorText(referenceValue.toString()); }; } -/// {@macro validator_is_not_equal} -Validator isNotEqual( +/// {@macro validator_not_equal} +Validator notEqual( T referenceValue, { - String Function(T input, T referenceValue)? isNotEqualMsg, + String Function(T input, T referenceValue)? notEqualMsg, }) { return (T input) { return referenceValue != input ? null - : isNotEqualMsg?.call(input, referenceValue) ?? + : notEqualMsg?.call(input, referenceValue) ?? FormBuilderLocalizations.current .notEqualErrorText(referenceValue.toString()); }; diff --git a/lib/src/validators/core_validators/required_validators.dart b/lib/src/validators/core_validators/required_validators.dart index 5c999a77..7d92c20f 100644 --- a/lib/src/validators/core_validators/required_validators.dart +++ b/lib/src/validators/core_validators/required_validators.dart @@ -1,17 +1,16 @@ import '../../../localization/l10n.dart'; import '../constants.dart'; -/// {@macro validator_is_required} -Validator isRequired([ +/// {@macro validator_required} +Validator required([ Validator? next, - String? isRequiredMsg, + String? requiredMsg, ]) { String? finalValidator(T? value) { final (bool isValid, T? transformedValue) = _isRequiredValidateAndConvert(value); if (!isValid) { - return isRequiredMsg ?? - FormBuilderLocalizations.current.requiredErrorText; + return requiredMsg ?? FormBuilderLocalizations.current.requiredErrorText; } return next?.call(transformedValue!); } @@ -25,10 +24,10 @@ Validator validateWithDefault( return (T? value) => next(value ?? defaultValue); } -/// {@macro validator_is_optional} -Validator isOptional([ +/// {@macro validator_optional} +Validator optional([ Validator? next, - String Function(T input, String nextErrorMsg)? isOptionalMsg, + String Function(T input, String nextErrorMsg)? optionalMsg, ]) { return (T? input) { final (bool isValid, T? transformedValue) = @@ -42,7 +41,7 @@ Validator isOptional([ return null; } - return isOptionalMsg?.call(input!, nextErrorMessage) ?? + return optionalMsg?.call(input!, nextErrorMessage) ?? FormBuilderLocalizations.current.isOptionalErrorText(nextErrorMessage); }; } diff --git a/lib/src/validators/core_validators/type_validators.dart b/lib/src/validators/core_validators/type_validators.dart index 607999a5..6ac04b89 100644 --- a/lib/src/validators/core_validators/type_validators.dart +++ b/lib/src/validators/core_validators/type_validators.dart @@ -3,16 +3,16 @@ import '../../../localization/l10n.dart'; import '../constants.dart'; -/// {@macro validator_is_string} -Validator isString([ +/// {@macro validator_string} +Validator string([ Validator? next, - String Function(T input)? isStringMsg, + String Function(T input)? stringMsg, ]) { String? finalValidator(T input) { final (bool isValid, String? typeTransformedValue) = _isStringValidateAndConvert(input); if (!isValid) { - return isStringMsg?.call(input) ?? + return stringMsg?.call(input) ?? FormBuilderLocalizations.current.isStringErrorText; } return next?.call(typeTransformedValue!); @@ -28,7 +28,7 @@ Validator isString([ return (false, null); } -/// {@macro validator_is_int} +/// {@macro validator_int} Validator isInt( [Validator? next, String Function(T input)? isIntMsg]) { String? finalValidator(T input) { @@ -57,7 +57,7 @@ Validator isInt( return (false, null); } -/// {@macro validator_is_num} +/// {@macro validator_num} Validator isNum( [Validator? next, String Function(T input)? isNumMsg]) { String? finalValidator(T input) { @@ -86,7 +86,7 @@ Validator isNum( return (false, null); } -/// {@macro validator_is_double} +/// {@macro validator_double} Validator isDouble( [Validator? next, String Function(T input)? isDoubleMsg]) { String? finalValidator(T input) { @@ -117,7 +117,7 @@ Validator isDouble( return (false, null); } -/// {@macro validator_is_bool} +/// {@macro validator_bool} Validator isBool( [Validator? next, String Function(T input)? isBoolMsg, @@ -152,16 +152,16 @@ Validator isBool( return (false, null); } -/// {@macro validator_is_date_time} -Validator isDateTime([ +/// {@macro validator_date_time} +Validator dateTime([ Validator? next, - String Function(T input)? isDateTimeMsg, + String Function(T input)? dateTimeMsg, ]) { String? finalValidator(T input) { final (bool isValid, DateTime? typeTransformedValue) = _isDateTimeValidateAndConvert(input); if (!isValid) { - return isDateTimeMsg?.call(input) ?? + return dateTimeMsg?.call(input) ?? FormBuilderLocalizations.current.dateTimeErrorText; } return next?.call(typeTransformedValue!); diff --git a/lib/src/validators/datetime_validators.dart b/lib/src/validators/datetime_validators.dart index 5ff13fd7..c51952b3 100644 --- a/lib/src/validators/datetime_validators.dart +++ b/lib/src/validators/datetime_validators.dart @@ -1,44 +1,44 @@ import '../../localization/l10n.dart'; import 'constants.dart'; -/// {@macro validator_is_after} -Validator isAfter( +/// {@macro validator_after} +Validator after( DateTime reference, { - String Function(DateTime input, DateTime reference)? isAfterMsg, + String Function(DateTime input, DateTime reference)? afterMsg, bool inclusive = false, }) { return (DateTime input) { return input.isAfter(reference) || (inclusive ? input.isAtSameMomentAs(reference) : false) ? null - : isAfterMsg?.call(input, reference) ?? + : afterMsg?.call(input, reference) ?? FormBuilderLocalizations.current .dateMustBeAfterErrorText(reference.toLocal()); }; } -/// {@macro validator_is_before} -Validator isBefore( +/// {@macro validator_before} +Validator before( DateTime reference, { - String Function(DateTime input, DateTime reference)? isBeforeMsg, + String Function(DateTime input, DateTime reference)? beforeMsg, bool inclusive = false, }) { return (DateTime input) { return input.isBefore(reference) || (inclusive ? input.isAtSameMomentAs(reference) : false) ? null - : isBeforeMsg?.call(input, reference) ?? + : beforeMsg?.call(input, reference) ?? FormBuilderLocalizations.current .dateMustBeBeforeErrorText(reference.toLocal()); }; } -/// {@macro validator_is_date_time_between} -Validator isDateTimeBetween( +/// {@macro validator_between_date_time} +Validator betweenDateTime( DateTime minReference, DateTime maxReference, { String Function(DateTime input, DateTime minReference, DateTime maxReference)? - isDateTimeBetweenMsg, + betweenDateTimeMsg, bool minInclusive = false, bool maxInclusive = false, }) { @@ -52,7 +52,7 @@ Validator isDateTimeBetween( (input.isAfter(minReference) || (minInclusive ? input.isAtSameMomentAs(minReference) : false)) ? null - : isDateTimeBetweenMsg?.call(input, minReference, maxReference) ?? + : betweenDateTimeMsg?.call(input, minReference, maxReference) ?? FormBuilderLocalizations.current.dateMustBeBetweenErrorText( minReference.toLocal(), maxReference.toLocal()); }; diff --git a/lib/src/validators/file_validators.dart b/lib/src/validators/file_validators.dart new file mode 100644 index 00000000..72025e11 --- /dev/null +++ b/lib/src/validators/file_validators.dart @@ -0,0 +1,120 @@ +import 'dart:math'; + +import 'package:intl/intl.dart'; + +import '../../localization/l10n.dart'; +import 'constants.dart'; + +/// Defines the unit systems used for file size calculations and representations. +/// +/// This enum provides two common standards for representing file sizes: +/// - 1000-based units (B, kB, MB, GB, TB) where 1 kilobyte = 1000 bytes +/// - 1024-based units (B, KiB, MiB, GiB, TiB) where 1 kibibyte = 1024 bytes +/// +/// ## Examples +/// ```dart +/// // Format 1_500_000 bytes using the 1000-based system +/// final readableSize = formatBytes(1_500_000, Base.b1000); // "1.5 MB" +/// +/// // Format 1_500_000 bytes using the 1024-based system +/// final readableSize = formatBytes(1_500_000, Base.b1024); // "1.46 MiB" +/// ``` +enum Base { + /// 1KB is equivalent to 10^3 = 1000 B + b1000(1000, ['B', 'KB', 'MB', 'GB', 'TB']), + + /// 1KiB is equivalent to 2^10 = 1024 B + b1024(1024, ['B', 'KiB', 'MiB', 'GiB', 'TiB']); + + /// The number of bytes in one 'kilo' unit. + final int base; + + /// The units available for the current base. + List get units => List.unmodifiable(_units); + final List _units; + + const Base(this.base, this._units); +} + +/// {@macro validator_max_file_size} +Validator maxFileSize( + int max, { + Base base = Base.b1024, + String Function(int input, int max, Base base)? maxFileSizeMsg, +}) { + return (int input) { + if (input <= max) { + return null; + } + if (maxFileSizeMsg != null) { + return maxFileSizeMsg(input, max, base); + } + + final (String formattedMax, String formattedInput) = + formatBoth(max, input, base); + + return FormBuilderLocalizations.current + .fileSizeErrorText(formattedMax, formattedInput); + }; +} + +/// Helper function to format bytes into a human-readable string (e.g., KB, MB, GB) +/// for both v1 and v2 in such a way that their representation will be different. +/// +/// ## Error +/// - `ArgumentError`: throws `ArgumentError` when v1 == v2. +(String v1, String v2) formatBoth(int v1, int v2, Base b) { + if (v1 == v2) { + throw ArgumentError.value( + v2, 'input', "'input' must be different from 'max'"); + } + assert(v1 != v2); + final int base = b.base; + final List units = b.units; + final List formattingMask = [ + NumberFormat("#,##0.#"), + NumberFormat("#,##0,000.#"), + NumberFormat("#,##0,000,000.#"), + NumberFormat("#,##0,000,000,000.#"), + NumberFormat("#,##0,000,000,000,000.#"), + ]; + + // format max + final int v1DigitGroups = (log(v1) / log(base)).floor(); + final double v1Size = v1 / pow(base, v1DigitGroups); + + // format input + final int v2DigitGroups = (log(v2) / log(base)).floor(); + final double v2Size = v2 / pow(base, v2DigitGroups); + String formattedV1; + String formattedV2; + for (final (int i, NumberFormat formatMask) in formattingMask.indexed) { + formattedV1 = + "${formatMask.format(v1Size * pow(base, i))} ${units[v1DigitGroups - i]}"; + formattedV2 = + "${formatMask.format(v2Size * pow(base, i))} ${units[v2DigitGroups - i]}"; + if (formattedV1 != formattedV2) { + return (formattedV1, formattedV2); + } + } + throw StateError( + "Unable to format 'max' and 'input' to distinct string representations despite having different values (max=$v1, input=$v2)"); +} + +/// Helper function to format bytes into a human-readable string (e.g., KB, MB, GB). +/// +/// ## Parameters: +/// - [bytes] The size in bytes to be formatted. +/// +/// ## Returns: +/// A formatted string representing the size in human-readable units. +String formatBytes(int bytes, Base b) { + double log10(num x) => log(x) / ln10; + if (bytes <= 0) return '0 B'; + final int base = b.base; + final List units = b.units; + final int digitGroups = (log10(bytes) / log10(base)).floor(); + final double size = bytes / pow(base, digitGroups); + + return "${NumberFormat("#,##0.#").format(size)} ${units[digitGroups]}"; +} diff --git a/lib/src/validators/finance_validators.dart b/lib/src/validators/finance_validators.dart index 1fc11f0b..3f924ce2 100644 --- a/lib/src/validators/finance_validators.dart +++ b/lib/src/validators/finance_validators.dart @@ -18,6 +18,32 @@ Validator creditCard({ }; } +/// {@macro validator_bic} +Validator bic({ + bool Function(String input)? isBic, + String Function(String input)? bicMsg, +}) { + return (String input) { + return (isBic?.call(input) ?? _isBIC(input)) + ? null + : (bicMsg?.call(input) ?? + FormBuilderLocalizations.current.bicErrorText); + }; +} + +/// {@macro validator_iban} +Validator iban({ + bool Function(String input)? isIban, + String Function(String input)? ibanMsg, +}) { + return (String input) { + return isIban?.call(input) ?? _isIBAN(input) + ? null + : (ibanMsg?.call(input) ?? + FormBuilderLocalizations.current.ibanErrorText); + }; +} + //****************************************************************************** //* Aux functions * //****************************************************************************** @@ -51,3 +77,50 @@ bool _isCreditCard(String value, RegExp regex) { return (sum % 10 == 0); } + +/// Check if the string is a valid BIC string. +/// +/// ## Parameters: +/// - [value] The string to be evaluated. +/// +/// ## Returns: +/// A boolean indicating whether the value is a valid BIC. +bool _isBIC(String value) { + final String bic = value.replaceAll(' ', '').toUpperCase(); + final RegExp regex = RegExp(r'^[A-Z]{4}[A-Z]{2}\w{2}(\w{3})?$'); + + if (bic.length != 8 && bic.length != 11) { + return false; + } + + return regex.hasMatch(bic); +} + +/// Check if the string is a valid IBAN. +bool _isIBAN(String value) { + final String iban = value.replaceAll(' ', '').toUpperCase(); + + if (iban.length < 15) { + return false; + } + + final String rearranged = iban.substring(4) + iban.substring(0, 4); + final String numericIban = rearranged.split('').map((String char) { + final int charCode = char.codeUnitAt(0); + return charCode >= 65 && charCode <= 90 ? (charCode - 55).toString() : char; + }).join(); + + int remainder = int.parse(numericIban.substring(0, 9)) % 97; + for (int i = 9; i < numericIban.length; i += 7) { + remainder = int.parse( + remainder.toString() + + numericIban.substring( + i, + i + 7 < numericIban.length ? i + 7 : numericIban.length, + ), + ) % + 97; + } + + return remainder == 1; +} diff --git a/lib/src/validators/generic_type_validators.dart b/lib/src/validators/generic_type_validators.dart index 8234c1de..9789f191 100644 --- a/lib/src/validators/generic_type_validators.dart +++ b/lib/src/validators/generic_type_validators.dart @@ -1,10 +1,10 @@ import '../../localization/l10n.dart'; import 'constants.dart'; -/// {@macro validator_contains_element} -Validator containsElement( +/// {@macro validator_in_list} +Validator inList( List values, { - String Function(T input, List values)? containsElementMsg, + String Function(T input, List values)? inListMsg, }) { if (values.isEmpty) { throw ArgumentError.value( @@ -14,11 +14,29 @@ Validator containsElement( return (T input) { return setOfValues.contains(input) ? null - : containsElementMsg?.call(input, values) ?? + : inListMsg?.call(input, values) ?? FormBuilderLocalizations.current.containsElementErrorText; }; } +/// {@macro validator_not_in_list} +Validator notInList( + List values, { + String Function(T input, List values)? notInListMsg, +}) { + if (values.isEmpty) { + throw ArgumentError.value( + '[]', 'values', 'The list of values must not be empty'); + } + final Set setOfValues = values.toSet(); + return (T input) { + return !setOfValues.contains(input) + ? null + : notInListMsg?.call(input, values) ?? + FormBuilderLocalizations.current.doesNotContainElementErrorText; + }; +} + /// {@macro validator_is_true} Validator isTrue( {String Function(T input)? isTrueMsg, @@ -39,7 +57,7 @@ Validator isTrue( }; } -/// {@macro validator_is_false} +/// {@macro validator_false} Validator isFalse( {String Function(T input)? isFalseMsg, bool caseSensitive = false, @@ -73,3 +91,16 @@ Validator isFalse( } return (false, null); } + +/// {@macro validator_satisfy} +Validator satisfy( + bool Function(T) condition, { + String Function(T input)? satisfyMsg, +}) { + return (T input) { + return condition(input) + ? null + : satisfyMsg?.call(input) ?? + FormBuilderLocalizations.current.satisfyErrorText; + }; +} diff --git a/lib/src/validators/miscellaneous_validators.dart b/lib/src/validators/miscellaneous_validators.dart new file mode 100644 index 00000000..760ac9a7 --- /dev/null +++ b/lib/src/validators/miscellaneous_validators.dart @@ -0,0 +1,215 @@ +import '../../form_builder_validators.dart'; + +/// {@macro validator_color_code} +Validator colorCode({ + Set formats = const { + ColorFormat.hex, + ColorFormat.rgb, + ColorFormat.hsl, + }, + bool Function(String)? customColorCode, + String Function(String input)? colorCodeMsg, +}) { + if (formats.isEmpty) { + throw ArgumentError.value( + '[]', 'formats', 'The set of formats allowed must not be empty'); + } + + return (String input) { + return customColorCode?.call(input) ?? _isColorCode(input, formats: formats) + ? null + : colorCodeMsg?.call(input) ?? + FormBuilderLocalizations.current + .colorCodeErrorText(_toStringFormats(formats)); + }; +} + +/// {@macro validator_isbn} +Validator isbn({ + String Function(String input)? isbnMsg, +}) { + return (String input) { + return _isISBN(input) + ? null + : isbnMsg?.call(input) ?? + FormBuilderLocalizations.current.isbnErrorText; + }; +} + +// Aux +/// {@template color_format} +/// Enumeration defining the supported color format types for color code validation. +/// This enum provides a standardized way to specify which color formats should be +/// accepted during validation operations. +/// +/// The enum supports three primary web-standard color formats: +/// - Hexadecimal notation (e.g., #FF5733) +/// - RGB functional notation (e.g., rgb(255, 87, 51)) +/// - HSL functional notation (e.g., hsl(14, 100%, 60%)) +/// +/// +/// ## Examples +/// ```dart +/// // Using individual formats +/// final hexOnly = {ColorFormat.Hex}; +/// final rgbOnly = {ColorFormat.Rgb}; +/// +/// // Using multiple formats +/// final webFormats = {ColorFormat.Hex, ColorFormat.Rgb}; +/// final allFormats = {ColorFormat.Hex, ColorFormat.Rgb, ColorFormat.Hsl}; +/// ``` +/// {@endtemplate} +enum ColorFormat { + /// Hexadecimal color format using # prefix followed by 6 hex digits + hex, + + /// RGB color format using rgb() functional notation with comma-separated values + rgb, + + /// HSL color format using hsl() functional notation with hue, saturation, and lightness + hsl; + + String get _displayName { + switch (this) { + case ColorFormat.hex: + return 'HEX'; + case ColorFormat.rgb: + return 'RGB'; + case ColorFormat.hsl: + return 'HSL'; + } + } +} + +String _toStringFormats(Set formats) { + List formatsBuilder = []; + for (final ColorFormat f in formats) { + formatsBuilder.add(f._displayName); + } + return formatsBuilder.join(', '); +} + +/// {@template hex_template} +/// This regex matches hexadecimal color codes. +/// +/// - It starts with a # character. +/// - It is followed by exactly six characters, each of which is a hexadecimal digit (0-9, a-f, or A-F). +/// +/// Examples: #1a2b3c, #ABCDEF +/// {@endtemplate} +final RegExp _hex = RegExp(r'^#[0-9a-fA-F]{6}$'); + +/// {@template rgb_template} +/// This regex matches RGB color values. +/// +/// - It checks for the rgb() format. +/// - It allows up to three digits for each color value (0-255). +/// +/// Examples: rgb(255, 0, 0), rgb(123, 123, 123) +/// {@endtemplate} +final RegExp _rgb = RegExp(r'^rgb\(\d{1,3},\s*\d{1,3},\s*\d{1,3}\)$'); + +/// {@template hsl_template} +/// This regex matches HSL color values. +/// +/// - It checks for the hsl() format. +/// - It allows integers for hue and percentages for saturation and lightness. +/// +/// Examples: hsl(360, 100%, 50%), hsl(120, 75%, 25%) +/// {@endtemplate} +final RegExp _hsl = RegExp( + r'^hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\)$', +); + +/// Checks if the string is a valid color code in specified formats. +/// +/// ## Parameters: +/// - [value] The string to be checked. +/// - [formats] The list of color formats to check against. +/// +/// ## Returns: +/// A boolean indicating whether the string is a valid color code. +bool _isColorCode( + String value, { + required Set formats, +}) { + for (final ColorFormat format in formats) { + switch (format) { + case ColorFormat.hex: + if (_hex.hasMatch(value)) { + return true; + } + case ColorFormat.rgb: + if (_rgb.hasMatch(value)) { + final List parts = + value.substring(4, value.length - 1).split(','); + for (final String part in parts) { + final int colorValue = int.tryParse(part.trim()) ?? -1; + if (colorValue < 0 || colorValue > 255) { + return false; + } + } + return true; + } + + case ColorFormat.hsl: + if (_hsl.hasMatch(value)) { + final List parts = + _hsl.firstMatch(value)!.groups([1, 2, 3]); + final int hue = int.tryParse(parts[0]!) ?? -1; + final int saturation = int.tryParse(parts[1]!) ?? -1; + final int lightness = int.tryParse(parts[2]!) ?? -1; + if (hue < 0 || + hue > 360 || + saturation < 0 || + saturation > 100 || + lightness < 0 || + lightness > 100) { + return false; + } + return true; + } + } + } + return false; +} + +/// Checks if the string is a valid ISBN (either ISBN-10 or ISBN-13). +/// +/// ## Parameters: +/// - [valueCandidate] The string to be checked. +/// +/// ## Returns: +/// A boolean indicating whether the string is a valid ISBN. +bool _isISBN(String valueCandidate) { + final String isbn = valueCandidate.replaceAll('-', '').replaceAll(' ', ''); + + if (isbn.length == 10) { + if (!RegExp(r'^\d{9}[\dX]$').hasMatch(isbn)) return false; + + int sum = 0; + for (int i = 0; i < 9; i++) { + sum += int.parse(isbn[i]) * (10 - i); + } + + final int checkDigit = (isbn[9] == 'X') ? 10 : int.parse(isbn[9]); + sum += checkDigit; + + return sum % 11 == 0; + } else if (isbn.length == 13) { + if (!RegExp(r'^\d{13}$').hasMatch(isbn)) return false; + + int sum = 0; + for (int i = 0; i < 12; i++) { + final int digit = int.parse(isbn[i]); + sum += (i.isEven) ? digit : digit * 3; + } + + final int checkDigit = int.parse(isbn[12]); + final int calculatedCheckDigit = (10 - (sum % 10)) % 10; + + return checkDigit == calculatedCheckDigit; + } else { + return false; + } +} diff --git a/lib/src/validators/network_validators.dart b/lib/src/validators/network_validators.dart index 0f44cf74..6a8007aa 100644 --- a/lib/src/validators/network_validators.dart +++ b/lib/src/validators/network_validators.dart @@ -85,6 +85,22 @@ Validator url({ }; } +/// {@macro validator_mac_address} +Validator macAddress({ + bool Function(String)? isMacAddress, + String Function(String input)? macAddressMsg, +}) { + final RegExp defaultRegex = RegExp( + r'^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$|^([0-9A-Fa-f]{4}\.){2}([0-9A-Fa-f]{4})$', + ); + return (String input) { + return (isMacAddress?.call(input) ?? defaultRegex.hasMatch(input)) + ? null + : (macAddressMsg?.call(input) ?? + FormBuilderLocalizations.current.macAddressErrorText); + }; +} + //****************************************************************************** //* Aux functions * //****************************************************************************** diff --git a/lib/src/validators/string_validators.dart b/lib/src/validators/string_validators.dart index 8ad6ab93..5e27c592 100644 --- a/lib/src/validators/string_validators.dart +++ b/lib/src/validators/string_validators.dart @@ -113,11 +113,11 @@ Validator hasMinSpecialChars({ /// {@macro validator_match} Validator match( - RegExp regex, { + RegExp regExp, { String Function(String input)? matchMsg, }) { return (String input) { - return regex.hasMatch(input) + return regExp.hasMatch(input) ? null : matchMsg?.call(input) ?? FormBuilderLocalizations.current.matchErrorText; diff --git a/lib/src/validators/validators.dart b/lib/src/validators/validators.dart index a1f57f02..6bf748c6 100644 --- a/lib/src/validators/validators.dart +++ b/lib/src/validators/validators.dart @@ -2,8 +2,10 @@ 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 'miscellaneous_validators.dart'; export 'network_validators.dart'; export 'numeric_validators.dart'; export 'path_validators.dart'; diff --git a/test/src/validators/core_validators/equality_validators/is_equal_validator_test.dart b/test/src/validators/core_validators/equality_validators/equal_validator_test.dart similarity index 94% rename from test/src/validators/core_validators/equality_validators/is_equal_validator_test.dart rename to test/src/validators/core_validators/equality_validators/equal_validator_test.dart index b2fce6ec..0c133096 100644 --- a/test/src/validators/core_validators/equality_validators/is_equal_validator_test.dart +++ b/test/src/validators/core_validators/equality_validators/equal_validator_test.dart @@ -7,7 +7,7 @@ class _CustomClass {} void main() { final _CustomClass myObject = _CustomClass(); - group('Validator: isEqual', () { + group('Validator: equal', () { final List< ({ String description, @@ -85,7 +85,7 @@ void main() { testFails: bool testFails ) in testCases) { test(desc, () { - final Validator v = isEqual(referenceValue); + final Validator v = equal(referenceValue); expect( v(userInput), @@ -100,7 +100,7 @@ void main() { const String ref = 'hello'; const String customErrorMessage = 'custom error'; final Validator v = - isEqual(ref, isEqualMsg: (_, __) => customErrorMessage); + equal(ref, equalMsg: (_, __) => customErrorMessage); // success expect(v(ref), isNull); diff --git a/test/src/validators/core_validators/equality_validators/is_not_equal_validator_test.dart b/test/src/validators/core_validators/equality_validators/not_equal_validator_test.dart similarity index 94% rename from test/src/validators/core_validators/equality_validators/is_not_equal_validator_test.dart rename to test/src/validators/core_validators/equality_validators/not_equal_validator_test.dart index 2f844bd2..3ba03607 100644 --- a/test/src/validators/core_validators/equality_validators/is_not_equal_validator_test.dart +++ b/test/src/validators/core_validators/equality_validators/not_equal_validator_test.dart @@ -7,7 +7,7 @@ class _CustomClass {} void main() { final _CustomClass myObject = _CustomClass(); - group('Validator: isNoEqual', () { + group('Validator: notEqual', () { final List< ({ String description, @@ -88,7 +88,7 @@ void main() { testFails: bool testFails ) in testCases) { test(desc, () { - final Validator v = isNotEqual(referenceValue); + final Validator v = notEqual(referenceValue); expect( v(userInput), @@ -103,7 +103,7 @@ void main() { const String ref = 'hello'; const String customErrorMessage = 'custom error'; final Validator v = - isNotEqual(ref, isNotEqualMsg: (_, __) => customErrorMessage); + notEqual(ref, notEqualMsg: (_, __) => customErrorMessage); // success expect(v(123), isNull); diff --git a/test/src/validators/core_validators/required_validators/is_optional_validator_test.dart b/test/src/validators/core_validators/required_validators/optional_validator_test.dart similarity index 80% rename from test/src/validators/core_validators/required_validators/is_optional_validator_test.dart rename to test/src/validators/core_validators/required_validators/optional_validator_test.dart index 096f7e71..5249530f 100644 --- a/test/src/validators/core_validators/required_validators/is_optional_validator_test.dart +++ b/test/src/validators/core_validators/required_validators/optional_validator_test.dart @@ -11,9 +11,9 @@ void main() { final String defaultError = FormBuilderLocalizations.current.isOptionalErrorText(errorMultBy6); - group('Validator: isOptional', () { + group('Validator: optional', () { test('Should make the input optional', () { - final Validator v = isOptional(); + final Validator v = optional(); expect(v(null), isNull); expect(v(''), isNull); @@ -25,7 +25,7 @@ void main() { }); test('Should make the input optional with composed validator `v`', () { - final Validator v = isOptional(isMultipleBy6); + final Validator v = optional(isMultipleBy6); expect(v(null), isNull); expect(v(0), isNull); @@ -36,9 +36,8 @@ void main() { test('Should return custom message for invalid input', () { const String customMsg = 'custom error message '; - final Validator v = isOptional(null, (_, __) => customMsg); - final Validator v1 = - isOptional(isMultipleBy6, (_, __) => customMsg); + final Validator v = optional(null, (_, __) => customMsg); + final Validator v1 = optional(isMultipleBy6, (_, __) => customMsg); expect(v(null), isNull); expect(v(''), isNull); diff --git a/test/src/validators/core_validators/required_validators/is_required_validator_test.dart b/test/src/validators/core_validators/required_validators/required_validator_test.dart similarity index 83% rename from test/src/validators/core_validators/required_validators/is_required_validator_test.dart rename to test/src/validators/core_validators/required_validators/required_validator_test.dart index 2b0727fb..966889e1 100644 --- a/test/src/validators/core_validators/required_validators/is_required_validator_test.dart +++ b/test/src/validators/core_validators/required_validators/required_validator_test.dart @@ -11,9 +11,9 @@ void main() { final String defaultError = FormBuilderLocalizations.current.requiredErrorText; - group('Validator: isRequired', () { + group('Validator: required', () { test('Should check if the input value is not null/empty', () { - final Validator v = isRequired(); + final Validator v = required(); expect(v(null), defaultError); expect(v(''), defaultError); @@ -27,7 +27,7 @@ void main() { test( 'Should check if the input value is not null/empty with composed validator `v`', () { - final Validator v = isRequired(isMultipleBy6); + final Validator v = required(isMultipleBy6); expect(v(null), equals(defaultError)); expect(v(0), isNull); @@ -38,8 +38,8 @@ void main() { test('Should return custom message for null input', () { const String customMsg = 'custom error message '; - final Validator v = isRequired(null, customMsg); - final Validator v1 = isRequired(isMultipleBy6, customMsg); + final Validator v = required(null, customMsg); + final Validator v1 = required(isMultipleBy6, customMsg); expect(v(null), equals(customMsg)); expect(v(''), equals(customMsg)); diff --git a/test/src/validators/core_validators/type_validators_test.dart b/test/src/validators/core_validators/type_validators_test.dart index 0b73b806..826993c3 100644 --- a/test/src/validators/core_validators/type_validators_test.dart +++ b/test/src/validators/core_validators/type_validators_test.dart @@ -12,9 +12,9 @@ void main() { String? isLaterThan1995(DateTime input) => input.year > 1995 ? null : errorMsg; - group('Validator: isString', () { + group('Validator: string', () { test('Should only check if the input is a String', () { - final Validator v = isString(); + final Validator v = string(); expect( v(123), equals(FormBuilderLocalizations.current.isStringErrorText)); @@ -24,7 +24,7 @@ void main() { }); test('Should check if the input is a String with length greater than 3', () { - final Validator v = isString(hasLengthGreaterThan3); + final Validator v = string(hasLengthGreaterThan3); expect( v(123), equals(FormBuilderLocalizations.current.isStringErrorText)); @@ -34,7 +34,7 @@ void main() { }); test('Should check if the input is a String with using custom error', () { const String customError = 'custom error'; - final Validator v = isString(null, (_) => customError); + final Validator v = string(null, (_) => customError); expect(v(123), equals(customError)); expect(v('1234'), isNull); @@ -230,10 +230,10 @@ void main() { }); }); - group('Validator: isDateTime', () { + group('Validator: dateTime', () { test('Should only check if the input is an DateTime/parsable to DateTime', () { - final Validator v = isDateTime(); + final Validator v = dateTime(); expect(v('not an DateTime'), equals(FormBuilderLocalizations.current.dateTimeErrorText)); @@ -318,7 +318,7 @@ void main() { }); test('Should check if the input is a DateTime with year later than 1995', () { - final Validator v = isDateTime(isLaterThan1995); + final Validator v = dateTime(isLaterThan1995); expect(v('not a datetime'), equals(FormBuilderLocalizations.current.dateTimeErrorText)); @@ -327,7 +327,7 @@ void main() { test('Should check if the input is a DateTime using custom error', () { const String customError = 'custom error'; - final Validator v = isDateTime(null, (_) => customError); + final Validator v = dateTime(null, (_) => customError); expect(v('not datetime'), equals(customError)); expect(v('1289-02-12'), isNull); diff --git a/test/src/validators/datetime_validators/is_after_validator_test.dart b/test/src/validators/datetime_validators/after_validator_test.dart similarity index 94% rename from test/src/validators/datetime_validators/is_after_validator_test.dart rename to test/src/validators/datetime_validators/after_validator_test.dart index a833f816..a392daa5 100644 --- a/test/src/validators/datetime_validators/is_after_validator_test.dart +++ b/test/src/validators/datetime_validators/after_validator_test.dart @@ -7,7 +7,7 @@ void main() { setUpAll(() async { await initializeDateFormatting('en', null); }); - group('Validator: isAfter', () { + group('Validator: after', () { test('Validation for the year 1994', () { final DateTime reference = DateTime(1994); final DateTime eq = reference.copyWith(); @@ -16,7 +16,7 @@ void main() { final DateTime before1Year = DateTime(1993); final DateTime before1Sec = reference.subtract(const Duration(seconds: 1)); - final Validator v = isAfter(reference); + final Validator v = after(reference); final String errorMsg = FormBuilderLocalizations.current.dateMustBeAfterErrorText(reference); @@ -56,7 +56,7 @@ void main() { final DateTime before1Year = reference.copyWith(year: 2088); final DateTime before1Sec = reference.subtract(const Duration(seconds: 1)); - final Validator v = isAfter(reference, inclusive: true); + final Validator v = after(reference, inclusive: true); final String errorMsg = FormBuilderLocalizations.current.dateMustBeAfterErrorText(reference); @@ -93,7 +93,7 @@ void main() { const String errorMsg = 'error msg'; final DateTime reference = DateTime(2); final Validator v = - isAfter(reference, isAfterMsg: (_, __) => errorMsg); + after(reference, afterMsg: (_, __) => errorMsg); expect( v(reference.copyWith()), diff --git a/test/src/validators/datetime_validators/is_before_validator_test.dart b/test/src/validators/datetime_validators/before_validator_test.dart similarity index 93% rename from test/src/validators/datetime_validators/is_before_validator_test.dart rename to test/src/validators/datetime_validators/before_validator_test.dart index 219f3111..3b7d4f73 100644 --- a/test/src/validators/datetime_validators/is_before_validator_test.dart +++ b/test/src/validators/datetime_validators/before_validator_test.dart @@ -7,7 +7,7 @@ void main() { setUpAll(() async { await initializeDateFormatting('en', null); }); - group('Validator: isBefore', () { + group('Validator: before', () { test('Validation for the year 1994', () { final DateTime reference = DateTime(1994); final DateTime eq = reference.copyWith(); @@ -16,7 +16,7 @@ void main() { final DateTime before1Year = DateTime(1993); final DateTime before1Sec = reference.subtract(const Duration(seconds: 1)); - final Validator v = isBefore(reference); + final Validator v = before(reference); final String errorMsg = FormBuilderLocalizations.current.dateMustBeBeforeErrorText(reference); @@ -56,7 +56,7 @@ void main() { final DateTime before1Year = reference.copyWith(year: 2088); final DateTime before1Sec = reference.subtract(const Duration(seconds: 1)); - final Validator v = isBefore(reference, inclusive: true); + final Validator v = before(reference, inclusive: true); final String errorMsg = FormBuilderLocalizations.current.dateMustBeBeforeErrorText(reference); @@ -93,7 +93,7 @@ void main() { const String errorMsg = 'error msg'; final DateTime reference = DateTime(2); final Validator v = - isBefore(reference, isBeforeMsg: (_, __) => errorMsg); + before(reference, beforeMsg: (_, __) => errorMsg); expect( v(reference.copyWith()), diff --git a/test/src/validators/datetime_validators/is_datetime_between_validator_test.dart b/test/src/validators/datetime_validators/datetime_between_validator_test.dart similarity index 94% rename from test/src/validators/datetime_validators/is_datetime_between_validator_test.dart rename to test/src/validators/datetime_validators/datetime_between_validator_test.dart index d478798a..a944dcec 100644 --- a/test/src/validators/datetime_validators/is_datetime_between_validator_test.dart +++ b/test/src/validators/datetime_validators/datetime_between_validator_test.dart @@ -7,7 +7,7 @@ void main() { setUpAll(() async { await initializeDateFormatting('en', null); }); - group('Validator: isDateTimeBetween', () { + group('Validator: dateTimeBetween', () { test('Validation for the range 1994 and 1997', () { final DateTime leftReference = DateTime(1994); final DateTime rightReference = DateTime(1997); @@ -25,7 +25,7 @@ void main() { final DateTime beforeLeft1Micro = leftReference.subtract(const Duration(microseconds: 1)); final Validator v = - isDateTimeBetween(leftReference, rightReference); + betweenDateTime(leftReference, rightReference); final String errorMsg = FormBuilderLocalizations.current .dateMustBeBetweenErrorText(leftReference, rightReference); @@ -91,7 +91,7 @@ void main() { final DateTime before1Sec = leftReference.subtract(const Duration(seconds: 1)); final Validator v = - isDateTimeBetween(leftReference, rightReference, minInclusive: true); + betweenDateTime(leftReference, rightReference, minInclusive: true); final String errorMsg = FormBuilderLocalizations.current .dateMustBeBetweenErrorText(leftReference, rightReference); @@ -145,9 +145,9 @@ void main() { const String errorMsg = 'error msg'; final DateTime leftReference = DateTime(2); final DateTime rightReference = DateTime(5); - final Validator v = isDateTimeBetween( + final Validator v = betweenDateTime( leftReference, rightReference, - isDateTimeBetweenMsg: (_, __, ___) => errorMsg); + betweenDateTimeMsg: (_, __, ___) => errorMsg); expect( v(rightReference.copyWith()), @@ -173,7 +173,7 @@ void main() { 'Should throw AssertionError when the right reference is not after left reference', () { expect( - () => isDateTimeBetween( + () => betweenDateTime( DateTime(1990, 12, 23, 20), DateTime(1990, 12, 22, 20)), throwsAssertionError); }); diff --git a/test/src/validators/file_validators/max_file_size_validator_test.dart b/test/src/validators/file_validators/max_file_size_validator_test.dart new file mode 100644 index 00000000..39c5ef0d --- /dev/null +++ b/test/src/validators/file_validators/max_file_size_validator_test.dart @@ -0,0 +1,269 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; +import 'package:intl/intl.dart'; + +void main() { + final List formattingMask = [ + NumberFormat("#,##0.#"), + NumberFormat("#,##0,000.#"), + NumberFormat("#,##0,000,000.#"), + NumberFormat("#,##0,000,000,000.#"), + NumberFormat("#,##0,000,000,000,000.#"), + ]; + group('Validator: maxFileSize', () { + final List< + ({ + Base base, + ({String input, String max})? expected, + int input, + int max + })> testCases = <({ + int max, + int input, + Base base, + ({String input, String max})? expected + })>[ + ( + max: 1000, + input: 1000, + base: Base.b1000, + expected: null, + ), + ( + max: 1024, + input: 1024, + base: Base.b1024, + expected: null, + ), + ( + max: 1024, + input: 512, + base: Base.b1024, + expected: null, + ), + ( + max: 1024, + input: 1025, + base: Base.b1024, + expected: ( + max: '${formattingMask[1].format(1024)} B', + input: '${formattingMask[1].format(1025)} B' + ), + ), + ( + max: 1024 * 1024, + input: 1025 * 1024, + base: Base.b1024, + expected: ( + max: '${formattingMask[1].format(1024)} KiB', + input: '${formattingMask[1].format(1025)} KiB' + ), + ), + ( + max: 5_000_000_000_000, + input: 5_000_000_000_234, + base: Base.b1000, + expected: ( + max: '${formattingMask[3].format(5_000_000_000.0)} KB', + input: '${formattingMask[3].format(5_000_000_000.2)} KB' + ), + ), + ( + max: 5_000_000_000_234, + input: 5_000_000_000_234, + base: Base.b1000, + expected: null, + ), + ( + max: 5_000_000_000_234, + input: 5_000_000_000_233, + base: Base.b1000, + expected: null, + ), + ( + max: 5_000_000_000_000, + input: 5_000_000_000_034, + base: Base.b1000, + expected: ( + max: '${formattingMask[4].format(5_000_000_000_000)} B', + input: '${formattingMask[4].format(5_000_000_000_034)} B' + ), + ), + ]; + + for (final ( + max: int max, + input: int input, + base: Base base, + expected: ({String input, String max})? expected + ) in testCases) { + test( + 'should ${expected == null ? 'match' : 'return error'} when max is $max B, input is $input B, and base ${base.base}', + () { + expect( + maxFileSize(max, base: base)(input), + expected == null + ? isNull + : stringContainsInOrder( + [expected.max, expected.input])); + }); + } + + test('should return the custom error msg', () { + final Validator v = maxFileSize(12, + maxFileSizeMsg: (_, int max, __) => + 'File size should be less than $max'); + + expect(v(10), isNull); + expect(v(12), isNull); + expect(v(13), equals('File size should be less than 12')); + }); + }); + + group('Helper function: formatBoth', () { + final List<({Base base, (String, String) expected, int v1, int v2})> + testCases = <({Base base, (String, String) expected, int v1, int v2})>[ + ( + v1: 1024, + v2: 1025, + base: Base.b1000, + expected: ( + '${formattingMask[1].format(1024)} B', + '${formattingMask[1].format(1025)} B' + ) + ), + ( + v1: 1024, + v2: 1025, + base: Base.b1024, + expected: ( + '${formattingMask[1].format(1024)} B', + '${formattingMask[1].format(1025)} B' + ) + ), + ( + v1: 1024, + v2: 1023, + base: Base.b1024, + expected: ( + '${formattingMask[0].format(1.0)} KiB', + '${formattingMask[1].format(1023)} B' + ) + ), + (v1: 1, v2: 2, base: Base.b1000, expected: ('1 B', '2 B')), + (v1: 1, v2: 2, base: Base.b1024, expected: ('1 B', '2 B')), + ( + v1: 10_000, + v2: 12_123, + base: Base.b1000, + expected: ( + '${formattingMask[0].format(10.0)} KB', + '${formattingMask[0].format(12.1)} KB' + ) + ), + ( + v1: 10_000, + v2: 12_123, + base: Base.b1024, + expected: ( + '${formattingMask[0].format(9.8)} KiB', + '${formattingMask[0].format(11.8)} KiB' + ) + ), + ( + v1: 12_124_000, + v2: 12_123_000, + base: Base.b1000, + expected: ( + '${formattingMask[1].format(12_124.0)} KB', + '${formattingMask[1].format(12_123.0)} KB' + ) + ), + ( + v1: 12_124_000, + v2: 12_123_000, + base: Base.b1024, + expected: ( + '${formattingMask[1].format(11_839.84)} KiB', + '${formattingMask[1].format(11_838.87)} KiB' + ) + ), + ( + v1: 12_100_000, + v2: 12_199_999, + base: Base.b1000, + expected: ( + '${formattingMask[0].format(12.1)} MB', + '${formattingMask[0].format(12.2)} MB' + ) + ), + ( + v1: 12_100_000, + v2: 12_199_999, + base: Base.b1024, + expected: ( + '${formattingMask[0].format(11.53)} MiB', + '${formattingMask[0].format(11.63)} MiB' + ) + ), + ( + v1: 9_100_000_000, + v2: 9_100_300_001, + base: Base.b1000, + expected: ( + '${formattingMask[1].format(9_100.0)} MB', + '${formattingMask[1].format(9_100.3)} MB' + ) + ), + ( + v1: 9_100_000_000, + v2: 9_100_000_001, + base: Base.b1024, + expected: ( + '${formattingMask[3].format(9_100_000_000)} B', + '${formattingMask[3].format(9_100_000_001)} B' + ) + ), + ( + v1: 999_231_665_990_003, + v2: 999_231_665_990_004, + base: Base.b1000, + expected: ( + '${formattingMask[4].format(999_231_665_990_003)} B', + '${formattingMask[4].format(999_231_665_990_004)} B' + ) + ), + ( + v1: 999_231_665_990_003, + v2: 999_231_665_990_004, + base: Base.b1024, + expected: ( + '${formattingMask[4].format(999_231_665_990_003)} B', + '${formattingMask[4].format(999_231_665_990_004)} B' + ) + ), + ( + v1: 12 * 1024 * 1024, + v2: 23 * 1024, + base: Base.b1024, + expected: ( + '${formattingMask[0].format(12)} MiB', + '${formattingMask[0].format(23)} KiB' + ) + ), + ]; + + for (final ( + v1: int v1, + v2: int v2, + base: Base base, + expected: (String, String) expected + ) in testCases) { + test( + 'should return $expected when v1:$v1, v2:$v2, and base: ${base.base}', + () { + expect(formatBoth(v1, v2, base).toString(), expected.toString()); + }); + } + }); +} diff --git a/test/src/validators/finance_validators/bic_validator_test.dart b/test/src/validators/finance_validators/bic_validator_test.dart new file mode 100644 index 00000000..c9334890 --- /dev/null +++ b/test/src/validators/finance_validators/bic_validator_test.dart @@ -0,0 +1,50 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/finance_validators.dart'; + +void main() { + group('Validator: bic', () { + final List<({bool fails, String input})> testCases = + <({bool fails, String input})>[ + ( + input: '', + fails: true, + ), + ( + input: 'DEUTDEFF', + fails: false, + ), + ( + input: 'DEUTDEFF500', + fails: false, + ), + ( + input: 'INVALIDBIC', + fails: true, + ), + ]; + + final Validator v = bic(); + for (final (input: String input, fails: bool fails) in testCases) { + test('should ${fails ? 'fail' : 'pass'} for input "$input"', () { + expect(v(input), + fails ? FormBuilderLocalizations.current.bicErrorText : isNull); + }); + } + + test('should return custom error msg', () { + final Validator v = bic(bicMsg: (_) => 'error text'); + + expect(v('BOTKJPJTXXX'), isNull); + expect(v('BOTKJPJTXX'), equals('error text')); + }); + + test('should use the custom logic to check if the input is a valid bic', + () { + final Validator v = + bic(isBic: (String input) => input.length == 3); + expect(v('abc'), isNull); + expect(v('abc '), equals(FormBuilderLocalizations.current.bicErrorText)); + }); + }); +} diff --git a/test/src/validators/finance_validators/iban_validator_test.dart b/test/src/validators/finance_validators/iban_validator_test.dart new file mode 100644 index 00000000..f7cfa960 --- /dev/null +++ b/test/src/validators/finance_validators/iban_validator_test.dart @@ -0,0 +1,105 @@ +import 'package:faker_dart/faker_dart.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/localization/l10n.dart'; +import 'package:form_builder_validators/src/validators/constants.dart'; +import 'package:form_builder_validators/src/validators/finance_validators.dart'; + +void main() { + final Faker faker = Faker.instance; + final String customErrorMessage = faker.lorem.sentence(); + + group('IbanValidator -', () { + test('should return null if the IBAN is valid', () { + // Arrange + final Validator validator = iban(); + const String validIban = 'DE89370400440532013000'; + + // Act + final String? result = validator(validIban); + + // Assert + expect(result, isNull); + }); + + test('should return null if the IBAN is valid with spaces', () { + // Arrange + final Validator validator = iban(); + const String validIban = 'DE89 3704 0044 0532 0130 00'; + + // Act + final String? result = validator(validIban); + + // Assert + expect(result, isNull); + }); + + test('should return the default error message if the IBAN is invalid', () { + // Arrange + final Validator validator = iban(); + const String invalidIban = 'DE89370400440532013001'; + + // Act + final String? result = validator(invalidIban); + + // Assert + expect(result, equals(FormBuilderLocalizations.current.ibanErrorText)); + }); + + test( + 'should return the custom error message if the IBAN Validator is invalid', + () { + // Arrange + final Validator validator = + iban(ibanMsg: (_) => customErrorMessage); + const String invalidIban = 'DE89370400440532013001'; + + // Act + final String? result = validator(invalidIban); + + // Assert + expect(result, equals(customErrorMessage)); + }); + + test( + 'should return the default error message if the value is an empty string', + () { + // Arrange + final Validator validator = iban(); + const String value = ''; + + // Act + final String? result = validator(value); + + // Assert + expect(result, equals(FormBuilderLocalizations.current.ibanErrorText)); + }); + + test( + 'should return the default error message if the IBAN length is less than 15 characters', + () { + // Arrange + final Validator validator = iban(); + const String shortIban = 'DE8937040044'; + + // Act + final String? result = validator(shortIban); + + // Assert + expect(result, equals(FormBuilderLocalizations.current.ibanErrorText)); + }); + + test( + 'should return null if the IBAValidator N length is exactly 15 characters and valid', + () { + // Arrange + final Validator validator = iban(); + const String validShortIban = 'AL47212110090000000235698741'; + + // Act + final String? result = validator(validShortIban); + + // Assert + expect(result, isNull); + }); + }); +} diff --git a/test/src/validators/generic_type_validators/contains_element_validator_test.dart b/test/src/validators/generic_type_validators/in_list_validator_test.dart similarity index 82% rename from test/src/validators/generic_type_validators/contains_element_validator_test.dart rename to test/src/validators/generic_type_validators/in_list_validator_test.dart index 29e92351..cc784320 100644 --- a/test/src/validators/generic_type_validators/contains_element_validator_test.dart +++ b/test/src/validators/generic_type_validators/in_list_validator_test.dart @@ -3,17 +3,17 @@ import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:form_builder_validators/src/validators/validators.dart'; void main() { - group('Validator: containsElement', () { + group('Validator: isInList', () { group('Validations with default error message', () { test('Should return null when int 0 is provided', () { - final Validator validator = containsElement([0]); + final Validator validator = inList([0]); expect(validator(0), isNull); expect(validator(2), equals(FormBuilderLocalizations.current.containsElementErrorText)); }); test('Should return null when int 0 or String "2" is provided', () { - final Validator validator = containsElement([0, '2']); + final Validator validator = inList([0, '2']); expect(validator(0), isNull); expect(validator('2'), isNull); @@ -21,8 +21,7 @@ void main() { equals(FormBuilderLocalizations.current.containsElementErrorText)); }); test('Should return null when int 0, int 2, or null is provided', () { - final Validator validator = - containsElement([0, 2, null]); + final Validator validator = inList([0, 2, null]); expect(validator(0), isNull); expect(validator(2), isNull); @@ -33,14 +32,14 @@ void main() { }); test('Should throw ArgumentError when list input is empty', () { - expect(() => containsElement([]), throwsArgumentError); + expect(() => inList([]), throwsArgumentError); }); test('Should return custom error message when invalid input is provided', () { const String customMessage = 'custom message'; - final Validator validator = containsElement([1, 2, 3], - containsElementMsg: (_, __) => customMessage); + final Validator validator = + inList([1, 2, 3], inListMsg: (_, __) => customMessage); expect(validator(4), equals(customMessage)); }); @@ -48,7 +47,7 @@ void main() { test('should remain immutable when input elements change', () { final List elements = [12, 15, 'hi']; - final Validator v = containsElement(elements); + final Validator v = inList(elements); expect(v(12), isNull); expect(v(15), isNull); diff --git a/test/src/validators/generic_type_validators/not_in_list_validator_test.dart b/test/src/validators/generic_type_validators/not_in_list_validator_test.dart new file mode 100644 index 00000000..3daefcb3 --- /dev/null +++ b/test/src/validators/generic_type_validators/not_in_list_validator_test.dart @@ -0,0 +1,91 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + group('Validator: notInList', () { + group('Validations with default error message', () { + test('Should return null when int 0 is provided', () { + final Validator validator = notInList([2]); + + expect(validator(0), isNull); + expect( + validator(2), + equals(FormBuilderLocalizations + .current.doesNotContainElementErrorText)); + }); + test('Should return null when int 0 or String "2" is provided', () { + final Validator validator = notInList(['0', 2]); + + expect(validator(0), isNull); + expect(validator('2'), isNull); + expect( + validator(2), + equals(FormBuilderLocalizations + .current.doesNotContainElementErrorText)); + }); + test('Should return null when int 0, int 2, or null is provided', () { + final Validator validator = notInList(['0', '2']); + + expect(validator(0), isNull); + expect(validator(2), isNull); + expect(validator(null), isNull); + expect( + validator('2'), + equals(FormBuilderLocalizations + .current.doesNotContainElementErrorText)); + }); + }); + + test('Should throw ArgumentError when list input is empty', () { + expect(() => notInList([]), throwsArgumentError); + }); + + test('Should return custom error message when invalid input is provided', + () { + const String customMessage = 'custom message'; + final Validator validator = + notInList([1, 2, 3], notInListMsg: (_, __) => customMessage); + + expect(validator(1), equals(customMessage)); + }); + + test('should remain immutable when input elements change', () { + final List elements = [12.02, 2, true]; + + final Validator v = notInList(elements); + + expect(v(12), isNull); + expect(v(15), isNull); + expect(v('hi'), isNull); + expect(v(12.02), + FormBuilderLocalizations.current.doesNotContainElementErrorText); + expect(v(2), + FormBuilderLocalizations.current.doesNotContainElementErrorText); + expect(v(true), + FormBuilderLocalizations.current.doesNotContainElementErrorText); + + elements.removeLast(); + expect(v(12), isNull); + expect(v(15), isNull); + expect(v('hi'), isNull); + expect(v(12.02), + FormBuilderLocalizations.current.doesNotContainElementErrorText); + expect(v(2), + FormBuilderLocalizations.current.doesNotContainElementErrorText); + expect(v(true), + FormBuilderLocalizations.current.doesNotContainElementErrorText); + + elements.add(true); + expect(v(12), isNull); + expect(v(15), isNull); + expect(v('hi'), isNull); + expect(v(12.02), + FormBuilderLocalizations.current.doesNotContainElementErrorText); + expect(v(2), + FormBuilderLocalizations.current.doesNotContainElementErrorText); + expect(v(true), + FormBuilderLocalizations.current.doesNotContainElementErrorText); + }); + }); +} diff --git a/test/src/validators/generic_type_validators/satisfy_validator_test.dart b/test/src/validators/generic_type_validators/satisfy_validator_test.dart new file mode 100644 index 00000000..9a8a4e9e --- /dev/null +++ b/test/src/validators/generic_type_validators/satisfy_validator_test.dart @@ -0,0 +1,53 @@ +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' as v; + +void main() { + group('Validator: satisfy', () { + test('Should validate always true condition', () { + expect(v.satisfy((Object? i) => true)('input'), isNull); + }); + test('Should validate always false condition', () { + expect(v.satisfy((Object? i) => false)('input'), + equals(FormBuilderLocalizations.current.satisfyErrorText)); + }); + test('Should validate if it is a positive even integer', () { + final Validator validator = v.satisfy((Object input) { + if (input is! int) { + return false; + } + if (input > 0 && input % 2 == 0) { + return true; + } + return false; + }); + expect(validator('input'), + equals(FormBuilderLocalizations.current.satisfyErrorText)); + expect(validator('12'), + equals(FormBuilderLocalizations.current.satisfyErrorText)); + expect(validator(-12), + equals(FormBuilderLocalizations.current.satisfyErrorText)); + expect(validator(0), + equals(FormBuilderLocalizations.current.satisfyErrorText)); + expect(validator(3), + equals(FormBuilderLocalizations.current.satisfyErrorText)); + expect(validator(2), isNull); + expect(validator(10000), isNull); + }); + + test('Should validate with custom message', () { + const String errorMsg = 'error message'; + final Validator validator = v.satisfy( + (double i) => i > 23.4 && i <= 56.0, + satisfyMsg: (_) => errorMsg); + + expect(validator(12), equals(errorMsg)); + expect(validator(23.4), equals(errorMsg)); + expect(validator(23.5), isNull); + expect(validator(28.0099), isNull); + expect(validator(56.0), isNull); + expect(validator(56.1), equals(errorMsg)); + expect(validator(1000), equals(errorMsg)); + }); + }); +} diff --git a/test/src/validators/miscellaneous_validators/color_code_validator_test.dart b/test/src/validators/miscellaneous_validators/color_code_validator_test.dart new file mode 100644 index 00000000..29e34f16 --- /dev/null +++ b/test/src/validators/miscellaneous_validators/color_code_validator_test.dart @@ -0,0 +1,113 @@ +import 'package:faker_dart/faker_dart.dart'; +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() { + final Faker faker = Faker.instance; + final String customErrorMessage = faker.lorem.sentence(); + + group('ColorCodeValidator -', () { + test('should return null if the color code is a valid hex', () { + // Arrange + final Validator validator = colorCode(); + const String validHex = '#1a2b3c'; + + // Act + final String? result = validator(validHex); + + // Assert + expect(result, isNull); + }); + + test('should return null if the color code is a valid RGB', () { + // Arrange + final Validator validator = colorCode(); + const String validRgb = 'rgb(255, 0, 0)'; + + // Act + final String? result = validator(validRgb); + + // Assert + expect(result, isNull); + }); + + test('should return null if the color code is a valid HSL', () { + // Arrange + final Validator validator = colorCode(); + const String validHsl = 'hsl(120, 100%, 50%)'; + + // Act + final String? result = validator(validHsl); + + // Assert + expect(result, isNull); + }); + + test('should return the default error message if the color code is invalid', + () { + // Arrange + final Validator validator = colorCode(); + const String invalidColor = 'invalid-color'; + + // Act + final String? result = validator(invalidColor); + + // Assert + expect( + result, + equals( + FormBuilderLocalizations.current.colorCodeErrorText('HEX, RGB, HSL'), + ), + ); + }); + + test('should return the custom error message if the color code is invalid', + () { + // Arrange + final Validator validator = + colorCode(colorCodeMsg: (_) => customErrorMessage); + const String invalidColor = 'invalid-color'; + + // Act + final String? result = validator(invalidColor); + + // Assert + expect(result, equals(customErrorMessage)); + }); + + test('should return null if the color code matches custom regex', () { + // Arrange + final Validator validator = colorCode( + customColorCode: (String input) => + RegExp(r'^[A-Fa-f0-9]{6}$').hasMatch(input), + ); + const String validCustomColor = '1A2B3C'; + + // Act + final String? result = validator(validCustomColor); + + // Assert + expect(result, isNull); + }); + + test( + 'should return the custom error message if the color code does not match custom regex', + () { + // Arrang + final Validator validator = colorCode( + customColorCode: (String input) => + RegExp(r'^[A-Fa-f0-9]{6}$').hasMatch(input), + colorCodeMsg: (_) => customErrorMessage, + ); + + const String invalidCustomColor = 'invalid'; + + // Act + final String? result = validator(invalidCustomColor); + + // Assert + expect(result, equals(customErrorMessage)); + }); + }); +} diff --git a/test/src/validators/miscellaneous_validators/isbn_validator_test.dart b/test/src/validators/miscellaneous_validators/isbn_validator_test.dart new file mode 100644 index 00000000..67fd1635 --- /dev/null +++ b/test/src/validators/miscellaneous_validators/isbn_validator_test.dart @@ -0,0 +1,112 @@ +import 'package:faker_dart/faker_dart.dart'; +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() { + final Faker faker = Faker.instance; + final String customErrorMessage = faker.lorem.sentence(); + + group('IsbnValidator -', () { + test('should return null if the ISBN-10 is valid', () { + // Arrange + final Validator validator = isbn(); + const String validIsbn10 = '0-306-40615-2'; + + // Act + final String? result = validator(validIsbn10); + + // Assert + expect(result, isNull); + }); + + test('should return null if the ISBN-13 is valid', () { + // Arrange + final Validator validator = isbn(); + const String validIsbn13 = '978-3-16-148410-0'; + + // Act + final String? result = validator(validIsbn13); + + // Assert + expect(result, isNull); + }); + + test('should return the default error message if the ISBN-10 is invalid', + () { + // Arrange + final Validator validator = isbn(); + const String invalidIsbn10 = '0-306-40615-3'; + + // Act + final String? result = validator(invalidIsbn10); + + // Assert + expect(result, equals(FormBuilderLocalizations.current.isbnErrorText)); + }); + + test('should return the default error message if the ISBN-13 is invalid', + () { + // Arrange + final Validator validator = isbn(); + const String invalidIsbn13 = '978-3-16-148410-1'; + + // Act + final String? result = validator(invalidIsbn13); + + // Assert + expect(result, equals(FormBuilderLocalizations.current.isbnErrorText)); + }); + + test('should return the custom error message if the ISBN is invalid', () { + // Arrange + final Validator validator = + isbn(isbnMsg: (_) => customErrorMessage); + const String invalidIsbn = '978-3-16-148410-1'; + + // Act + final String? result = validator(invalidIsbn); + + // Assert + expect(result, equals(customErrorMessage)); + }); + + test( + 'should return the default error message if the ISBN length is neither 10 nor 13 characters', + () { + // Arrange + final Validator validator = isbn(); + const String invalidIsbn = '123456789'; + + // Act + final String? result = validator(invalidIsbn); + + // Assert + expect(result, equals(FormBuilderLocalizations.current.isbnErrorText)); + }); + + test('should return null if the ISBN-10 is valid without hyphens', () { + // Arrange + final Validator validator = isbn(); + const String validIsbn10 = '0306406152'; + + // Act + final String? result = validator(validIsbn10); + + // Assert + expect(result, isNull); + }); + + test('should return null if the ISBN-13 is valid without hyphens', () { + // Arrange + final Validator validator = isbn(); + const String validIsbn13 = '9783161484100'; + + // Act + final String? result = validator(validIsbn13); + + // Assert + expect(result, isNull); + }); + }); +} diff --git a/test/src/validators/network_validators/mac_address_validator_test.dart b/test/src/validators/network_validators/mac_address_validator_test.dart new file mode 100644 index 00000000..7c3d1fc5 --- /dev/null +++ b/test/src/validators/network_validators/mac_address_validator_test.dart @@ -0,0 +1,137 @@ +import 'package:faker_dart/faker_dart.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/network_validators.dart'; + +void main() { + final Faker faker = Faker.instance; + final String customErrorMessage = faker.lorem.sentence(); + + group('MacAddressValidator -', () { + test('should return null for valid MAC addresses', () { + // Arrange + final Validator validator = macAddress(); + const List validMacAddresses = [ + '00:1B:44:11:3A:B7', + '00:1a:2b:3c:4d:5e', + '00-1A-2B-3C-4D-5E', + '00-1a-2b-3c-4d-5e', + ]; + + // Act & Assert + for (final String value in validMacAddresses) { + expect(validator(value), isNull); + } + }); + + test('should return the default error message for invalid MAC addresses', + () { + // Arrange + final Validator validator = macAddress(); + const List invalidMacAddresses = [ + '00:1A:2B:3C:4D', + '00:1A:2B:3C:4D:5E:6F', + '00:1A:2B:3C:4D:ZZ', + '00-1A-2B-3C-4D', + '001A2B3C4D5E', + 'G0:1A:2B:3C:4D:5E', + ]; + + // Act & Assert + for (final String value in invalidMacAddresses) { + final String? result = validator(value); + expect(result, isNotNull); + expect( + result, + equals(FormBuilderLocalizations.current.macAddressErrorText), + ); + } + }); + + test('should return the custom error message for invalid MAC addresses', + () { + // Arrange + final Validator validator = + macAddress(macAddressMsg: (_) => customErrorMessage); + const List invalidMacAddresses = [ + '00:1A:2B:3C:4D', + '00:1A:2B:3C:4D:5E:6F', + '00:1A:2B:3C:4D:ZZ', + '00-1A-2B-3C-4D', + '001A2B3C4D5E', + 'G0:1A:2B:3C:4D:5E', + ]; + + // Act & Assert + for (final String value in invalidMacAddresses) { + final String? result = validator(value); + expect(result, equals(customErrorMessage)); + } + }); + + test( + 'should return the default error message when the MAC address is an empty string', + () { + // Arrange + final Validator validator = macAddress(); + const String emptyMacAddress = ''; + + // Act + final String? result = validator(emptyMacAddress); + + // Assert + expect(result, isNotNull); + expect( + result, + equals(FormBuilderLocalizations.current.macAddressErrorText), + ); + }); + + test('should return null for valid MAC addresses with custom regex', () { + // Arrange + final RegExp customRegex = RegExp( + r'^[0-9A-Fa-f]{2}[:-]{1}[0-9A-Fa-f]{2}[:-]{1}[0-9A-Fa-f]{2}[:-]{1}[0-9A-Fa-f]{2}[:-]{1}[0-9A-Fa-f]{2}[:-]{1}[0-9A-Fa-f]{2}$', + ); + final Validator validator = + macAddress(isMacAddress: (String i) => customRegex.hasMatch(i)); + const List validMacAddresses = [ + '00:1A:2B:3C:4D:5E', + '00:1a:2b:3c:4d:5e', + '00-1A-2B-3C-4D-5E', + '00-1a-2b-3c-4d-5e', + ]; + + // Act & Assert + for (final String value in validMacAddresses) { + expect(validator(value), isNull); + } + }); + + test( + 'should return the custom error message for invalid MAC addresses with custom regex', + () { + // Arrange + final RegExp customRegex = RegExp( + r'^[0-9A-Fa-f]{2}[:-]{1}[0-9A-Fa-f]{2}[:-]{1}[0-9A-Fa-f]{2}[:-]{1}[0-9A-Fa-f]{2}[:-]{1}[0-9A-Fa-f]{2}[:-]{1}[0-9A-Fa-f]{2}$', + ); + final Validator validator = macAddress( + isMacAddress: (String i) => customRegex.hasMatch(i), + macAddressMsg: (_) => customErrorMessage, + ); + const List invalidMacAddresses = [ + '00:1A:2B:3C:4D', + '00:1A:2B:3C:4D:5E:6F', + '00:1A:2B:3C:4D:ZZ', + '00-1A-2B-3C-4D', + '001A2B3C4D5E', + 'G0:1A:2B:3C:4D:5E', + ]; + + // Act & Assert + for (final String value in invalidMacAddresses) { + final String? result = validator(value); + expect(result, equals(customErrorMessage)); + } + }); + }); +}