सार्वजनिक जानकारी से बना चयन मार्गदर्शन। केवल संदर्भ हेतु।
Lucid Validation एक Dart/Flutter पैकेज है जो विकासकर्ताओं को FluentValidation से प्रेरित मजबूत प्रकार, पुन: उपयोगी नियम बनाने की अनुमति देता है। Flutterando समुदाय द्वारा डिज़ाइन किया गया, यह Flutter एप्लिकेशन में साफ, पठनीय और रखरखाव योग्य नियमों को बढ़ावा देता है। पैकेज Android, iOS, Web, macOS, Windows और Linux जैसे कई प्लेटफॉर्म का समर्थन करता है, जिससे यह क्रॉस-प्लेटफॉर्म प्रोजेक्ट्स के लिए आदर्श है। प्रकार सुरक्षा और विकासकर्ता अनुभव पर ध्यान केंद्रित करते हुए, Lucid Validation फॉर्म वैधता और डेटा अखंडता जाँच को सरल बनाता है।
उपयोग
फॉर्म इनपुट वैधता
डेटा मॉडल वैधता
क्रॉस-प्लेटफॉर्म ऐप सत्यापन
API अनुरोध प्रतिबंब वैधता
उपयोगकर्ता पंजीकरण प्रवाह
मुख्य विशेषताएँ
स्ट्रॉन्गली टाइप्ड वैधता नियम
फ्लुएंट एपीआई डिज़ाइन
पुनर्उपयोगी वैधता सत्यापक
प्रकार सुरक्षित त्रुटि प्रबंधन
विस्तारयोग्य सत्यापन श्रृंखला
इनके लिए उपयुक्त
मजबूत वैधता की तलाश में Flutter विकासकर्ता
कोड बनाए रखने के प्राथमिकता वाली टीमें
स्थिर डेटा सत्यापन की आवश्यकता वाले प्रोजेक्ट
FluentValidation पैटर्न के साथ परिचित विकासकर्मी
क्रॉस-प्लेटफ़ॉर्म मोबाइल और वेब एप्लिकेशन
विचार योग्य बातें
Dart जनरिक्स की समझ आवश्यक है
प्रारंभिक सेटअप जटिलता में वृद्धि कर सकता है
बड़े अनुप्रयोगों के लिए सबसे उपयुक्त
मुख्यधारा के पैकेजों की तुलना में सीमित दस्तावेज़
अपडेट के लिए समुदाय के समर्थन पर निर्भर करता है
मूल README
संबंधित सोर्स रिपॉज़िटरी के मूल README का स्नैपशॉट। नवीनतम सामग्री के लिए रिपॉज़िटरी देखें।
LucidValidation
LucidValidation is a pure Dart package for building strongly-typed validation rules, inspired by FluentValidation. Developed by the Flutterando community, this package offers a fluent, expressive, and highly extensible API for both frontend (Flutter) and backend applications.
Key Features
Strongly Typed: Errors are caught at compile-time, with full autocomplete support.
Fluent API: Expressive chainable validators that read like natural language.
Highly Extensible: Easily add custom validation rules via Dart extensions.
Polymorphic Validations: Handle complex conditional flows out of the box using switchOn and when/unless branches.
Unified Validation System: Write validation rules once and reuse them across frontend (Flutter) and backend applications.
Deep Nesting Support: Effortlessly validate complex object graphs, lists of items, and simple collections with index tracking.
Built-in Localization: Localized messages out of the box with custom translation capabilities.
Installation
Add lucid_validation to your project using the Dart CLI:
Extend LucidValidator and declare your validation rules in the constructor:
import'package:lucid_validation/lucid_validation.dart';
classUserValidatorextendsLucidValidator<UserModel> {
UserValidator() {
final now = DateTime.now();
ruleFor((user) => user.email, key: 'email')
.notEmpty()
.validEmail();
ruleFor((user) => user.password, key: 'password')
.notEmpty()
.minLength(8, message: 'Must be at least 8 characters long')
.mustHaveLowercase()
.mustHaveUppercase()
.mustHaveNumber()
.mustHaveSpecialCharacter();
ruleFor((user) => user.age, key: 'age')
.min(18, message: 'Minimum age is 18 years');
ruleFor((user) => user.dateOfBirth, key: 'dateOfBirth')
.lessThan(DateTime(now.year - 18, now.month, now.day));
}
}
3. Run Validation
Instantiate your validator and validate your model instances:
voidmain() {
final user = UserModel(
email: 'test@example.com',
password: 'Passw0rd!',
age: 25,
dateOfBirth: DateTime(2001, 1, 1),
);
final validator = UserValidator();
final result = validator.validate(user);
if (result.isValid) {
print('User is valid!');
} else {
print('Validation errors: ${result.exceptions.map((e) => e.message).join(', ')}');
}
}
Inline Validation (LucidValidator.inline)
For simple, lightweight, or dynamically configured validation needs, you do not need to declare a dedicated class extending LucidValidator. You can construct a ready-to-use validator inline using the LucidValidator.inline factory constructor.
This is extremely useful for:
Quick validation logic within a controller, API handler, controller action, or leaving your validations alongside your form (Flutter).
Dynamic validation rules created on the fly.
Nested or sub-property validations passed to .setValidator() or .setEach().
[!TIP]
Keep your codebase lean by using inline validators for one-off tasks, and reserve class-based validators (e.g. UserValidator extends LucidValidator<User>) for reusable domain model validations.
How to use
final validator = LucidValidator<UserModel>.inline((v) {
v.ruleFor((user) => user.email, key: 'email')
.notEmpty()
.validEmail();
v.ruleFor((user) => user.password, key: 'password')
.notEmpty()
.minLength(8);
});
// Run validation normallyfinal result = validator.validate(user);
Working with ValidationResult
The returned ValidationResult provides helpers for extracting error details, which is especially useful for rendering forms or building API responses:
final result = validator.validate(user);
// Check overall statusbool isValid = result.isValid;
// Map containing errors grouped by field key — perfect for JSON API payloadsMap<String, List<String>> errors = result.errorsByKey;
// Output: { "email": ["'email' is not a valid email address."], "password": [...] }// Retrieve only the first error message for a specific keyString? emailError = result.firstErrorFor('email');
If your application architecture prefers exceptions over manual boolean checks, use validateAndThrow (or its asynchronous equivalent validateAndThrowAsync). This will raise a LucidValidationException if validation fails:
try {
validator.validateAndThrow(user);
} onLucidValidationExceptioncatch (e) {
print(e.exceptions); // Access the list of ValidationExceptions
}
Direct Exception Retrieval (getExceptions / getExceptionsByKey)
If you want to retrieve raw validation exceptions directly without checking ValidationResult.exceptions, you can use the following methods:
getExceptions(entity): Returns a flat List<ValidationException> of all failures encountered.
getExceptionsByKey(entity, key): Returns only the validation exceptions related to a specific property key.
// Retrieve all validation exceptionsfinal allExceptions = validator.getExceptions(user);
// Retrieve exceptions only for the email fieldfinal emailExceptions = validator.getExceptionsByKey(user, 'email');
Real-Time Field Inspection (rulesForField / rulesForFieldAsync)
For interactive user interfaces, you often want to show a real-time checklist (for example, password strength requirements) that updates as the user types.
Instead of writing duplicate logic, you can use rulesForField (or rulesForFieldAsync if any rules are asynchronous) to evaluate and retrieve the status of all rules registered for a specific field, regardless of whether they currently pass or fail.
Each returned ExposedRuleResult contains:
code: The unique rule code (e.g. notEmpty, minLength).
message: The formatted error message for that rule.
isValid: A boolean indicating if the entity currently satisfies that specific rule.
Example
final user = UserModel(
email: 'test@example.com',
password: 'short', // Fails length and character requirements
age: 25,
dateOfBirth: DateTime(2001, 1, 1),
);
final validator = UserValidator();
final rules = validator.rulesForField(user, 'password');
for (final rule in rules) {
print('${rule.isValid ? '✓' : '✗'} [${rule.code}]: ${rule.message}');
}
// Output:// ✓ [notEmpty]: 'password' must not be empty.// ✗ [minLength]: Must be at least 8 characters long// ✗ [mustHaveLowercase]: 'password' must have at least one lowercase letter.// ...
Built-In Validators
LucidValidation includes a rich set of built-in validation rules.
[!TIP]
Almost all built-in rules have a nullable counterpart ending with the OrNull suffix (e.g., validEmailOrNull()), which permits null values but validates them if they are present.
Common & Custom Validators
Validator
Target Type
Description
must(predicate)
T
Custom validation using a synchronous predicate.
mustWith(predicate)
T
Custom validation with access to the parent entity.
mustAsync(predicate)
T
Custom asynchronous validation.
mustWithAsync(predicate)
T
Custom asynchronous validation with access to the parent entity.
isNull()
T?
Checks if the value is null.
isNotNull()
T?
Checks if the value is not null.
equalTo(otherField)
T
Checks if the value is equal to another field's value.
notEqualTo(otherField)
T
Checks if the value is not equal to another field's value.
isInEnum(values)
T
Checks if the value is contained within a list of allowed values (e.g. MyEnum.values).
String Validators
Validator
Description
isEmpty() / notEmpty()
Checks if a string is empty or contains only whitespace / not empty.
minLength(min) / maxLength(max)
Asserts string length boundaries.
length(min, max)
Asserts string length is within a specific range.
isAlphanumeric()
Checks if the string contains only letters and digits.
isNumeric()
Checks if the string contains only numeric characters.
isUppercase() / isLowercase()
Checks if the string is entirely uppercase / lowercase.
contains(substring)
Checks if the string contains the specified substring.
startsWith(prefix) / endsWith(suffix)
Checks if the string starts/ends with a substring.
matchesPattern(regex)
Checks if the string matches a regular expression pattern.
mustHaveLowercase() / mustHaveUppercase()
Requires at least one lowercase / uppercase letter.
mustHaveNumber() / mustHaveSpecialCharacter()
Requires at least one numeric character / special character.
hasNoSequentialRepeatedCharacters(length)
Ensures there are no sequences of repeated characters (e.g. "aaa").
hasNoSequentialCharacters(length)
Ensures there are no sequences of sequential characters (e.g. "123", "abc").
Numeric Validators
Validator
Description
min(val) / max(val)
Asserts a number is greater than/equal to val or less than/equal to val.
range(min, max)
Asserts a number is between min and max (inclusive).
inclusiveBetween(min, max)
Asserts value is within a range, including the bounds.
exclusiveBetween(min, max)
Asserts value is within a range, excluding the bounds.
isPositive() / isNegative()
Checks if a number is positive ($> 0$) / negative ($< 0$).
isNonNegative() / isNonZero()
Checks if a number is non-negative ($\ge 0$) / non-zero ($\ne 0$).
isEven() / isOdd()
Checks if a number is even / odd.
multipleOf(factor)
Checks if a number is a multiple of a given factor.
precisionScale(precision, scale)
Asserts maximum total digits (precision) and decimal places (scale).
Boolean Validators
Validator
Description
isTrue() / isFalse()
Checks if the boolean value is strictly true / false.
DateTime Validators
Validator
Description
greaterThan(dateTime) / lessThan(dateTime)
Checks if value is strictly after / before a specified DateTime.
Asserts a valid Brazilian phone format (e.g., (xx) 9xxxx-xxxx or xx9xxxxxxxx).
validPhoneWithCountryCodeBR()
String
Asserts a valid Brazilian phone format with country code (e.g., +55xx9xxxxxxxx).
Integration with Flutter
Using lucid_validation with Flutter TextFormField validation is clean and straightforward. Leverage the byField method to bind validation directly to your form fields.
You can do this by extending LucidValidator or by declaring the validator inline directly alongside your widget using LucidValidator.inline.
[!NOTE]
Architectural Recommendation: Although inline validation is convenient for rapid UI prototyping or local forms, LucidValidation strongly recommends adopting Domain-Driven Design (DDD) practices. Ideally, your validation rules should reside in your project's Domain Layer as part of your core business logic. This ensures that your entities are kept valid across all application layers (controllers, APIs, and UI views).
Cascade Mode
Cascade Mode controls validation rule execution once a failure is detected on a property.
Available Modes
CascadeMode.continueExecution (Default): Executes all rules in the validation chain for a property, even if preceding rules failed. Use this to collect all errors at once.
CascadeMode.stopOnFirstFailure: Aborts further checks on that specific property as soon as a failure is encountered. Excellent for stopping heavy or redundant checks.
Apply cascade settings using the .cascade() method at the end of the rule chain:
ruleFor((user) => user.password, key: 'password')
.notEmpty()
.minLength(8)
.mustHaveLowercase()
.mustHaveUppercase()
.mustHaveNumber()
.mustHaveSpecialCharacter()
.cascade(CascadeMode.stopOnFirstFailure); // Halts validation on the first error found in this chain
Conditional Execution
The when Condition
Applies validation rules in a builder only if the provided condition evaluates to true:
ruleFor((user) => user.phoneNumber, key: 'phoneNumber')
.when((user) => user.requiresPhoneNumber)
.notEmpty()
.must((value) => value.length == 10, message: 'Phone number must be 10 digits', code: 'phone_length');
The unless Condition
The inverse of when: rules run only when the condition evaluates to false:
switchOn routes an entire block of rules based on a discriminator value (such as an enum, String, or int) derived from the entity. Only the rules for the matching case are executed; all others are ignored.
This is ideal for polymorphic entities where required fields change depending on the type selected:
Instead of inline parameters, you can customize the preceding rule in your chain using fluent extensions mirroring FluentValidation:
ruleFor((user) => user.email, key: 'email')
.notEmpty().withMessage('Please inform your email.')
.validEmail().withErrorCode('EMAIL_INVALID');
withMessage(msg): Overrides the error message of the preceding rule.
withErrorCode(code): Overrides the error code of the preceding rule.
withName(displayName): Sets a user-friendly display name (e.g. 'Date of birth' instead of 'dateOfBirth') for error messages, while preserving the key for field mapping:
ruleFor((user) => user.dateOfBirth, key: 'dateOfBirth')
.withName('Date of birth')
.notEmpty(); // Output message: "'Date of birth' must not be empty."
Data Sanitization with normalize
Transform and sanitize input values (e.g., trim whitespace, lower-case, or remove masks) before validations execute. This affects only the validation engine and does not mutate the original model:
For validation rules needing network calls, database lookups, or file system access, use mustAsync / mustWithAsync and execute your checks with validateAsync or validateAndThrowAsync:
[!IMPORTANT]
If any validation chain contains asynchronous rules, calling the synchronous validate() or byField() will throw an AsyncValidationException. Always use validateAsync for asynchronous rules.
Partitioning Rules with Rule Sets
Rule Sets allow you to partition rules for different workflows (such as creating vs updating entities). Rules defined outside any explicitly declared ruleSet are placed in the default set.
To fetch error validation status on a nested field using byField, use dot-notation:
final validator = CustomerValidator();
final postcodeValidationFunc = validator.byField(customer, 'address.postcode');
final errorMsg = postcodeValidationFunc(); // Returns the validation error for postcode, if any.
Validating Collections with setEach
To validate a list of child objects, use setEach to apply a validator to each element individually. This tracks item index numbers in case of failure:
In the validation exceptions, index tracking is handled automatically:
final result = validator.validate(classroom);
final exceptions = result.exceptions;
// If the first student in the list failed validation:print(exceptions[2].key); // "name"print(exceptions[2].index); // 0 (index of the failing item)
Validating Simple Collections with ruleForEach
If you are validating collections containing simple primitive types (e.g. List<String>), use ruleForEach:
For custom logic, use useValidation. Provide a predicate (value, entity) => bool. You can optionally supply custom parameter tokens for message replacement:
extensionMinWordsValidationonSimpleValidationBuilder<String> {
SimpleValidationBuilder<String> minWords(int min, {String? message, String? code}) {
returnuseValidation(
(value, entity) => value.trim().split(RegExp(r'\s+')).length >= min,
code: code ?? 'minWords',
message: message ?? "'{PropertyName}' must have at least {Min} words.",
parameters: (value, entity) => {'Min': '$min'},
);
}
}
3. Asynchronous Custom Rules with useValidationAsync
For rules requiring external API calls or database checks, use useValidationAsync. The predicate must return a Future<bool>:
If you need absolute control over the created ValidationException on failure (for example, to dynamically format the entity type name, modify properties, or construct a highly customized payload), you can use use (sync) or useAsync (async) directly:
AI-Assisted Development (Copilot, Cursor, Gemini, etc.)
If you are using AI-powered coding assistants (such as Cursor, GitHub Copilot, Gemini, or Claude) to write or maintain validators in your codebase, we have prepared a specialized context instruction file.
Instruct your AI assistant to read the SKILL.md file, available in the root of this repository, before starting. It provides a fairly detailed guide covering built-in rules, complex nesting, custom validation extension patterns, and common architectural pitfalls.
Contributing
Contributions are welcome! If you encounter issues, have feature requests, or want to contribute translations, please open an issue or submit a pull request on the GitHub Repository.
License
This package is licensed under the MIT License. See the [LICENSE](file:///Users/wellignton.santos/freelance/lucid_validation/LICENSE) file for more information.