diff --git a/lib/src/lucid_validation_builder.dart b/lib/src/lucid_validation_builder.dart index 066df91..8a40470 100644 --- a/lib/src/lucid_validation_builder.dart +++ b/lib/src/lucid_validation_builder.dart @@ -63,6 +63,14 @@ abstract class LucidValidationBuilder { /// The [key] can be used to identify this specific validation in a larger validation context. LucidValidationBuilder(this.key, this._selector); + String? Function([String?]) nestedByField(Entity entity, String key) { + if (_nestedValidator == null) { + return ([_]) => null; + } + + return _nestedValidator!.byField(_selector(entity), key); + } + /// Registers a validation rule for the property. /// /// [validator] is a function that returns `true` if the property is valid and `false` otherwise. diff --git a/lib/src/lucid_validator.dart b/lib/src/lucid_validator.dart index 7d24a7a..2d913f0 100644 --- a/lib/src/lucid_validator.dart +++ b/lib/src/lucid_validator.dart @@ -18,7 +18,7 @@ abstract class LucidValidator { /// final validator = UserValidation(); /// validator.ruleFor((user) => user.email).validEmail(); /// ``` - LucidValidationBuilder ruleFor(TProp Function(E entity) selector, {String key = ''}) { + LucidValidationBuilder ruleFor(TProp Function(E entity) selector, {required String key}) { final builder = _LucidValidationBuilder(key, selector); _builders.add(builder); @@ -36,23 +36,39 @@ abstract class LucidValidator { /// String? validationResult = emailValidator('user@example.com'); /// ``` String? Function([String?]) byField(E entity, String key) { - final builder = _builders + if (key.contains('.')) { + final keys = key.split('.'); + + final firstKey = keys.removeAt(0); + final builder = _getBuilderByKey(firstKey); + if (builder == null) { + return ([_]) => null; + } + + return builder.nestedByField(entity, keys.join('.')); + } else { + final builder = _getBuilderByKey(key); + + if (builder == null) { + return ([_]) => null; + } + + return ([_]) { + final errors = builder.executeRules(entity); + if (errors.isNotEmpty) { + return errors.first.message; + } + return null; + }; + } + } + + LucidValidationBuilder? _getBuilderByKey(String key) { + return _builders .where( (builder) => builder.key == key, ) .firstOrNull; - - if (builder == null) { - return ([_]) => null; - } - - return ([_]) { - final errors = builder.executeRules(entity); - if (errors.isNotEmpty) { - return errors.first.message; - } - return null; - }; } /// Validates the entire entity [E] and returns a list of [ValidationError]s if any rules fail. @@ -70,8 +86,8 @@ abstract class LucidValidator { /// } /// ``` ValidationResult validate(E entity) { - final errors = _builders.fold([], (previousErrors, errors) { - return previousErrors..addAll(errors.executeRules(entity)); + final errors = _builders.fold([], (previousErrors, builder) { + return previousErrors..addAll(builder.executeRules(entity)); }); return ValidationResult( diff --git a/test/byfield_validation_test.dart b/test/byfield_validation_test.dart new file mode 100644 index 0000000..8560041 --- /dev/null +++ b/test/byfield_validation_test.dart @@ -0,0 +1,118 @@ +import 'package:lucid_validation/lucid_validation.dart'; +import 'package:test/test.dart'; + +import 'mocks/mocks.dart'; + +void main() { + test('byfield normal', () { + final validator = TestLucidValidator(); + + validator.ruleFor((model) => model.age, key: 'age').greaterThan(17); + validator.ruleFor((model) => model.email, key: 'email').notEmpty(); + + final user = UserModel()..age = 16; + + final ageValidator = validator.byField(user, 'age'); + + expect(ageValidator(), isNotNull); + + user.age = 18; + + expect(ageValidator(), null); + + final emailValidator = validator.byField(user, 'email'); + + expect(emailValidator(), isNotNull); + + user.email = 'test@gmail.com'; + + expect(emailValidator(), null); + }); + + group('nested byfield normal', () { + final flagValidator = TestLucidValidator(); + flagValidator.ruleFor((flag) => flag.value, key: 'value').equalTo((entity) => true); + + final categoryValidator = TestLucidValidator(); + categoryValidator.ruleFor((category) => category.name, key: 'name').notEmpty(); + categoryValidator.ruleFor((category) => category.flag, key: 'flag').setValidator(flagValidator); + + final productValidator = TestLucidValidator(); + productValidator.ruleFor((product) => product.name, key: 'name').notEmpty(); + productValidator.ruleFor((product) => product.price, key: 'price').greaterThan(0); + productValidator.ruleFor((product) => product.category, key: 'category').setValidator(categoryValidator); + + final product = ProductModel(); + + test('nameValidator', () { + final nameValidator = productValidator.byField(product, 'name'); + expect(nameValidator(), isNotNull); + }); + + test('priceValidator', () { + final priceValidator = productValidator.byField(product, 'price'); + expect(priceValidator(), isNotNull); + }); + + test('categoryValidator', () { + final categoryValidatorFn = productValidator.byField(product, 'category'); + expect(categoryValidatorFn(), isNotNull); + }); + + test('categoryNameValidator', () { + final categoryNameValidatorFn = productValidator.byField(product, 'category.name'); + expect(categoryNameValidatorFn(), isNotNull); + }); + + test('flagValidator', () { + final flagValidatorFn = productValidator.byField(product, 'category.flag'); + expect(flagValidatorFn(), isNotNull); + }); + + test('flagValueValidator', () { + final flagValueValidatorFn = productValidator.byField(product, 'category.flag.value'); + expect(flagValueValidatorFn(), isNotNull); + }); + + test('must return null if not found key', () { + final flagValueValidatorFn = productValidator.byField(product, 'category.flag.notExists'); + expect(flagValueValidatorFn(), null); + }); + + test('all validators', () { + product.name = 'Product'; + product.price = 10; + product.category.name = 'Category'; + product.category.flag.value = true; + + final nameValidator = productValidator.byField(product, 'name'); + final priceValidator = productValidator.byField(product, 'price'); + final categoryValidatorFn = productValidator.byField(product, 'category'); + final categoryNameValidatorFn = productValidator.byField(product, 'category.name'); + final flagValidatorFn = productValidator.byField(product, 'category.flag'); + final flagValueValidatorFn = productValidator.byField(product, 'category.flag.value'); + + expect(nameValidator(), null); + expect(priceValidator(), null); + expect(categoryValidatorFn(), null); + expect(categoryNameValidatorFn(), null); + expect(flagValidatorFn(), null); + expect(flagValueValidatorFn(), null); + }); + }); +} + +class ProductModel { + String name = ''; + double price = 0.0; + CategoryModel category = CategoryModel(); +} + +class CategoryModel { + String name = ''; + Flag flag = Flag(); +} + +class Flag { + bool value = false; +}