JustValidation
🇬🇧 English
A fluent validation library for Dart inspired by FluentValidation (.NET). Build expressive, strongly-typed validation rules for your Dart and Flutter applications with parallel processing support via isolates.
✨ Features
- ✅ Fluent API - Chainable, readable validation rules
- ✅ Type-safe - Full support for Dart's type system and null-safety
- ✅ Rich Validators - 30+ built-in validators for common scenarios
- ✅ Advanced Validators - IP, MAC, GPS, Credit Card, IBAN, ISBN, UUID, Postal Codes (129 countries)
- ✅ DateTime Validators - Past/Future, Age validation, Date ranges, Same day checks
- ✅ Identity Documents - 26 validators for 18 countries (DNI, SSN, CPF, CURP, etc.)
- ✅ Parallel Processing - Isolate-based validation for large batches (1000+ objects)
- ✅ Custom Validators - Easy to add custom validation logic
- ✅ Async Validation - Built-in support for asynchronous validation
- ✅ Conditional Rules - When/Unless conditions for dynamic validation
- ✅ Nested Validation - Validate complex object graphs
- ✅ Collection Validation - Validate collections and their items
- ✅ Inline Validation - Quick validation without creating validator classes
- ✅ Zero Dependencies - Lightweight with no external dependencies
- ✅ Universal - Works with Dart VM, Flutter, Web, and backend
📦 Installation
Add just_validation to your pubspec.yaml:
dependencies:
just_validation: ^0.3.0
environment:
sdk: '>=2.19.0 <4.0.0'
Then run:
dart pub get
# or
flutter pub get
🚀 Quick Start
import 'package:just_validation/just_validation.dart';
// Define your model
class User {
final String? name;
final String? email;
final int? age;
User({this.name, this.email, this.age});
}
// Create a validator
class UserValidator extends AbstractValidator<User> {
UserValidator() {
ruleFor((user) => user.name)
.notEmpty()
.withMessage('Name is required')
.minLength(2)
.maxLength(50);
ruleFor((user) => user.email)
.notEmpty()
.emailAddress()
.withMessage('Invalid email address');
ruleFor((user) => user.age)
.greaterThanOrEqual(18)
.withMessage('Must be 18 or older')
.lessThan(120)
.when((user) => user.age != null);
}
}
// Use the validator
void main() {
final validator = UserValidator();
final user = User(name: 'John', email: 'john@example.com', age: 25);
final result = validator.validate(user);
if (result.isValid) {
print('✅ User is valid!');
} else {
for (var error in result.errors) {
print('❌ ${error.propertyName}: ${error.errorMessage}');
}
}
}
⚡ Parallel Processing with Isolates
For large batches or computationally expensive validations, use isolate-based validation:
// Validate 10,000 objects without blocking the main thread
final users = List.generate(10000, (i) => User(...));
// Instead of:
// final results = users.map((u) => validator.validate(u)).toList(); // Blocks UI
// Use isolates:
final results = await validator.validateManyIsolate(users); // Non-blocking!
print('✅ Validated ${results.length} users in parallel');
When to use isolates:
- ✅ Batch processing (1000+ objects)
- ✅ Complex validations (50+ rules)
- ✅ Cannot block UI thread (Flutter apps)
- ✅ Background processing
When NOT to use isolates:
- ❌ Simple forms (1-100 fields)
- ❌ Fast validations (<50ms total)
- ❌ Small batches (<100 objects)
📚 Documentation
String Validators
ruleFor((x) => x.name)
.notEmpty() // Must not be empty
.notNull() // Must not be null
.minLength(2) // Minimum length
.maxLength(50) // Maximum length
.length(5, 20) // Length between 5 and 20
.matches(RegExp(r'^[a-zA-Z]+$')) // Regex pattern
.emailAddress() // Valid email
.url() // Valid URL
Number Validators
ruleFor((x) => x.age)
.greaterThan(0) // > 0
.greaterThanOrEqual(18) // >= 18
.lessThan(100) // < 100
.lessThanOrEqual(99) // <= 99
.inclusiveBetween(18, 65) // Between 18 and 65 (inclusive)
.exclusiveBetween(0, 100) // Between 0 and 100 (exclusive)
Comparison Validators
ruleFor((x) => x.password)
.equal('confirmed_password') // Must equal
.notEqual('old_password') // Must not equal
Advanced Validators
// IP Address (IPv4, IPv6, or both)
ruleFor((x) => x.ip)
.ipAddress() // IPv4 or IPv6
.ipv4Address() // IPv4 only
.ipv6Address() // IPv6 only
// MAC Address
ruleFor((x) => x.mac)
.macAddress() // Supports :, -, or no separator
// GPS Coordinates
ruleFor((x) => x.lat)
.latitude() // -90 to 90
ruleFor((x) => x.lng)
.longitude() // -180 to 180
// Credit Card (Luhn algorithm)
ruleFor((x) => x.card)
.creditCard() // Visa, MC, Amex, etc.
// IBAN (52 countries)
ruleFor((x) => x.iban)
.iban() // Full Mod-97 validation
// ISBN (10 or 13)
ruleFor((x) => x.isbn)
.isbn() // ISBN-10 or ISBN-13
// UUID (v1-v5)
ruleFor((x) => x.uuid)
.uuid() // All UUID versions
// Postal Code (129 countries)
ruleFor((x) => x.zip)
.postalCode('US') // US ZIP codes
.postalCode('ES') // Spanish postal codes
.postalCode('GB') // UK postcodes
// Supports 129 countries across all continents
DateTime Validators
ruleFor((x) => x.birthDate)
.isInPast() // Must be in the past
.minAge(18) // Must be 18+ years old
.withMessage('Must be 18 or older');
ruleFor((x) => x.appointmentDate)
.isInFuture() // Must be in the future
.isBefore(DateTime(2025, 12, 31)) // Before end of year
.isAfter(DateTime.now()); // After today
ruleFor((x) => x.eventDate)
.isBetween(startDate, endDate) // Within date range (inclusive)
.isToday() // Must be today
.isSameDay(referenceDate); // Same day as reference
ruleFor((x) => x.expiryDate)
.isAfterOrEqual(DateTime.now())
.isWithinDuration(DateTime.now(), Duration(days: 365));
Identity Document Validators
// European Documents
ruleFor((x) => x.document)
.dni() // 🇪🇸 Spanish DNI
.nie() // 🇪🇸 Spanish NIE
.nin() // 🇬🇧 UK National Insurance
.germanId() // 🇩🇪 German Personalausweis
.codiceFiscale() // 🇮🇹 Italian Fiscal Code
.nif() // 🇵🇹 Portuguese NIF
.bsn() // 🇳🇱 Dutch BSN
.belgianNationalNumber() // 🇧🇪 Belgian National Number
.pesel() // 🇵🇱 Polish PESEL
.frenchNationalId(); // 🇫🇷 French CNI
// American Documents
ruleFor((x) => x.document)
.ssn() // 🇺🇸 US Social Security Number
.curp() // 🇲🇽 Mexican CURP
.rfc() // 🇲🇽 Mexican RFC
.cpf() // 🇧🇷 Brazilian CPF
.cnpj() // 🇧🇷 Brazilian CNPJ
.cuit() // 🇦🇷 Argentine CUIT/CUIL
.rut() // 🇨🇱 Chilean RUT
.sin() // 🇨🇦 Canadian SIN
.colombianCc() // 🇨🇴 Colombian CC
.ecuadorianCedula(); // 🇪🇨 Ecuadorian Cédula
// Asian Documents
ruleFor((x) => x.document)
.chineseId() // 🇨🇳 Chinese ID (18 digits)
.aadhaar() // 🇮🇳 Indian Aadhaar
.pan() // 🇮🇳 Indian PAN
.tfn() // 🇦🇺 Australian TFN
.nric(); // 🇸🇬 Singapore NRIC/FIN
// Generic validator with enum
ruleFor((x) => x.document)
.nationalId(NationalIdType.esDni)
.nationalId(NationalIdType.fromCountry('BR', 'CPF')!);
Custom Validators
// Simple predicate
ruleFor((x) => x.username)
.must((username) => !_reservedNames.contains(username))
.withMessage('Username is reserved');
// With object context
ruleFor((x) => x.password)
.mustWith((password, user) => password != user.email)
.withMessage('Password cannot be your email');
// Async validation
ruleFor((x) => x.email)
.mustAsync((email) async {
final exists = await _checkEmailExists(email);
return !exists;
}, 'Email already exists');
Conditional Validation
// Only validate if condition is true
ruleFor((x) => x.passport)
.notEmpty()
.when((user) => user.country != 'US');
// Skip validation if condition is true
ruleFor((x) => x.ssn)
.notEmpty()
.unless((user) => user.isInternational);
Nested Object Validation
class Address {
final String? street;
final String? city;
Address({this.street, this.city});
}
class Person {
final Address? address;
Person({this.address});
}
class AddressValidator extends AbstractValidator<Address> {
AddressValidator() {
ruleFor((a) => a.street).notEmpty();
ruleFor((a) => a.city).notEmpty();
}
}
class PersonValidator extends AbstractValidator<Person> {
PersonValidator() {
ruleFor((p) => p.address)
.setValidator(AddressValidator());
}
}
Collection Validation
class Order {
final List<OrderItem> items;
Order({required this.items});
}
class OrderValidator extends AbstractValidator<Order> {
OrderValidator() {
// Validate the collection itself
ruleFor((o) => o.items)
.notEmpty()
.withMessage('Order must have at least one item');
// Validate each item in the collection
ruleForEach((o) => o.items)
.setValidator(OrderItemValidator());
}
}
Inline Validation
Quick validation without creating validator classes:
// Single value
final result = 'john@example.com'.validateEmail();
if (result.isValid) {
print('✅ Valid email');
}
// With custom message
final result = password.validateMinLength(8, 'Too short');
// Chain validations
final result = email
.validateNotEmpty()
.validateEmail()
.validateMaxLength(100);
// Get value or throw
try {
final validEmail = email.validateEmail().getOrThrow();
print('Using: $validEmail');
} catch (e) {
print('Invalid email');
}
Cascade Mode
Control how validation continues after errors:
// Stop on first error (default: Continue)
ruleFor((x) => x.password)
.notEmpty()
.minLength(8)
.matches(RegExp(r'[A-Z]'))
.cascadeMode(CascadeMode.stop); // Stops at first failure
🚀 Isolate Validation API
Three methods for parallel processing:
// 1. Single object validation in isolate
final result = await validator.validateIsolate(user);
// 2. Batch validation (most efficient)
final results = await validator.validateManyIsolate(users);
// 3. Async validation in isolate
final result = await validator.validateAsyncIsolate(user);
Performance:
- Overhead: ~5-10ms per isolate
- Batch of 10,000 objects: ~150ms (vs ~1000ms synchronous)
- Ideal for: CSV imports, Excel validation, API batch endpoints
📊 Supported Postal Codes
129 countries organized by region:
- North America (12): US, CA, MX, CR, PA, GT, SV, HN, NI, CU, DO, PR
- South America (10): BR, AR, CL, CO, PE, VE, EC, BO, PY, UY
- Western Europe (18): GB, FR, DE, ES, IT, NL, BE, CH, AT, PT, IE, LU, MC, AD, SM, LI, VA, GI
- Nordic Europe (7): SE, NO, DK, FI, IS, FO, GL
- Eastern Europe (25): PL, CZ, SK, HU, RO, BG, HR, SI, EE, LV, LT, UA, BY, MD, RS, BA, MK, AL, GR, CY, MT, ME, XK
- Russia & Central Asia (5): RU, KZ, AM, AZ, GE
- East Asia (6): JP, CN, KR, TW, HK, MO
- Southeast Asia (10): TH, VN, MY, SG, ID, PH, MM, KH, LA, BN
- South Asia (7): IN, PK, BD, LK, NP, BT, MV
- Middle East (13): TR, IL, SA, AE, QA, KW, BH, OM, JO, LB, IQ, IR, PS
- Africa (13): ZA, EG, MA, DZ, TN, LY, KE, NG, ET, GH, MR, MU, LC
- Oceania (5): AU, NZ, PG, FJ, VG
📈 Statistics
- 56+ Validators built-in
- 129 Countries postal code support
- 200+ Tests (100% passing)
- 93% Code Coverage
- Zero Dependencies
- Production Ready
🎯 Real-World Examples
CSV Import with Progress
Future<void> importCSV(File file) async {
final lines = await file.readAsLines();
final users = lines.skip(1).map((line) => User.fromCsv(line)).toList();
print('Validating ${users.length} users...');
// Validate in isolate (non-blocking)
final results = await validator.validateManyIsolate(users);
// Filter valid/invalid
final valid = <User>[];
final invalid = <(User, ValidationResult)>[];
for (var i = 0; i < users.length; i++) {
if (results[i].isValid) {
valid.add(users[i]);
} else {
invalid.add((users[i], results[i]));
}
}
await database.insertUsers(valid);
print('✅ Imported: ${valid.length}');
print('❌ Rejected: ${invalid.length}');
}
Flutter Form with Real-time Validation
class LoginForm extends StatefulWidget {
@override
State<LoginForm> createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
final _validator = LoginValidator();
String _email = '';
String _password = '';
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
onChanged: (value) => setState(() => _email = value),
validator: (_) {
final result = _email.validateEmail();
return result.isValid ? null : result.errors.first.errorMessage;
},
),
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
onChanged: (value) => setState(() => _password = value),
validator: (_) {
final result = _password.validateMinLength(8);
return result.isValid ? null : result.errors.first.errorMessage;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_login();
}
},
child: Text('Login'),
),
],
),
);
}
}
Backend API Batch Validation
@Post('/users/batch')
Future<Response> createUsersBatch(List<UserDto> users) async {
final validator = UserValidator();
// Validate all users in parallel
final results = await validator.validateManyIsolate(users);
final validUsers = <UserDto>[];
final errors = <Map<String, dynamic>>[];
for (var i = 0; i < users.length; i++) {
if (results[i].isValid) {
validUsers.add(users[i]);
} else {
errors.add({
'index': i,
'user': users[i].toJson(),
'errors': results[i].errors.map((e) => {
'property': e.propertyName,
'message': e.errorMessage,
}).toList(),
});
}
}
await userRepository.createMany(validUsers);
return Response.json({
'created': validUsers.length,
'failed': errors.length,
'errors': errors,
});
}
📖 Complete Examples
Check the /example folder for complete working examples:
basic_example.dart- Simple validationadvanced_example.dart- Complex validations with all featuresisolate_validation_example.dart- Parallel processing examplesinline_validation_example.dart- Quick inline validations
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
📄 License
MIT License - see LICENSE file for details.
🙏 Acknowledgments
Inspired by FluentValidation for .NET
🇪🇸 Español
Una biblioteca de validación fluida para Dart inspirada en FluentValidation (.NET). Construye reglas de validación expresivas y fuertemente tipadas para tus aplicaciones Dart y Flutter con soporte para procesamiento paralelo mediante isolates.
✨ Características
- ✅ API Fluida - Reglas de validación encadenables y legibles
- ✅ Tipado seguro - Soporte completo para el sistema de tipos de Dart y null-safety
- ✅ Validadores ricos - Más de 56 validadores integrados para escenarios comunes
- ✅ Validadores avanzados - IP, MAC, GPS, Tarjeta de crédito, IBAN, ISBN, UUID, Códigos postales (129 países)
- ✅ Validadores de Fecha/Hora - Pasado/Futuro, Validación de edad, Rangos de fechas
- ✅ Documentos de Identidad - 26 validadores para 18 países (DNI, SSN, CPF, CURP, etc.)
- ✅ Procesamiento paralelo - Validación basada en isolates para lotes grandes (1000+ objetos)
- ✅ Validadores personalizados - Fácil agregar lógica de validación personalizada
- ✅ Validación asíncrona - Soporte integrado para validación asíncrona
- ✅ Reglas condicionales - Condiciones When/Unless para validación dinámica
- ✅ Validación anidada - Valida grafos de objetos complejos
- ✅ Validación de colecciones - Valida colecciones y sus elementos
- ✅ Validación en línea - Validación rápida sin crear clases validadoras
- ✅ Cero dependencias - Ligero sin dependencias externas
- ✅ Universal - Funciona con Dart VM, Flutter, Web y backend
📦 Instalación
Añade just_validation a tu pubspec.yaml:
dependencies:
just_validation: ^0.3.0
environment:
sdk: '>=2.19.0 <4.0.0'
Luego ejecuta:
dart pub get
# o
flutter pub get
🚀 Inicio Rápido
import 'package:just_validation/just_validation.dart';
// Define tu modelo
class Usuario {
final String? nombre;
final String? email;
final int? edad;
Usuario({this.nombre, this.email, this.edad});
}
// Crea un validador
class ValidadorUsuario extends AbstractValidator<Usuario> {
ValidadorUsuario() {
ruleFor((usuario) => usuario.nombre)
.notEmpty()
.withMessage('El nombre es requerido')
.minLength(2)
.maxLength(50);
ruleFor((usuario) => usuario.email)
.notEmpty()
.emailAddress()
.withMessage('Dirección de email inválida');
ruleFor((usuario) => usuario.edad)
.greaterThanOrEqual(18)
.withMessage('Debe ser mayor de 18 años')
.lessThan(120)
.when((usuario) => usuario.edad != null);
}
}
// Usa el validador
void main() {
final validador = ValidadorUsuario();
final usuario = Usuario(nombre: 'Juan', email: 'juan@example.com', edad: 25);
final resultado = validador.validate(usuario);
if (resultado.isValid) {
print('✅ Usuario válido!');
} else {
for (var error in resultado.errors) {
print('❌ ${error.propertyName}: ${error.errorMessage}');
}
}
}
⚡ Procesamiento Paralelo con Isolates
Para lotes grandes o validaciones computacionalmente costosas, usa validación basada en isolates:
// Valida 10,000 objetos sin bloquear el hilo principal
final usuarios = List.generate(10000, (i) => Usuario(...));
// En lugar de:
// final resultados = usuarios.map((u) => validador.validate(u)).toList(); // Bloquea UI
// Usa isolates:
final resultados = await validador.validateManyIsolate(usuarios); // ¡No bloqueante!
print('✅ Validados ${resultados.length} usuarios en paralelo');
Cuándo usar isolates:
- ✅ Procesamiento por lotes (1000+ objetos)
- ✅ Validaciones complejas (50+ reglas)
- ✅ No puede bloquear el hilo UI (apps Flutter)
- ✅ Procesamiento en segundo plano
Cuándo NO usar isolates:
- ❌ Formularios simples (1-100 campos)
- ❌ Validaciones rápidas (<50ms total)
- ❌ Lotes pequeños (<100 objetos)
📚 Documentación
Validadores de String
ruleFor((x) => x.nombre)
.notEmpty() // No debe estar vacío
.notNull() // No debe ser nulo
.minLength(2) // Longitud mínima
.maxLength(50) // Longitud máxima
.length(5, 20) // Longitud entre 5 y 20
.matches(RegExp(r'^[a-zA-Z]+$')) // Patrón regex
.emailAddress() // Email válido
.url() // URL válida
Validadores Numéricos
ruleFor((x) => x.edad)
.greaterThan(0) // > 0
.greaterThanOrEqual(18) // >= 18
.lessThan(100) // < 100
.lessThanOrEqual(99) // <= 99
.inclusiveBetween(18, 65) // Entre 18 y 65 (inclusivo)
.exclusiveBetween(0, 100) // Entre 0 y 100 (exclusivo)
Validadores de Comparación
ruleFor((x) => x.password)
.equal('password_confirmado') // Debe ser igual
.notEqual('password_antiguo') // No debe ser igual
Validadores Avanzados
// Dirección IP (IPv4, IPv6, o ambas)
ruleFor((x) => x.ip)
.ipAddress() // IPv4 o IPv6
.ipv4Address() // Solo IPv4
.ipv6Address() // Solo IPv6
// Dirección MAC
ruleFor((x) => x.mac)
.macAddress() // Soporta :, -, o sin separador
// Coordenadas GPS
ruleFor((x) => x.lat)
.latitude() // -90 a 90
ruleFor((x) => x.lng)
.longitude() // -180 a 180
// Tarjeta de Crédito (algoritmo Luhn)
ruleFor((x) => x.tarjeta)
.creditCard() // Visa, MC, Amex, etc.
// IBAN (52 países)
ruleFor((x) => x.iban)
.iban() // Validación completa Mod-97
// ISBN (10 o 13)
ruleFor((x) => x.isbn)
.isbn() // ISBN-10 o ISBN-13
// UUID (v1-v5)
ruleFor((x) => x.uuid)
.uuid() // Todas las versiones UUID
// Código Postal (129 países)
ruleFor((x) => x.cp)
.postalCode('US') // Códigos ZIP de EE.UU.
.postalCode('ES') // Códigos postales españoles
.postalCode('MX') // Códigos postales mexicanos
// Soporta 129 países en todos los continentes
Validadores de Fecha/Hora
ruleFor((x) => x.fechaNacimiento)
.isInPast() // Debe ser en el pasado
.minAge(18) // Debe tener 18+ años
.withMessage('Debe ser mayor de 18 años');
ruleFor((x) => x.fechaCita)
.isInFuture() // Debe ser en el futuro
.isBefore(DateTime(2025, 12, 31)) // Antes de fin de año
.isAfter(DateTime.now()); // Después de hoy
ruleFor((x) => x.fechaEvento)
.isBetween(fechaInicio, fechaFin) // Dentro del rango (inclusivo)
.isToday() // Debe ser hoy
.isSameDay(fechaReferencia); // Mismo día que referencia
Validadores de Documentos de Identidad
// Documentos Europeos
ruleFor((x) => x.documento)
.dni() // 🇪🇸 DNI Español
.nie() // 🇪🇸 NIE Español
.nin() // 🇬🇧 NIN Británico
.germanId() // 🇩🇪 Personalausweis Alemán
.codiceFiscale() // 🇮🇹 Codice Fiscale Italiano
.nif() // 🇵🇹 NIF Portugués
.bsn() // 🇳🇱 BSN Holandés
.belgianNationalNumber() // 🇧🇪 Número Nacional Belga
.pesel() // 🇵🇱 PESEL Polaco
.frenchNationalId(); // 🇫🇷 CNI Francés
// Documentos Americanos
ruleFor((x) => x.documento)
.ssn() // 🇺🇸 Social Security Number
.curp() // 🇲🇽 CURP Mexicano
.rfc() // 🇲🇽 RFC Mexicano
.cpf() // 🇧🇷 CPF Brasileño
.cnpj() // 🇧🇷 CNPJ Brasileño
.cuit() // 🇦🇷 CUIT/CUIL Argentino
.rut() // 🇨🇱 RUT Chileno
.sin() // 🇨🇦 SIN Canadiense
.colombianCc() // 🇨🇴 Cédula Colombiana
.ecuadorianCedula(); // 🇪🇨 Cédula Ecuatoriana
// Documentos Asiáticos
ruleFor((x) => x.documento)
.chineseId() // 🇨🇳 身份证 Chino (18 dígitos)
.aadhaar() // 🇮🇳 Aadhaar Indio
.pan() // 🇮🇳 PAN Indio
.tfn() // 🇦🇺 TFN Australiano
.nric(); // 🇸🇬 NRIC Singapurense
// Validador genérico con enum
ruleFor((x) => x.documento)
.nationalId(NationalIdType.esDni)
.nationalId(NationalIdType.fromCountry('BR', 'CPF')!);
Validadores Personalizados
// Predicado simple
ruleFor((x) => x.username)
.must((username) => !_nombresReservados.contains(username))
.withMessage('El nombre de usuario está reservado');
// Con contexto del objeto
ruleFor((x) => x.password)
.mustWith((password, usuario) => password != usuario.email)
.withMessage('La contraseña no puede ser tu email');
// Validación asíncrona
ruleFor((x) => x.email)
.mustAsync((email) async {
final existe = await _verificarEmailExiste(email);
return !existe;
}, 'El email ya existe');
Validación Condicional
// Solo validar si la condición es verdadera
ruleFor((x) => x.pasaporte)
.notEmpty()
.when((usuario) => usuario.pais != 'MX');
// Saltar validación si la condición es verdadera
ruleFor((x) => x.curp)
.notEmpty()
.unless((usuario) => usuario.esExtranjero);
Validación de Objetos Anidados
class Direccion {
final String? calle;
final String? ciudad;
Direccion({this.calle, this.ciudad});
}
class Persona {
final Direccion? direccion;
Persona({this.direccion});
}
class ValidadorDireccion extends AbstractValidator<Direccion> {
ValidadorDireccion() {
ruleFor((d) => d.calle).notEmpty();
ruleFor((d) => d.ciudad).notEmpty();
}
}
class ValidadorPersona extends AbstractValidator<Persona> {
ValidadorPersona() {
ruleFor((p) => p.direccion)
.setValidator(ValidadorDireccion());
}
}
Validación de Colecciones
class Pedido {
final List<ItemPedido> items;
Pedido({required this.items});
}
class ValidadorPedido extends AbstractValidator<Pedido> {
ValidadorPedido() {
// Validar la colección en sí
ruleFor((p) => p.items)
.notEmpty()
.withMessage('El pedido debe tener al menos un item');
// Validar cada item en la colección
ruleForEach((p) => p.items)
.setValidator(ValidadorItemPedido());
}
}
Validación en Línea
Validación rápida sin crear clases validadoras:
// Valor único
final resultado = 'juan@example.com'.validateEmail();
if (resultado.isValid) {
print('✅ Email válido');
}
// Con mensaje personalizado
final resultado = password.validateMinLength(8, 'Demasiado corto');
// Encadenar validaciones
final resultado = email
.validateNotEmpty()
.validateEmail()
.validateMaxLength(100);
// Obtener valor o lanzar excepción
try {
final emailValido = email.validateEmail().getOrThrow();
print('Usando: $emailValido');
} catch (e) {
print('Email inválido');
}
Modo Cascada
Controla cómo continúa la validación después de errores:
// Detener en el primer error (por defecto: Continue)
ruleFor((x) => x.password)
.notEmpty()
.minLength(8)
.matches(RegExp(r'[A-Z]'))
.cascadeMode(CascadeMode.stop); // Se detiene en el primer fallo
🚀 API de Validación con Isolates
Tres métodos para procesamiento paralelo:
// 1. Validación de un solo objeto en isolate
final resultado = await validador.validateIsolate(usuario);
// 2. Validación por lotes (más eficiente)
final resultados = await validador.validateManyIsolate(usuarios);
// 3. Validación asíncrona en isolate
final resultado = await validador.validateAsyncIsolate(usuario);
Rendimiento:
- Overhead: ~5-10ms por isolate
- Lote de 10,000 objetos: ~150ms (vs ~1000ms síncrono)
- Ideal para: Importaciones CSV, validación de Excel, endpoints de lote en API
📊 Códigos Postales Soportados
129 países organizados por región:
- América del Norte (12): US, CA, MX, CR, PA, GT, SV, HN, NI, CU, DO, PR
- América del Sur (10): BR, AR, CL, CO, PE, VE, EC, BO, PY, UY
- Europa Occidental (18): GB, FR, DE, ES, IT, NL, BE, CH, AT, PT, IE, LU, MC, AD, SM, LI, VA, GI
- Europa Nórdica (7): SE, NO, DK, FI, IS, FO, GL
- Europa Oriental (25): PL, CZ, SK, HU, RO, BG, HR, SI, EE, LV, LT, UA, BY, MD, RS, BA, MK, AL, GR, CY, MT, ME, XK
- Rusia y Asia Central (5): RU, KZ, AM, AZ, GE
- Asia Oriental (6): JP, CN, KR, TW, HK, MO
- Sudeste Asiático (10): TH, VN, MY, SG, ID, PH, MM, KH, LA, BN
- Asia del Sur (7): IN, PK, BD, LK, NP, BT, MV
- Oriente Medio (13): TR, IL, SA, AE, QA, KW, BH, OM, JO, LB, IQ, IR, PS
- África (13): ZA, EG, MA, DZ, TN, LY, KE, NG, ET, GH, MR, MU, LC
- Oceanía (5): AU, NZ, PG, FJ, VG
📈 Estadísticas
- Más de 56 validadores integrados
- 129 países con soporte de códigos postales
- 200+ tests (100% pasando)
- 93% de cobertura de código
- Cero dependencias
- Listo para producción
📖 Ejemplos Completos
Revisa la carpeta /example para ejemplos completos funcionales:
basic_example.dart- Validación simpleadvanced_example.dart- Validaciones complejas con todas las característicasisolate_validation_example.dart- Ejemplos de procesamiento paraleloinline_validation_example.dart- Validaciones rápidas en línea
🤝 Contribuir
¡Las contribuciones son bienvenidas! Por favor siéntete libre de enviar un Pull Request.
📄 Licencia
Licencia MIT - ver archivo LICENSE para detalles.
🙏 Agradecimientos
Inspirado en FluentValidation para .NET
Made with ❤️ for the Dart & Flutter community
Libraries
- just_validation
- A fluent validation library for Dart inspired by FluentValidation.