From 37ae04a4d8411daa5e8fc68137a2ffe8f4c9aaa3 Mon Sep 17 00:00:00 2001 From: Jacob Moura Date: Sun, 25 Aug 2024 11:38:44 -0300 Subject: [PATCH] Added `when` and `setValidator` --- CHANGELOG.md | 4 + README.md | 116 +++++++++++++-- .../lib/domain/validations/extensions.dart | 14 +- .../register_page/register_page.dart | 10 +- example/pubspec.lock | 26 ++-- lib/lucid_validation.dart | 1 + lib/src/lucid_validation_builder.dart | 133 +++++++++++++++--- lib/src/lucid_validator.dart | 76 ++++------ lib/src/types/types.dart | 4 +- ...dator_error.dart => validation_error.dart} | 8 +- lib/src/types/validation_result.dart | 22 +++ lib/src/types/validator_result.dart | 22 --- pubspec.yaml | 2 +- test/lucid_validation_test.dart | 54 +++++-- test/mocks/mocks.dart | 42 ++++++ 15 files changed, 395 insertions(+), 139 deletions(-) rename lib/src/types/{validator_error.dart => validation_error.dart} (80%) create mode 100644 lib/src/types/validation_result.dart delete mode 100644 lib/src/types/validator_result.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index c69404b..e99b64f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.7 + +* Added `when` and `setValidator` + ## 0.0.5 * Added must and mustWith diff --git a/README.md b/README.md index f236087..508856f 100644 --- a/README.md +++ b/README.md @@ -71,12 +71,12 @@ void main() { final user = UserModel(email: 'test@example.com', password: 'Passw0rd!', age: 25); final validator = UserValidator(); - final errors = validator.validate(user); + final result = validator.validate(user); - if (errors.isEmpty) { + if (result.isValid) { print('User is valid'); } else { - print('Validation errors: \${errors.map((e) => e.message).join(', ')}'); + print('Validation errors: \${result.errors.map((e) => e.message).join(', ')}'); } } ``` @@ -169,14 +169,114 @@ You can apply CascadeMode to your validation chain using the cascaded method: .mustHaveSpecialCharacter() ``` +## When condition + +Adds a conditional execution rule for the validation logic based on the given [condition]. + +The `when` method allows you to specify a condition that must be met for the validation rules +within this builder to be executed. If the condition is not met, the validation rules areskipped, +and the property is considered valid by default. + +This is particularly useful for scenarios where certain validation rules should only apply +under specific circumstances, such as when a certain property is set to a particular value. + +[condition] is a function that takes the entire entity and returns a boolean indicating whether +the validation rules should be applied. + +Example: + +```dart +ruleFor((user) => user.phoneNumber, key: 'phoneNumber') + .when((user) => user.requiresPhoneNumber) + .must((value) => value.isNotEmpty, 'Phone number is required', 'phone_required') + .must((value) => value.length == 10, 'Phone number must be 10 digits', 'phone_length'); +``` + +In the example above, the phone number validation rules are only applied if the user's`requiresPhoneNumber` +property is true. If the condition is false, the phone number field will be considered valid,and the +associated rules will not be executed. + +## Complex Validations + +When working with complex models that contain nested objects, it’s often necessary to apply validation rules not only to the parent model but also to its nested properties. The `setValidator` method allows you to integrate a nested `LucidValidator` within another validator, enabling a modular and scalable approach to validation. + +See this example: +```dart +// Models +class Customer { + String name; + Address address; + + Customer({ + required this.name, + required this.address, + }); +} + +class Address { + String country; + String postcode; + + Address({ + required this.country, + required this.postcode, + }); +} + +``` + +Now, we can create two validators, `CustomerValidator` and `AddressValidator`. +Use `setValidator` to integrate `AddressValidor` into `CustomerValidator`; + +```dart +class AddressValidator extends LucidValidator
{ + AddressValidator() { + ruleFor((address) => address.country, key: 'country') // + .notEmpty(); + + ruleFor((address) => address.postcode, key: 'postcode') // + .notEmpty(); + } +} + +class CustomerValidator extends LucidValidator { + final addressValidator = AddressValidator(); + + CustomerValidator() { + ruleFor((customer) => customer.name, key: 'name') // + .notEmpty(); + + ruleFor((customer) => customer.address, key: 'address') // + .setValidator(addressValidator); + } +} +``` + +After that, execute a validation normaly: + +```dart + var customer = Customer( + name: 'John Doe', + address: Address( + country: 'Brazil', + postcode: '12345-678', + ), + ); + + final validator = CustomerValidator(); + + var result = validator.validate(customer); + expect(result.isValid, isTrue); +``` + ## Creating Custom Rules -You can easily extend the functionality of `LucidValidation` by creating your own custom rules using `extensions`. Here’s an example of how to create a validation for phone numbers: +You can easily extend the functionality of `LucidValidator` by creating your own custom rules using `extensions`. Here’s an example of how to create a validation for phone numbers: ```dart -extension CustomValidPhoneValidator on LucidValidationBuilder { - LucidValidationBuilder customValidPhone({String message = 'Invalid phone number format'}) { +extension CustomValidPhoneValidator on SimpleValidationBuilder { + SimpleValidationBuilder customValidPhone({String message = 'Invalid phone number format'}) { return matchesPattern( r'^\(?(\d{2})\)?\s?9?\d{4}-?\d{4}\$', message, @@ -185,8 +285,8 @@ extension CustomValidPhoneValidator on LucidValidationBuilder { } } -extension CustomValidPasswordValidator on LucidValidationBuilder { - LucidValidationBuilder customValidPassword() { +extension CustomValidPasswordValidator on SimpleValidationBuilder { + SimpleValidationBuilder customValidPassword() { return notEmpty() .minLength(8) .mustHaveLowercase() diff --git a/example/lib/domain/validations/extensions.dart b/example/lib/domain/validations/extensions.dart index 1eec2a1..a2fb1c8 100644 --- a/example/lib/domain/validations/extensions.dart +++ b/example/lib/domain/validations/extensions.dart @@ -1,7 +1,7 @@ import 'package:lucid_validation/lucid_validation.dart'; -extension CustomValidPasswordValidator on LucidValidationBuilder { - LucidValidationBuilder customValidPassword() { +extension CustomValidPasswordValidator on SimpleValidationBuilder { + SimpleValidationBuilder customValidPassword() { return notEmpty() // .minLength(5) .mustHaveLowercase() @@ -11,8 +11,12 @@ extension CustomValidPasswordValidator on LucidValidationBuilder { - LucidValidationBuilder customValidPhone(String message) { - return must((value) => RegExp(r'^\(?(\d{2})\)?\s?9?\d{4}-?\d{4}$').hasMatch(value), message, 'invalid_phone'); +extension CustomValidPhoneValidator on SimpleValidationBuilder { + SimpleValidationBuilder customValidPhone(String message) { + return matchesPattern( + r'^\(?(\d{2})\)?\s?9?\d{4}-?\d{4}$', + message: message, + code: 'invalid_phone', + ); } } diff --git a/example/lib/presentation/register_page/register_page.dart b/example/lib/presentation/register_page/register_page.dart index 1fcaa5f..90b7fb5 100644 --- a/example/lib/presentation/register_page/register_page.dart +++ b/example/lib/presentation/register_page/register_page.dart @@ -40,13 +40,13 @@ class _RegisterPageState extends State { } void signIn() { - final errors = validator.validate(registerParamDto); + final result = validator.validate(registerParamDto); - if (errors.isEmpty) { + if (result.isValid) { /// call to api passing the parameter loginParamDto ScaffoldMessenger.of(context).showSnackBar(sucessSnackBar()); } else { - ScaffoldMessenger.of(context).showSnackBar(failureSnackBar(errors.first.message)); + ScaffoldMessenger.of(context).showSnackBar(failureSnackBar(result.errors.first.message)); } } @@ -104,10 +104,10 @@ class _RegisterPageState extends State { ListenableBuilder( listenable: registerParamDto, builder: (context, child) { - final errors = validator.validate(registerParamDto); + final result = validator.validate(registerParamDto); return ElevatedButton( - onPressed: errors.isEmpty ? signIn : null, + onPressed: result.isValid ? signIn : null, child: const Text('Register'), ); }, diff --git a/example/pubspec.lock b/example/pubspec.lock index c1ef826..003ad8d 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -79,18 +79,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -113,7 +113,7 @@ packages: path: ".." relative: true source: path - version: "0.0.5" + version: "0.0.6" matcher: dependency: transitive description: @@ -126,18 +126,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" path: dependency: transitive description: @@ -195,10 +195,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" vector_math: dependency: transitive description: @@ -211,10 +211,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" sdks: dart: ">=3.4.3 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/lib/lucid_validation.dart b/lib/lucid_validation.dart index abb87f3..d5b9ab6 100644 --- a/lib/lucid_validation.dart +++ b/lib/lucid_validation.dart @@ -54,6 +54,7 @@ /// library lucid_validation; +export 'src/lucid_validation_builder.dart'; export 'src/lucid_validator.dart'; export 'src/types/types.dart'; export 'src/validations/validations.dart'; diff --git a/lib/src/lucid_validation_builder.dart b/lib/src/lucid_validation_builder.dart index 75e14ac..163c45d 100644 --- a/lib/src/lucid_validation_builder.dart +++ b/lib/src/lucid_validation_builder.dart @@ -1,4 +1,4 @@ -part of 'lucid_validator.dart'; +import '../lucid_validation.dart'; /// Defines the behavior of rule execution when a validation failure occurs. /// @@ -45,17 +45,23 @@ enum CascadeMode { /// Builder class used to define validation rules for a specific property type [TProp]. /// /// [TProp] represents the type of the property being validated. -typedef RuleFunc = ValidatorResult Function(dynamic value, dynamic entity); +typedef RuleFunc = ValidationError? Function(Entity entity); -class LucidValidationBuilder { +typedef SimpleValidationBuilder = LucidValidationBuilder; + +abstract class LucidValidationBuilder { final String key; - final List> _rules = []; + final TProp Function(Entity entity) _selector; + final List> _rules = []; var _mode = CascadeMode.continueExecution; + LucidValidator? _nestedValidator; + + bool Function(Entity entity)? _condition; /// Creates a [LucidValidationBuilder] instance with an optional [key]. /// /// The [key] can be used to identify this specific validation in a larger validation context. - LucidValidationBuilder({this.key = ''}); + LucidValidationBuilder(this.key, this._selector); /// Registers a validation rule for the property. /// @@ -70,14 +76,17 @@ class LucidValidationBuilder { /// builder.must((username) => username.isNotEmpty, 'Username cannot be empty'); /// ``` LucidValidationBuilder must(bool Function(TProp value) validator, String message, String code) { - ValidatorResult callback(value, entity) => ValidatorResult( - isValid: validator(value), - error: ValidatorError( - message: message, - key: key, - code: code, - ), - ); + ValidationError? callback(entity) { + final value = _selector(entity); + if (validator(value)) { + return null; + } + return ValidationError( + message: message, + key: key, + code: code, + ); + } _rules.add(callback); @@ -110,14 +119,18 @@ class LucidValidationBuilder { String message, String code, ) { - ValidatorResult callback(value, entity) => ValidatorResult( - isValid: validator(value, entity), - error: ValidatorError( - message: message, - key: key, - code: code, - ), - ); + ValidationError? callback(entity) { + final value = _selector(entity); + if (validator(value, entity)) { + return null; + } + + return ValidationError( + message: message, + key: key, + code: code, + ); + } _rules.add(callback); @@ -150,4 +163,82 @@ class LucidValidationBuilder { _mode = mode; return this; } + + /// Allows the integration of another `LucidValidator` to validate nested properties. + /// + /// The `setValidator` method enables you to nest another `LucidValidator` within the current validation context. + /// This is particularly useful when dealing with complex models that contain nested objects or properties. + /// By setting a nested validator, you can apply validation rules to the properties of the nested object + /// within the context of the parent object. + /// + /// [validator] is an instance of `LucidValidator` that will be applied to the nested property. + /// + /// Example: + /// + /// ```dart + /// .ruleFor((user) => user.address, key: 'address') + /// .setValidator(AddressValidator()); // Integrating the nested validator + /// + /// ``` + void setValidator(LucidValidator validator) { + _nestedValidator = validator; + } + + /// Adds a conditional execution rule for the validation logic based on the given [condition]. + /// + /// The `when` method allows you to specify a condition that must be met for the validation rules + /// within this builder to be executed. If the condition is not met, the validation rules are skipped, + /// and the property is considered valid by default. + /// + /// This is particularly useful for scenarios where certain validation rules should only apply + /// under specific circumstances, such as when a certain property is set to a particular value. + /// + /// [condition] is a function that takes the entire entity and returns a boolean indicating whether + /// the validation rules should be applied. + /// + /// Example: + /// + /// ```dart + /// ruleFor((user) => user.phoneNumber, key: 'phoneNumber') + /// .when((user) => user.requiresPhoneNumber) + /// .must((value) => value.isNotEmpty, 'Phone number is required', 'phone_required') + /// .must((value) => value.length == 10, 'Phone number must be 10 digits', 'phone_length'); + /// ``` + /// + /// In the example above, the phone number validation rules are only applied if the user's `requiresPhoneNumber` + /// property is true. If the condition is false, the phone number field will be considered valid, and the + /// associated rules will not be executed. + LucidValidationBuilder when(bool Function(Entity entity) condition) { + _condition = condition; + return this; + } + + /// Executes all validation rules associated with this property and returns a list of [ValidationError]s. + List executeRules(Entity entity) { + final byPass = _condition?.call(entity) ?? true; + if (!byPass) { + return []; + } + + final errors = []; + + if (_nestedValidator != null) { + final nestedErrors = _nestedValidator!.validate(_selector(entity)).errors; + errors.addAll(nestedErrors); + } else { + for (var rule in _rules) { + final error = rule(entity); + + if (error != null) { + errors.add(error); + + if (_mode == CascadeMode.stopOnFirstFailure) { + break; + } + } + } + } + + return errors; + } } diff --git a/lib/src/lucid_validator.dart b/lib/src/lucid_validator.dart index e6de712..7d24a7a 100644 --- a/lib/src/lucid_validator.dart +++ b/lib/src/lucid_validator.dart @@ -1,19 +1,10 @@ import '../lucid_validation.dart'; -part 'lucid_validation_builder.dart'; - -class _PropSelector { - final TProp Function(E entity) selector; - final LucidValidationBuilder builder; - - _PropSelector({required this.selector, required this.builder}); -} - /// Abstract class for creating validation logic for a specific entity type [E]. /// /// [E] represents the type of the entity being validated. abstract class LucidValidator { - final List<_PropSelector> _propSelectors = []; + final List> _builders = []; /// Registers a validation rule for a specific property of the entity. /// @@ -27,11 +18,9 @@ abstract class LucidValidator { /// final validator = UserValidation(); /// validator.ruleFor((user) => user.email).validEmail(); /// ``` - LucidValidationBuilder ruleFor(TProp Function(E entity) func, {String key = ''}) { - final builder = LucidValidationBuilder(key: key); - final propSelector = _PropSelector(selector: func, builder: builder); - - _propSelectors.add(propSelector); + LucidValidationBuilder ruleFor(TProp Function(E entity) selector, {String key = ''}) { + final builder = _LucidValidationBuilder(key, selector); + _builders.add(builder); return builder; } @@ -46,31 +35,27 @@ abstract class LucidValidator { /// final emailValidator = validator.byField('email'); /// String? validationResult = emailValidator('user@example.com'); /// ``` - String? Function([String?])? byField(E entity, String key) { - final propSelector = _propSelectors + String? Function([String?]) byField(E entity, String key) { + final builder = _builders .where( - (propSelector) => propSelector.builder.key == key, + (builder) => builder.key == key, ) .firstOrNull; - if (propSelector == null) return null; + if (builder == null) { + return ([_]) => null; + } return ([_]) { - final builder = propSelector.builder; - final value = propSelector.selector(entity); - final rules = builder._rules.cast>(); - for (var rule in rules) { - final result = rule(value, entity); - - if (!result.isValid) { - return result.error.message; - } + final errors = builder.executeRules(entity); + if (errors.isNotEmpty) { + return errors.first.message; } return null; }; } - /// Validates the entire entity [E] and returns a list of [ValidatorError]s if any rules fail. + /// Validates the entire entity [E] and returns a list of [ValidationError]s if any rules fail. /// /// This method iterates through all registered rules and checks if the entity meets all of them. /// @@ -84,25 +69,18 @@ abstract class LucidValidator { /// print('Validation failed: ${errors.map((e) => e.message).join(', ')}'); /// } /// ``` - List validate(E entity) { - final List errors = []; - - for (var propSelector in _propSelectors) { - final propValue = propSelector.selector(entity); - final mode = propSelector.builder._mode; - - for (var rule in propSelector.builder._rules) { - final result = rule(propValue, entity); - - if (!result.isValid) { - errors.add(result.error); - if (mode == CascadeMode.stopOnFirstFailure) { - break; - } - } - } - } - - return errors; + ValidationResult validate(E entity) { + final errors = _builders.fold([], (previousErrors, errors) { + return previousErrors..addAll(errors.executeRules(entity)); + }); + + return ValidationResult( + isValid: errors.isEmpty, + errors: errors, + ); } } + +class _LucidValidationBuilder extends LucidValidationBuilder { + _LucidValidationBuilder(super.key, super.selector); +} diff --git a/lib/src/types/types.dart b/lib/src/types/types.dart index d423c7c..3b7f539 100644 --- a/lib/src/types/types.dart +++ b/lib/src/types/types.dart @@ -1,2 +1,2 @@ -export 'validator_error.dart'; -export 'validator_result.dart'; +export 'validation_error.dart'; +export 'validation_result.dart'; diff --git a/lib/src/types/validator_error.dart b/lib/src/types/validation_error.dart similarity index 80% rename from lib/src/types/validator_error.dart rename to lib/src/types/validation_error.dart index 97b3582..8c7d76e 100644 --- a/lib/src/types/validator_error.dart +++ b/lib/src/types/validation_error.dart @@ -1,8 +1,8 @@ /// Represents an error that occurs during validation. /// -/// [ValidatorError] provides details about the validation error, including +/// [ValidationError] provides details about the validation error, including /// an optional key that identifies which field or property the error is associated with. -class ValidatorError { +class ValidationError { /// The error message describing what went wrong during validation. final String message; @@ -12,11 +12,11 @@ class ValidatorError { /// An optional code that identifies the specific validation error. final String code; - /// Constructs a [ValidatorError]. + /// Constructs a [ValidationError]. /// /// [message] provides a description of the error. /// [key] optionally identifies the field or property related to the error. - const ValidatorError({ + const ValidationError({ required this.message, required this.code, this.key = '', diff --git a/lib/src/types/validation_result.dart b/lib/src/types/validation_result.dart new file mode 100644 index 0000000..eb5ccc2 --- /dev/null +++ b/lib/src/types/validation_result.dart @@ -0,0 +1,22 @@ +import 'validation_error.dart'; + +/// Represents the result of a validation rule. +/// +/// [ValidationResult] encapsulates whether the validation was successful +/// and, if not, provides the associated [ValidationError]. +class ValidationResult { + /// Indicates whether the validation was successful. + final bool isValid; + + /// Provides details about the validation error if the validation failed. + final List errors; + + /// Constructs a [ValidationResult]. + /// + /// [isValid] specifies whether the validation passed or failed. + /// [errors] provides the errors details in case of a validation failure. + const ValidationResult({ + required this.isValid, + required this.errors, + }); +} diff --git a/lib/src/types/validator_result.dart b/lib/src/types/validator_result.dart deleted file mode 100644 index 032319e..0000000 --- a/lib/src/types/validator_result.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'validator_error.dart'; - -/// Represents the result of a validation rule. -/// -/// [ValidatorResult] encapsulates whether the validation was successful -/// and, if not, provides the associated [ValidatorError]. -class ValidatorResult { - /// Indicates whether the validation was successful. - final bool isValid; - - /// Provides details about the validation error if the validation failed. - final ValidatorError error; - - /// Constructs a [ValidatorResult]. - /// - /// [isValid] specifies whether the validation passed or failed. - /// [error] provides the error details in case of a validation failure. - const ValidatorResult({ - required this.isValid, - required this.error, - }); -} diff --git a/pubspec.yaml b/pubspec.yaml index e7717fd..6f88ecf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: "A Dart/Flutter package for building strongly typed validation rule repository: https://github.com/Flutterando/lucid_validation homepage: https://pub.dev/documentation/lucid_validation/latest/ -version: 0.0.6 +version: 0.0.7 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/test/lucid_validation_test.dart b/test/lucid_validation_test.dart index e236fe1..5b93277 100644 --- a/test/lucid_validation_test.dart +++ b/test/lucid_validation_test.dart @@ -12,7 +12,8 @@ void main() { phone: '(11) 99999-9999', ); - final errors = validator.validate(userEntity); + final result = validator.validate(userEntity); + final errors = result.errors; expect(errors.length, 2); expect(errors.first.key, 'email'); @@ -29,7 +30,8 @@ void main() { phone: '(11) 99999-9999', ); - final errors = validator.validate(userEntity); + final result = validator.validate(userEntity); + final errors = result.errors; expect(errors.length, 6); expect(errors.first.key, 'password'); @@ -50,7 +52,8 @@ void main() { phone: '(11) 99999-9999', ); - final errors = validator.validate(userEntity); + final result = validator.validate(userEntity); + final errors = result.errors; expect(errors.length, 1); expect(errors.first.key, 'age'); @@ -66,7 +69,8 @@ void main() { phone: '', ); - final errors = validator.validate(userEntity); + final result = validator.validate(userEntity); + final errors = result.errors; expect(errors.length, 1); expect(errors.first.key, 'phone'); @@ -82,7 +86,8 @@ void main() { phone: '', ); - final errors = validator.validate(userEntity); + final result = validator.validate(userEntity); + final errors = result.errors; expect(errors.length, 10); expect( @@ -97,15 +102,46 @@ void main() { confirmPassword: '123asdASD@', password: '123asdASD@', ); - final registerValidator = CredentialsRegisterValidator(); + final validator = CredentialsRegisterValidator(); - var errors = registerValidator.validate(credentials); + var result = validator.validate(credentials); + var errors = result.errors; expect(errors.length, 0); credentials = credentials.copyWith(confirmPassword: '123asdASDsdsdw'); - errors = registerValidator.validate(credentials); - + result = validator.validate(credentials); + errors = result.errors; expect(errors.length, 2); + + final stringError = validator.byField(credentials, 'confirmPassword')(); + expect(stringError, 'Must be equal to password'); + }); + + test('setValidator', () { + var customer = Customer( + name: 'John Doe', + address: Address( + country: 'Brazil', + postcode: '12345-678', + ), + ); + + final validator = CustomerValidator(); + + var result = validator.validate(customer); + expect(result.isValid, isTrue); + + customer.address = Address( + country: 'Brazil', + postcode: '', + ); + + result = validator.validate(customer); + + expect(result.isValid, isFalse); + + final stringError = validator.byField(customer, 'address')(); + expect(stringError, 'Cannot be empty'); }); } diff --git a/test/mocks/mocks.dart b/test/mocks/mocks.dart index 3f4bc8b..8d6f494 100644 --- a/test/mocks/mocks.dart +++ b/test/mocks/mocks.dart @@ -93,3 +93,45 @@ class CredentialsRegisterValidator extends LucidValidator { .equalTo((entity) => entity.password, message: 'Must be equal to password'); } } + +class Address { + String country; + String postcode; + + Address({ + required this.country, + required this.postcode, + }); +} + +class AddressValidator extends LucidValidator
{ + AddressValidator() { + ruleFor((address) => address.country, key: 'country') // + .notEmpty(); + + ruleFor((address) => address.postcode, key: 'postcode') // + .notEmpty(); + } +} + +class Customer { + String name; + Address address; + + Customer({ + required this.name, + required this.address, + }); +} + +class CustomerValidator extends LucidValidator { + final addressValidator = AddressValidator(); + + CustomerValidator() { + ruleFor((customer) => customer.name, key: 'name') // + .notEmpty(); + + ruleFor((customer) => customer.address, key: 'address') // + .setValidator(addressValidator); + } +}