From 3695c0d31af35b0e5127167d617792b1b7326969 Mon Sep 17 00:00:00 2001 From: Wellington Santos Date: Mon, 18 Nov 2024 20:22:15 -0300 Subject: [PATCH 1/4] feature: initial proposal of 'is not null' --- lib/src/lucid_validation_builder.dart | 28 ++++++++++++++++++- lib/src/lucid_validator.dart | 20 +++++++++---- .../validations/is_not_null_validation.dart | 4 +-- test/mocks/mocks.dart | 3 +- .../valid_cpf_validation_test.dart | 6 ++-- 5 files changed, 49 insertions(+), 12 deletions(-) diff --git a/lib/src/lucid_validation_builder.dart b/lib/src/lucid_validation_builder.dart index de117c2..f0c2b9f 100644 --- a/lib/src/lucid_validation_builder.dart +++ b/lib/src/lucid_validation_builder.dart @@ -56,13 +56,14 @@ abstract class LucidValidationBuilder { final List> _rules = []; var _mode = CascadeMode.continueExecution; LucidValidator? _nestedValidator; + LucidValidator _lucid; 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, this.label, this._selector); + LucidValidationBuilder(this.key, this.label, this._selector, this._lucid); String? Function([String?]) nestedByField(Entity entity, String key) { if (_nestedValidator == null) { @@ -154,6 +155,26 @@ abstract class LucidValidationBuilder { return this; } + LucidValidationBuilder useNotNull( + ValidationException? Function(TProp value, Entity entity) rule) { + _rules.add((entity) => rule(_selector(entity), entity)); + + final builder = + _LucidValidationBuilder(key, label, (Entity entity) { + final value = _selector(entity) as T; + return value; + }, _lucid); + + this._mode = CascadeMode.stopOnFirstFailure; + + _lucid.addBuilder(builder); + return builder; + } + + CascadeMode getMode() { + return this._mode; + } + /// Sets the [CascadeMode] for the validation rules associated with this property. /// /// The [cascade] method allows you to control the behavior of rule execution when a validation failure occurs. @@ -261,3 +282,8 @@ abstract class LucidValidationBuilder { return exceptions; } } + +class _LucidValidationBuilder + extends LucidValidationBuilder { + _LucidValidationBuilder(super.key, super.label, super.selector, super.lucid); +} diff --git a/lib/src/lucid_validator.dart b/lib/src/lucid_validator.dart index 928fcf9..3f3c956 100644 --- a/lib/src/lucid_validator.dart +++ b/lib/src/lucid_validator.dart @@ -6,6 +6,10 @@ import '../lucid_validation.dart'; abstract class LucidValidator { final List> _builders = []; + addBuilder(LucidValidationBuilder builder) { + this._builders.add(builder); + } + /// Registers a validation rule for a specific property of the entity. /// /// [func] is a function that selects the property from the entity [E]. @@ -22,7 +26,7 @@ abstract class LucidValidator { TProp Function(E entity) selector, {required String key, String label = ''}) { - final builder = _LucidValidationBuilder(key, label, selector); + final builder = _LucidValidationBuilder(key, label, selector, this); _builders.add(builder); return builder; @@ -89,10 +93,14 @@ abstract class LucidValidator { /// } /// ``` ValidationResult validate(E entity) { - final exceptions = - _builders.fold([], (previousErrors, builder) { - return previousErrors..addAll(builder.executeRules(entity)); - }); + final List exceptions = []; + + for (var builder in _builders) { + exceptions.addAll(builder.executeRules(entity)); + if(builder.getMode() == CascadeMode.stopOnFirstFailure && exceptions.isNotEmpty) { + break; + } + } return ValidationResult( isValid: exceptions.isEmpty, @@ -103,5 +111,5 @@ abstract class LucidValidator { class _LucidValidationBuilder extends LucidValidationBuilder { - _LucidValidationBuilder(super.key, super.label, super.selector); + _LucidValidationBuilder(super.key, super.label, super.selector, super.lucid); } diff --git a/lib/src/validations/is_not_null_validation.dart b/lib/src/validations/is_not_null_validation.dart index b7fbf9b..1eaf12c 100644 --- a/lib/src/validations/is_not_null_validation.dart +++ b/lib/src/validations/is_not_null_validation.dart @@ -22,8 +22,8 @@ extension IsNotNullValidation on SimpleValidationBuilder { /// String format args: /// - **{PropertyName}**: The name of the property. /// - SimpleValidationBuilder isNotNull({String? message, String? code}) { - return use( + SimpleValidationBuilder isNotNull({String? message, String? code}) { + return useNotNull( (value, entity) { if (value != null) return null; diff --git a/test/mocks/mocks.dart b/test/mocks/mocks.dart index ba9877e..4ab893b 100644 --- a/test/mocks/mocks.dart +++ b/test/mocks/mocks.dart @@ -8,7 +8,7 @@ class UserModel { String? description; int age = 0; String phone = ''; - String cpf = ''; + String? cpf = ''; } class UserValidator extends LucidValidator { @@ -27,6 +27,7 @@ class UserValidator extends LucidValidator { .customValidPhone('Phone invalid format'); ruleFor((user) => user.cpf, key: 'cpf') // + .isNotNull() .notEmpty() .validCPF(); } diff --git a/test/src/validations/valid_cpf_validation_test.dart b/test/src/validations/valid_cpf_validation_test.dart index 5b2c896..cfa8417 100644 --- a/test/src/validations/valid_cpf_validation_test.dart +++ b/test/src/validations/valid_cpf_validation_test.dart @@ -9,9 +9,11 @@ void main() { validator .ruleFor((e) => e.cpf, key: 'cpf') // + .isNotNull() + .notEmpty() .validCPF(); - final user = UserModel()..cpf = '1'; + final user = UserModel()..cpf = null; final result = validator.validate(user); @@ -21,6 +23,6 @@ void main() { final error = result.exceptions.first; - expect(error.message, "'cpf' is not a valid CPF."); + expect(error.message, "'cpf' must not be null."); }); } From fd44bcd0574e16dcb3f7dc5fb34500c76ec0e739 Mon Sep 17 00:00:00 2001 From: Wellington Santos Date: Sun, 15 Dec 2024 19:37:01 -0300 Subject: [PATCH 2/4] feature: implement support nullable --- ...exclusive_between_datetime_validation.dart | 96 +++++++++++++ .../greater_than_datetime_validation.dart | 86 +++++++++++ ..._than_or_equal_to_datetime_validation.dart | 95 ++++++++++++- .../validations/greater_than_validation.dart | 86 +++++++++++ ...inclusive_between_datetime_validation.dart | 102 ++++++++++++++ lib/src/validations/is_empty_validation.dart | 72 ++++++++++ .../less_than_datetime_validation.dart | 86 +++++++++++ ..._than_or_equal_to_datetime_validation.dart | 97 ++++++++++++- lib/src/validations/less_then_validation.dart | 84 +++++++++++ .../matches_pattern_validation.dart | 89 ++++++++++++ .../validations/max_length_validation.dart | 88 ++++++++++++ lib/src/validations/max_validation.dart | 86 +++++++++++ .../validations/min_length_validation.dart | 86 +++++++++++ lib/src/validations/min_validation.dart | 86 +++++++++++ .../must_have_lowercase_validation.dart | 87 ++++++++++++ .../must_have_number_validation.dart | 85 +++++++++++ ...ust_have_special_character_validation.dart | 85 +++++++++++ .../must_have_uppercase_validation.dart | 85 +++++++++++ lib/src/validations/not_empty_validation.dart | 77 ++++++++++ lib/src/validations/valid_cep_validation.dart | 81 +++++++++++ .../validations/valid_cnpj_validation.dart | 133 ++++++++++++++---- lib/src/validations/valid_cpf_validation.dart | 115 ++++++++++++--- .../valid_creditcard_validation.dart | 120 +++++++++++++--- .../validations/valid_email_validation.dart | 79 +++++++++++ .../valid_cpf_validation_test.dart | 4 +- 25 files changed, 2119 insertions(+), 71 deletions(-) diff --git a/lib/src/validations/exclusive_between_datetime_validation.dart b/lib/src/validations/exclusive_between_datetime_validation.dart index f1c42d9..0bb9617 100644 --- a/lib/src/validations/exclusive_between_datetime_validation.dart +++ b/lib/src/validations/exclusive_between_datetime_validation.dart @@ -51,3 +51,99 @@ extension ExclusiveBetweenDatetimeValidation }); } } + +extension ExclusiveBetweenDatetimeNullableValidation +on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [DateTime?] is greater than [comparison]. + /// + /// [start] is the date and time value must be greater than. + /// [end] is the date and time value must be less than. + /// [message] is the error message returned if the validation fails. + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// .ruleFor((event) => event.start, key: 'start') // event.start is nullable + /// .exclusiveBetween(start: DateTime.now(), end: DateTime.now().add(Duration(days: 1))); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{StartValue}**: The value must be greater than or equal to. + /// - **{EndValue}**: The value must be less than or equal to. + /// + SimpleValidationBuilder exclusiveBetween({ + required DateTime start, + required DateTime end, + String? message, + String? code, + }) { + return use((value, entity) { + if (value != null && (value.isAfter(start) && value.isBefore(end))) return null; + + final currentCode = code ?? Language.code.exclusiveBetweenDatetime; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'StartValue': start.toString(), + 'EndValue': end.toString(), + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} + +extension ExclusiveBetweenDatetimeOrNullableValidation +on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [DateTime?] is greater than [comparison] or [null]. + /// + /// [start] is the date and time value must be greater than. + /// [end] is the date and time value must be less than. + /// [message] is the error message returned if the validation fails. + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// .ruleFor((event) => event.start, key: 'start') // + /// .exclusiveBetweenOrNull(start: DateTime.now(), end: DateTime.now().add(Duration(days: 1))); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{StartValue}**: The value must be greater than or equal to. + /// - **{EndValue}**: The value must be less than or equal to. + /// + SimpleValidationBuilder exclusiveBetweenOrNull({ + required DateTime start, + required DateTime end, + String? message, + String? code, + }) { + return use((value, entity) { + if (value == null || (value.isAfter(start) && value.isBefore(end))) return null; + + final currentCode = code ?? Language.code.exclusiveBetweenDatetime; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'StartValue': start.toString(), + 'EndValue': end.toString(), + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} diff --git a/lib/src/validations/greater_than_datetime_validation.dart b/lib/src/validations/greater_than_datetime_validation.dart index f18db12..e49c9fb 100644 --- a/lib/src/validations/greater_than_datetime_validation.dart +++ b/lib/src/validations/greater_than_datetime_validation.dart @@ -46,3 +46,89 @@ extension GreaterThanDateTimeValidation on SimpleValidationBuilder { }); } } + +extension GreaterThanDateTimeNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [DateTime?] is greater than [comparison]. + /// + /// [comparison] is the date and time value must be greater than. + /// [message] is the error message returned if the validation fails. + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// .ruleFor((event) => event.start, key: 'start') // event.start is nullable + /// .greaterThan(DateTime.now()); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{ComparisonValue}**: The value to compare against. + /// + SimpleValidationBuilder greaterThan( + DateTime comparison, { + String? message, + String? code, + }) { + return use((value, entity) { + if (value != null && value.isAfter(comparison)) return null; + + final currentCode = code ?? Language.code.greaterThanDatetime; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'ComparisonValue': comparison.toString(), + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} + +extension GreaterThanDateTimeOrNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [DateTime?] is greater than [comparison] or [null]. + /// + /// [comparison] is the date and time value must be greater than. + /// [message] is the error message returned if the validation fails. + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// .ruleFor((event) => event.start, key: 'start') // + /// .greaterThanOrNull(DateTime.now()); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{ComparisonValue}**: The value to compare against. + /// + SimpleValidationBuilder greaterThanOrNull( + DateTime comparison, { + String? message, + String? code, + }) { + return use((value, entity) { + if (value == null || value.isAfter(comparison)) return null; + + final currentCode = code ?? Language.code.greaterThanDatetime; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'ComparisonValue': comparison.toString(), + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} diff --git a/lib/src/validations/greater_than_or_equal_to_datetime_validation.dart b/lib/src/validations/greater_than_or_equal_to_datetime_validation.dart index 6832e74..8792a60 100644 --- a/lib/src/validations/greater_than_or_equal_to_datetime_validation.dart +++ b/lib/src/validations/greater_than_or_equal_to_datetime_validation.dart @@ -31,8 +31,101 @@ extension GreaterThanOrEqualToDateTimeValidation String? code, }) { return use((value, entity) { - if (value.isAfter(comparison) || value.isAtSameMomentAs(comparison)) + if (value.isAfter(comparison) || value.isAtSameMomentAs(comparison)) { return null; + } + + final currentCode = code ?? Language.code.greaterThanOrEqualToDateTime; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'ComparisonValue': comparison.toString(), + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} + +extension GreaterThanOrEqualToDateTimeNullableValidation +on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [DateTime?] is greater than [comparison]. + /// + /// [comparison] is the date and time value must be greater than or equal. + /// [message] is the error message returned if the validation fails. + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// .ruleFor((event) => event.start, key: 'start') // event.start is nullable + /// .greaterThanOrEqualTo(DateTime.now()); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{ComparisonValue}**: The value to compare against. + /// + SimpleValidationBuilder greaterThanOrEqualTo( + DateTime comparison, { + String? message, + String? code, + }) { + return use((value, entity) { + if (value != null && (value.isAfter(comparison) || value.isAtSameMomentAs(comparison))) { + return null; + } + + final currentCode = code ?? Language.code.greaterThanOrEqualToDateTime; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'ComparisonValue': comparison.toString(), + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} + +extension GreaterThanOrEqualToDateTimeOrNullableValidation +on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [DateTime?] is greater than [comparison] or [null]. + /// + /// [comparison] is the date and time value must be greater than or equal. + /// [message] is the error message returned if the validation fails. + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// .ruleFor((event) => event.start, key: 'start') // + /// .greaterThanOrEqualToOrNull(DateTime.now()); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{ComparisonValue}**: The value to compare against. + /// + SimpleValidationBuilder greaterThanOrEqualToOrNull( + DateTime comparison, { + String? message, + String? code, + }) { + return use((value, entity) { + if (value == null || (value.isAfter(comparison) || value.isAtSameMomentAs(comparison))) { + return null; + } final currentCode = code ?? Language.code.greaterThanOrEqualToDateTime; final currentMessage = LucidValidation.global.languageManager.translate( diff --git a/lib/src/validations/greater_than_validation.dart b/lib/src/validations/greater_than_validation.dart index 2bb0c2f..ead504f 100644 --- a/lib/src/validations/greater_than_validation.dart +++ b/lib/src/validations/greater_than_validation.dart @@ -46,3 +46,89 @@ extension GreaterThanValidation on SimpleValidationBuilder { }); } } + +extension GreaterThanNullablealidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [num?] is greater than [minValue]. + /// + /// [minValue] is the value that the number must be greater than. + /// [message] is the error message returned if the validation fails. + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.age, key: 'age') // user.age is nullable + /// .greaterThan(18); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{ComparisonValue}**: The value to compare against. + /// + SimpleValidationBuilder greaterThan( + num minValue, { + String? message, + String? code, + }) { + return use((value, entity) { + if (value != null && value > minValue) return null; + + final currentCode = code ?? Language.code.greaterThan; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'ComparisonValue': '$minValue', + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} + +extension GreaterThanOrNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [num?] is greater than [minValue] or [null]. + /// + /// [minValue] is the value that the number must be greater than. + /// [message] is the error message returned if the validation fails. + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.age, key: 'age') + /// .greaterThanOrNull(18); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{ComparisonValue}**: The value to compare against. + /// + SimpleValidationBuilder greaterThanOrNull( + num minValue, { + String? message, + String? code, + }) { + return use((value, entity) { + if (value == null || value > minValue) return null; + + final currentCode = code ?? Language.code.greaterThan; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'ComparisonValue': '$minValue', + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} diff --git a/lib/src/validations/inclusive_between_datetime_validation.dart b/lib/src/validations/inclusive_between_datetime_validation.dart index e93f267..368313e 100644 --- a/lib/src/validations/inclusive_between_datetime_validation.dart +++ b/lib/src/validations/inclusive_between_datetime_validation.dart @@ -53,3 +53,105 @@ extension InclusiveBetweenDatetimeValidation }); } } + +extension InclusiveBetweenDatetimeNullableValidation + on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [DateTime?] is greater than [comparison]. + /// + /// [start] is the date and time value must be greater than or equal to. + /// [end] is the date and time value must be less than or equal to. + /// [message] is the error message returned if the validation fails. + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// .ruleFor((event) => event.start, key: 'start') // event.start is nullable + /// .inclusiveBetween(start: DateTime.now(), end: DateTime.now().add(Duration(days: 1))); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{StartValue}**: The value must be greater than or equal to. + /// - **{EndValue}**: The value must be less than or equal to. + /// + SimpleValidationBuilder inclusiveBetween({ + required DateTime start, + required DateTime end, + String? message, + String? code, + }) { + return use((value, entity) { + if (value != null && + (value.isAfter(start) || + value.isAtSameMomentAs(start) && value.isBefore(end) || + value.isAtSameMomentAs(end))) return null; + + final currentCode = code ?? Language.code.inclusiveBetweenDatetime; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'StartValue': start.toString(), + 'EndValue': end.toString(), + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} + +extension InclusiveBetweenDatetimeOrNullableValidation + on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [DateTime?] is greater than [comparison] or [null]. + /// + /// [start] is the date and time value must be greater than or equal to. + /// [end] is the date and time value must be less than or equal to. + /// [message] is the error message returned if the validation fails. + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// .ruleFor((event) => event.start, key: 'start') // + /// .inclusiveBetweenOrNull(start: DateTime.now(), end: DateTime.now().add(Duration(days: 1))); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{StartValue}**: The value must be greater than or equal to. + /// - **{EndValue}**: The value must be less than or equal to. + /// + SimpleValidationBuilder inclusiveBetweenOrNull({ + required DateTime start, + required DateTime end, + String? message, + String? code, + }) { + return use((value, entity) { + if (value == null || + (value.isAfter(start) || + value.isAtSameMomentAs(start) && value.isBefore(end) || + value.isAtSameMomentAs(end))) return null; + + final currentCode = code ?? Language.code.inclusiveBetweenDatetime; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'StartValue': start.toString(), + 'EndValue': end.toString(), + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} diff --git a/lib/src/validations/is_empty_validation.dart b/lib/src/validations/is_empty_validation.dart index 8d3cbf3..24c4b09 100644 --- a/lib/src/validations/is_empty_validation.dart +++ b/lib/src/validations/is_empty_validation.dart @@ -39,3 +39,75 @@ extension IsEmptyValidation on SimpleValidationBuilder { }); } } + +extension IsEmptyNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] is empty. + /// + /// [message] is the error message returned if the validation fails. + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.name, key: 'name') // user.name is nullable + /// .isEmpty(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder isEmpty({String? message, String? code}) { + return use((value, entity) { + if (value != null && value.isEmpty) return null; + + final currentCode = code ?? Language.code.isEmpty; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} + +extension IsEmptyOrNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] is empty. + /// + /// [message] is the error message returned if the validation fails. + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.name, key: 'name') + /// .isEmptyOrNull(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder isEmptyOrNull({String? message, String? code}) { + return use((value, entity) { + if (value == null || value.isEmpty) return null; + + final currentCode = code ?? Language.code.isEmpty; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} diff --git a/lib/src/validations/less_than_datetime_validation.dart b/lib/src/validations/less_than_datetime_validation.dart index b056f31..e89492e 100644 --- a/lib/src/validations/less_than_datetime_validation.dart +++ b/lib/src/validations/less_than_datetime_validation.dart @@ -46,3 +46,89 @@ extension LessThanDatetimeValidation on SimpleValidationBuilder { }); } } + +extension LessThanDatetimeNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [DateTime?] is greater than [comparison]. + /// + /// [comparison] is the date and time value must be less than. + /// [message] is the error message returned if the validation fails. + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// .ruleFor((event) => event.end, key: 'start') // event.end is nullable + /// .lessThan(DateTime.now()); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{ComparisonValue}**: The value to compare against. + /// + SimpleValidationBuilder lessThan( + DateTime comparison, { + String? message, + String? code, + }) { + return use((value, entity) { + if (value != null && value.isBefore(comparison)) return null; + + final currentCode = code ?? Language.code.lessThanDateTime; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'ComparisonValue': comparison.toString(), + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} + +extension LessThanDatetimeOrNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [DateTime?] is greater than [comparison] or [null]. + /// + /// [comparison] is the date and time value must be less than. + /// [message] is the error message returned if the validation fails. + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// .ruleFor((event) => event.end, key: 'start') // + /// .lessThanOrNull(DateTime.now()); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{ComparisonValue}**: The value to compare against. + /// + SimpleValidationBuilder lessThanOrNull( + DateTime comparison, { + String? message, + String? code, + }) { + return use((value, entity) { + if (value == null || value.isBefore(comparison)) return null; + + final currentCode = code ?? Language.code.lessThanDateTime; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'ComparisonValue': comparison.toString(), + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} diff --git a/lib/src/validations/less_than_or_equal_to_datetime_validation.dart b/lib/src/validations/less_than_or_equal_to_datetime_validation.dart index 123e598..f29a4e1 100644 --- a/lib/src/validations/less_than_or_equal_to_datetime_validation.dart +++ b/lib/src/validations/less_than_or_equal_to_datetime_validation.dart @@ -31,8 +31,103 @@ extension LessThanOrEqualToDatetimeValidation String? code, }) { return use((value, entity) { - if (value.isBefore(comparison) || value.isAtSameMomentAs(comparison)) + if (value.isBefore(comparison) || value.isAtSameMomentAs(comparison)) { return null; + } + + final currentCode = code ?? Language.code.lessThanOrEqualToDateTime; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'ComparisonValue': comparison.toString(), + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} + +extension LessThanOrEqualToDatetimeNullableValidation + on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [DateTime?] is greater than [comparison]. + /// + /// [comparison] is the date and time value must be less than or equal to. + /// [message] is the error message returned if the validation fails. + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// .ruleFor((event) => event.end, key: 'start') // event.end is nullable + /// .lessThanOrEqualTo(DateTime.now()); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{ComparisonValue}**: The value to compare against. + /// + SimpleValidationBuilder lessThanOrEqualTo( + DateTime comparison, { + String? message, + String? code, + }) { + return use((value, entity) { + if (value != null && + (value.isBefore(comparison) || value.isAtSameMomentAs(comparison))) { + return null; + } + + final currentCode = code ?? Language.code.lessThanOrEqualToDateTime; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'ComparisonValue': comparison.toString(), + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} + +extension LessThanOrEqualToDatetimeOrNullableValidation + on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [DateTime] is greater than [comparison] or [null]. + /// + /// [comparison] is the date and time value must be less than or equal to. + /// [message] is the error message returned if the validation fails. + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// .ruleFor((event) => event.end, key: 'start') // + /// .lessThanOrEqualToOrNull(DateTime.now()); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{ComparisonValue}**: The value to compare against. + /// + SimpleValidationBuilder lessThanOrEqualToOrNull( + DateTime comparison, { + String? message, + String? code, + }) { + return use((value, entity) { + if (value == null || + (value.isBefore(comparison) || value.isAtSameMomentAs(comparison))) { + return null; + } final currentCode = code ?? Language.code.lessThanOrEqualToDateTime; final currentMessage = LucidValidation.global.languageManager.translate( diff --git a/lib/src/validations/less_then_validation.dart b/lib/src/validations/less_then_validation.dart index 7c3a558..01804aa 100644 --- a/lib/src/validations/less_then_validation.dart +++ b/lib/src/validations/less_then_validation.dart @@ -45,3 +45,87 @@ extension LessThanValidation on SimpleValidationBuilder { ); } } + +extension LessThanNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [num?] is less than [maxValue]. + /// + /// [maxValue] is the value that the number must be less than. + /// [message] is the error message returned if the validation fails. Defaults to "Must be less than $maxValue". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.discount, key: 'discount') // user.discount is nullable + /// .lessThan(100); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{ComparisonValue}**: The value to compare against. + /// + SimpleValidationBuilder lessThan(num maxValue, + {String? message, String? code}) { + return use( + (value, entity) { + if (value != null && value < maxValue) return null; + + final currentCode = code ?? Language.code.lessThan; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'ComparisonValue': '$maxValue', + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} + +extension LessThanOrNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [num?] is less than [maxValue] or [null]. + /// + /// [maxValue] is the value that the number must be less than. + /// [message] is the error message returned if the validation fails. Defaults to "Must be less than $maxValue". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.discount, key: 'discount') + /// .lessThanOrNull(100); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{ComparisonValue}**: The value to compare against. + /// + SimpleValidationBuilder lessThanOrNull(num maxValue, + {String? message, String? code}) { + return use( + (value, entity) { + if (value == null || value < maxValue) return null; + + final currentCode = code ?? Language.code.lessThan; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'ComparisonValue': '$maxValue', + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} diff --git a/lib/src/validations/matches_pattern_validation.dart b/lib/src/validations/matches_pattern_validation.dart index ce60e7b..bce679d 100644 --- a/lib/src/validations/matches_pattern_validation.dart +++ b/lib/src/validations/matches_pattern_validation.dart @@ -45,3 +45,92 @@ extension MatchesPatternValidation on SimpleValidationBuilder { ); } } + +extension MatchesPatternNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] matches the [pattern]. + /// + /// [pattern] is the regex pattern that the string must match. + /// [message] is the error message returned if the validation fails. Defaults to "Invalid format". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.phoneNumber, key: 'phoneNumber') // user.phoneNumber is nullable + /// .matchesPattern(r'^\d{3}-\d{3}-\d{4}$'); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder matchesPattern(String pattern, + {String? message, String? code}) { + return use( + (value, entity) { + if (value != null) { + final isValid = RegExp(pattern).hasMatch(value); + + if (isValid) return null; + } + + final currentCode = code ?? Language.code.matchesPattern; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} + +extension MatchesPatternOrNullableValidation + on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] matches the [pattern] or [null]. + /// + /// [pattern] is the regex pattern that the string must match. + /// [message] is the error message returned if the validation fails. Defaults to "Invalid format". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.phoneNumber, key: 'phoneNumber') + /// .matchesPatternOrNull(r'^\d{3}-\d{3}-\d{4}$'); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder matchesPatternOrNull(String pattern, + {String? message, String? code}) { + return use( + (value, entity) { + if (value == null) return null; + + final isValid = RegExp(pattern).hasMatch(value); + + if (isValid) return null; + + final currentCode = code ?? Language.code.matchesPattern; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} diff --git a/lib/src/validations/max_length_validation.dart b/lib/src/validations/max_length_validation.dart index e93adec..7b562f9 100644 --- a/lib/src/validations/max_length_validation.dart +++ b/lib/src/validations/max_length_validation.dart @@ -47,3 +47,91 @@ extension MaxLengthValidation on SimpleValidationBuilder { ); } } + +extension MaxLengthNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the length of a [String?] is less than or equal to [num]. + /// + /// [num] is the maximum allowed length for the string. + /// [message] is the error message returned if the validation fails. Defaults to "Must be at most $num characters long". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.username, key: 'username') // user.username is nullable + /// .maxLength(10); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{MaxLength}**: The value to compare against. + /// - **{TotalLength}**: total characters entered. + /// + SimpleValidationBuilder maxLength(int num, + {String? message, String? code}) { + return use( + (value, entity) { + if (value != null && value.length <= num) return null; + + final currentCode = code ?? Language.code.maxLength; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'MaxLength': '$num', + 'TotalLength': '${value != null ? value.length : 0}', + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} + +extension MaxLengthOrNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the length of a [String?] is less than or equal to [num] or [null]. + /// + /// [num] is the maximum allowed length for the string. + /// [message] is the error message returned if the validation fails. Defaults to "Must be at most $num characters long". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.username, key: 'username') + /// .maxLengthOrNull(10); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{MaxLength}**: The value to compare against. + /// - **{TotalLength}**: total characters entered. + /// + SimpleValidationBuilder maxLengthOrNull(int num, + {String? message, String? code}) { + return use( + (value, entity) { + if (value == null || value.length <= num) return null; + + final currentCode = code ?? Language.code.maxLength; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'MaxLength': '$num', + 'TotalLength': '${value.length}', + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} diff --git a/lib/src/validations/max_validation.dart b/lib/src/validations/max_validation.dart index 698107d..3a65abd 100644 --- a/lib/src/validations/max_validation.dart +++ b/lib/src/validations/max_validation.dart @@ -46,3 +46,89 @@ extension MaxValidation on SimpleValidationBuilder { ); } } + +extension MaxNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if a [num?] value is less than or equal to [num]. + /// + /// [num] is the maximum allowed value. + /// [message] is the error message returned if the validation fails. Defaults to "Must be less than or equal to $num". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.age, key: 'age') // user.age is nullable + /// .maxLength(18); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{MaxValue}**: The maximum value. + /// - **{PropertyValue}**: value entered. + /// + SimpleValidationBuilder max(num num, {String? message, String? code}) { + return use( + (value, entity) { + if (value != null && value <= num) return null; + + final currentCode = code ?? Language.code.max; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'MaxValue': '$num', + 'PropertyValue': '$value', + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} + +extension MaxOrNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if a [num?] value is less than or equal to [num] or [null]. + /// + /// [num] is the maximum allowed value. + /// [message] is the error message returned if the validation fails. Defaults to "Must be less than or equal to $num". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.age, key: 'age') + /// .maxOrNull(18); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{MaxValue}**: The maximum value. + /// - **{PropertyValue}**: value entered. + /// + SimpleValidationBuilder maxOrNull(num num, {String? message, String? code}) { + return use( + (value, entity) { + if (value == null || value <= num) return null; + + final currentCode = code ?? Language.code.max; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'MaxValue': '$num', + 'PropertyValue': '$value', + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} diff --git a/lib/src/validations/min_length_validation.dart b/lib/src/validations/min_length_validation.dart index 9157751..228807e 100644 --- a/lib/src/validations/min_length_validation.dart +++ b/lib/src/validations/min_length_validation.dart @@ -46,3 +46,89 @@ extension MinLengthValidation on SimpleValidationBuilder { ); } } + +extension MinLengthNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the length of a [String?] is greater than or equal to [num]. + /// + /// [num] is the minimum required length for the string. + /// [message] is the error message returned if the validation fails. Defaults to "Must be at least $num characters long". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.password, key: 'password') // user.password is nullable + /// .maxLength(8); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{MinLength}**: The value to compare against. + /// - **{TotalLength}**: total characters entered. + SimpleValidationBuilder minLength(int num, + {String? message, String? code}) { + return use( + (value, entity) { + if (value != null && value.length >= num) return null; + + final currentCode = code ?? Language.code.minLength; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'MinLength': '$num', + 'TotalLength': '${value != null ? value.length : 0}', + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} + +extension MinLengthOrNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the length of a [String?] is greater than or equal to [num] or [null]. + /// + /// [num] is the minimum required length for the string. + /// [message] is the error message returned if the validation fails. Defaults to "Must be at least $num characters long". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.password, key: 'password') + /// .minLengthOrNull(8); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{MinLength}**: The value to compare against. + /// - **{TotalLength}**: total characters entered. + SimpleValidationBuilder minLengthOrNull(int num, + {String? message, String? code}) { + return use( + (value, entity) { + if (value == null || value.length >= num) return null; + + final currentCode = code ?? Language.code.minLength; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'MinLength': '$num', + 'TotalLength': '${value.length}', + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} diff --git a/lib/src/validations/min_validation.dart b/lib/src/validations/min_validation.dart index 94d52a8..751e1d6 100644 --- a/lib/src/validations/min_validation.dart +++ b/lib/src/validations/min_validation.dart @@ -46,3 +46,89 @@ extension MinValidation on SimpleValidationBuilder { ); } } + +extension MinNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if a [num?] value is greater than or equal to [num]. + /// + /// [num] is the minimum allowed value. + /// [message] is the error message returned if the validation fails. Defaults to "Must be greater than or equal to $num". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.age, key: 'age') + /// .maxLength(18); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{MinValue}**: The minimum value. + /// - **{PropertyValue}**: value entered. + /// + SimpleValidationBuilder min(num num, {String? message, String? code}) { + return use( + (value, entity) { + if (value != null && value >= num) return null; + + final currentCode = code ?? Language.code.min; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'MinValue': '$num', + 'PropertyValue': '$value', + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} + +extension MinOrNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if a [num?] value is greater than or equal to [num] or [null]. + /// + /// [num] is the minimum allowed value. + /// [message] is the error message returned if the validation fails. Defaults to "Must be greater than or equal to $num". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.age, key: 'age') + /// .minOrNull(18); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{MinValue}**: The minimum value. + /// - **{PropertyValue}**: value entered. + /// + SimpleValidationBuilder minOrNull(num num, {String? message, String? code}) { + return use( + (value, entity) { + if (value == null || value >= num) return null; + + final currentCode = code ?? Language.code.min; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'MinValue': '$num', + 'PropertyValue': '$value', + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} diff --git a/lib/src/validations/must_have_lowercase_validation.dart b/lib/src/validations/must_have_lowercase_validation.dart index a8bbb65..b4ec5b6 100644 --- a/lib/src/validations/must_have_lowercase_validation.dart +++ b/lib/src/validations/must_have_lowercase_validation.dart @@ -44,3 +44,90 @@ extension MustHaveLowercaseValidation on SimpleValidationBuilder { ); } } + +extension MustHaveLowercaseNullableValidation + on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] contains at least one lowercase letter. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Must contain at least one lowercase letter". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.password, key: 'password') // user.password is nullable + /// .mustHaveLowercase(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + /// '{PropertyName}' must have at least one lowercase letter. + SimpleValidationBuilder mustHaveLowercase( + {String? message, String? code}) { + return use( + (value, entity) { + if (value != null) { + final isValid = RegExp(r'[a-z]').hasMatch(value); + if (isValid) return null; + } + + final currentCode = code ?? Language.code.mustHaveLowercase; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} + +extension MustHaveLowercaseOrNullableValidation + on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] contains at least one lowercase letter or [null]. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Must contain at least one lowercase letter". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.password, key: 'password') + /// .mustHaveLowercaseOrNull(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + /// '{PropertyName}' must have at least one lowercase letter. + SimpleValidationBuilder mustHaveLowercaseOrNull( + {String? message, String? code}) { + return use( + (value, entity) { + if (value == null) return null; + final isValid = RegExp(r'[a-z]').hasMatch(value); + if (isValid) return null; + + final currentCode = code ?? Language.code.mustHaveLowercase; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} diff --git a/lib/src/validations/must_have_number_validation.dart b/lib/src/validations/must_have_number_validation.dart index 38567e5..a6468a6 100644 --- a/lib/src/validations/must_have_number_validation.dart +++ b/lib/src/validations/must_have_number_validation.dart @@ -43,3 +43,88 @@ extension MustHaveNumbersValidation on SimpleValidationBuilder { ); } } + +extension MustHaveNumbersNullableValidation + on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] contains at least one numeric digit. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Must contain at least one numeric digit". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.password, key: 'password') // user.password is nullable + /// .mustHaveNumbers(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder mustHaveNumber( + {String? message, String? code}) { + return use( + (value, entity) { + if (value != null) { + final isValid = RegExp(r'[0-9]').hasMatch(value); + if (isValid) return null; + } + + final currentCode = code ?? Language.code.mustHaveNumber; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} + +extension MustHaveNumbersOrNullableValidation + on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] contains at least one numeric digit or [null]. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Must contain at least one numeric digit". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.password, key: 'password') + /// .mustHaveNumberOrNull(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder mustHaveNumberOrNull( + {String? message, String? code}) { + return use( + (value, entity) { + if (value == null) return null; + final isValid = RegExp(r'[0-9]').hasMatch(value); + if (isValid) return null; + + final currentCode = code ?? Language.code.mustHaveNumber; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} diff --git a/lib/src/validations/must_have_special_character_validation.dart b/lib/src/validations/must_have_special_character_validation.dart index fc39738..08db296 100644 --- a/lib/src/validations/must_have_special_character_validation.dart +++ b/lib/src/validations/must_have_special_character_validation.dart @@ -44,3 +44,88 @@ extension MustHaveSpecialCharacterValidation ); } } + +extension MustHaveSpecialCharacterNullableValidation + on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] contains at least one special character. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Must contain at least one special character". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.password, key: 'password') // user.password is nullable + /// .mustHaveSpecialCharacter(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder mustHaveSpecialCharacter( + {String? message, String? code}) { + return use( + (value, entity) { + if (value != null) { + final isValid = RegExp(r'[!@#\$%\^&\*(),.?":{}|<>]').hasMatch(value); + if (isValid) return null; + } + + final currentCode = code ?? Language.code.mustHaveSpecialCharacter; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} + +extension MustHaveSpecialCharacterOrNullableValidation + on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] contains at least one special character or [null]. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Must contain at least one special character". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.password, key: 'password') + /// .mustHaveSpecialCharacterOrNull(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder mustHaveSpecialCharacterOrNull( + {String? message, String? code}) { + return use( + (value, entity) { + if (value == null) return null; + final isValid = RegExp(r'[!@#\$%\^&\*(),.?":{}|<>]').hasMatch(value); + if (isValid) return null; + + final currentCode = code ?? Language.code.mustHaveSpecialCharacter; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} diff --git a/lib/src/validations/must_have_uppercase_validation.dart b/lib/src/validations/must_have_uppercase_validation.dart index e3fbf77..de8a349 100644 --- a/lib/src/validations/must_have_uppercase_validation.dart +++ b/lib/src/validations/must_have_uppercase_validation.dart @@ -43,3 +43,88 @@ extension MustHaveUppercaseValidation on SimpleValidationBuilder { ); } } + +extension MustHaveUppercaseNullableValidation + on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] contains at least one uppercase letter. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Must contain at least one uppercase letter". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.password, key: 'password') // user.password is nullable + /// .mustHaveUppercase(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder mustHaveUppercase( + {String? message, String? code}) { + return use( + (value, entity) { + if (value != null) { + final isValid = RegExp(r'[A-Z]').hasMatch(value); + if (isValid) return null; + } + + final currentCode = code ?? Language.code.mustHaveUppercase; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} + +extension MustHaveUppercaseOrNullableValidation + on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] contains at least one uppercase letter or [null]. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Must contain at least one uppercase letter". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.password, key: 'password') + /// .mustHaveUppercaseOrNull(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder mustHaveUppercaseOrNull( + {String? message, String? code}) { + return use( + (value, entity) { + if (value == null) return null; + final isValid = RegExp(r'[A-Z]').hasMatch(value); + if (isValid) return null; + + final currentCode = code ?? Language.code.mustHaveUppercase; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} diff --git a/lib/src/validations/not_empty_validation.dart b/lib/src/validations/not_empty_validation.dart index bc2d35d..9fe50e7 100644 --- a/lib/src/validations/not_empty_validation.dart +++ b/lib/src/validations/not_empty_validation.dart @@ -41,3 +41,80 @@ extension NotEmptyValidation on SimpleValidationBuilder { ); } } + +extension NotEmptyNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] is not empty. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Cannot be empty". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.username, key: 'username') // user.username is nullable + /// .notEmpty(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder notEmpty({String? message, String? code}) { + return use( + (value, entity) { + if (value != null && value.isNotEmpty) return null; + + final currentCode = code ?? Language.code.notEmpty; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} + +extension NotEmptyOrNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] is not empty or [null]. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Cannot be empty". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.username, key: 'username') /// user.name is nullable + /// .notEmptyOrNull(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder notEmptyOrNull( + {String? message, String? code}) { + return use( + (value, entity) { + if (value == null || value.isNotEmpty) return null; + + final currentCode = code ?? Language.code.notEmpty; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} diff --git a/lib/src/validations/valid_cep_validation.dart b/lib/src/validations/valid_cep_validation.dart index 6885040..9e98fcb 100644 --- a/lib/src/validations/valid_cep_validation.dart +++ b/lib/src/validations/valid_cep_validation.dart @@ -38,3 +38,84 @@ extension ValidCEPValidation on SimpleValidationBuilder { }); } } + +extension ValidCEPNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] is a valid CEP (Brazilian postal code). + /// + /// This method verifies that the CEP is in the correct format (#####-###) and consists + /// of only numbers. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Invalid CEP". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.cep, key: 'cep') // user.cep is nullable + /// .validCEP(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder validCEP({String? message, String? code}) { + return use((value, entity) { + if (value != null && RegExp(r'^\d{5}-?\d{3}$').hasMatch(value)) + return null; + + final currentCode = code ?? Language.code.validCEP; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} + +extension ValidCEPOrNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] is a valid CEP (Brazilian postal code) or [null]. + /// + /// This method verifies that the CEP is in the correct format (#####-###) and consists + /// of only numbers. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Invalid CEP". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.cep, key: 'cep') + /// .validCEPOrNull(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder validCEPOrNull( + {String? message, String? code}) { + return use((value, entity) { + if (value == null) return null; + if (RegExp(r'^\d{5}-?\d{3}$').hasMatch(value)) return null; + + final currentCode = code ?? Language.code.validCEP; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} diff --git a/lib/src/validations/valid_cnpj_validation.dart b/lib/src/validations/valid_cnpj_validation.dart index 4828e64..36a4604 100644 --- a/lib/src/validations/valid_cnpj_validation.dart +++ b/lib/src/validations/valid_cnpj_validation.dart @@ -38,37 +38,118 @@ extension ValidCnpjValidation on SimpleValidationBuilder { return ValidationException(message: currentMessage, code: currentCode); }); } +} + +extension ValidCnpjNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] is a valid CNPJ number. + /// + /// The CNPJ is the national identifier for Brazilian companies. This method + /// verifies the format and the validity of the CNPJ, ensuring it follows the correct + /// algorithm for digit verification. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Invalid CNPJ". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.cnpj, key: 'cnpj') + /// .validCNPJ(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder validCNPJ({String? message, String? code}) { + return use((value, entity) { + if (value != null && _validateCNPJ(value)) return null; + + final currentCode = code ?? Language.code.validCNPJ; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} - bool _validateCNPJ(String cnpj) { - // Remove non-numeric characters - cnpj = cnpj.replaceAll(RegExp(r'[^0-9]'), ''); - if (cnpj.length != 14) return false; +extension ValidCnpjOrNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] is a valid CNPJ number or [null]. + /// + /// The CNPJ is the national identifier for Brazilian companies. This method + /// verifies the format and the validity of the CNPJ, ensuring it follows the correct + /// algorithm for digit verification. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Invalid CNPJ". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.cnpj, key: 'cnpj') + /// .validCNPJOrNull(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder validCNPJOrNull({String? message, String? code}) { + return use((value, entity) { + if (value == null) return null; + if (_validateCNPJ(value)) return null; + + final currentCode = code ?? Language.code.validCNPJ; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); - // Check if all digits are the same - if (RegExp(r'^(\d)\1*$').hasMatch(cnpj)) return false; + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} + +bool _validateCNPJ(String cnpj) { + // Remove non-numeric characters + cnpj = cnpj.replaceAll(RegExp(r'[^0-9]'), ''); + if (cnpj.length != 14) return false; - // Calculate the first check digit - int sum = 0; - List weight1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]; - for (int i = 0; i < 12; i++) { - sum += int.parse(cnpj[i]) * weight1[i]; - } - int remainder = (sum % 11); - int firstCheckDigit = remainder < 2 ? 0 : 11 - remainder; + // Check if all digits are the same + if (RegExp(r'^(\d)\1*$').hasMatch(cnpj)) return false; - // Check the first check digit - if (firstCheckDigit != int.parse(cnpj[12])) return false; + // Calculate the first check digit + int sum = 0; + List weight1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]; + for (int i = 0; i < 12; i++) { + sum += int.parse(cnpj[i]) * weight1[i]; + } + int remainder = (sum % 11); + int firstCheckDigit = remainder < 2 ? 0 : 11 - remainder; - // Calculate the second check digit - sum = 0; - List weight2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]; - for (int i = 0; i < 13; i++) { - sum += int.parse(cnpj[i]) * weight2[i]; - } - remainder = (sum % 11); - int secondCheckDigit = remainder < 2 ? 0 : 11 - remainder; + // Check the first check digit + if (firstCheckDigit != int.parse(cnpj[12])) return false; - // Check the second check digit - return secondCheckDigit == int.parse(cnpj[13]); + // Calculate the second check digit + sum = 0; + List weight2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]; + for (int i = 0; i < 13; i++) { + sum += int.parse(cnpj[i]) * weight2[i]; } + remainder = (sum % 11); + int secondCheckDigit = remainder < 2 ? 0 : 11 - remainder; + + // Check the second check digit + return secondCheckDigit == int.parse(cnpj[13]); } diff --git a/lib/src/validations/valid_cpf_validation.dart b/lib/src/validations/valid_cpf_validation.dart index a643b12..6beb8f3 100644 --- a/lib/src/validations/valid_cpf_validation.dart +++ b/lib/src/validations/valid_cpf_validation.dart @@ -38,31 +38,104 @@ extension ValidCPFValidation on SimpleValidationBuilder { return ValidationException(message: currentMessage, code: currentCode); }); } +} + +extension ValidCPFNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] is a valid CPF number. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Invalid CPF". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.cpf, key: 'cpf') // user.cpf is nullable + /// .validCPF(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder validCPF({String? message, String? code}) { + return use((value, entity) { + if (value != null && _validateCPF(value)) return null; + + final currentCode = code ?? Language.code.validCPF; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} - bool _validateCPF(String cpf) { - cpf = cpf.replaceAll(RegExp(r'[^0-9]'), ''); - if (cpf.length != 11) return false; +extension ValidCPFOrNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] is a valid CPF number or [null]. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Invalid CPF". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.cpf, key: 'cpf') /// user.name is nullable + /// .validCPFOrNull(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder validCPFOrNull( + {String? message, String? code}) { + return use((value, entity) { + if (value == null || _validateCPF(value)) return null; + + final currentCode = code ?? Language.code.validCPF; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); - for (int i = 0; i < 10; i++) { - if (cpf == '$i' * 11) return false; - } + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} - int add = 0; - for (int i = 0; i < 9; i++) { - add += int.parse(cpf[i]) * (10 - i); - } - int rev = 11 - (add % 11); - if (rev == 10 || rev == 11) rev = 0; - if (rev != int.parse(cpf[9])) return false; +bool _validateCPF(String cpf) { + cpf = cpf.replaceAll(RegExp(r'[^0-9]'), ''); + if (cpf.length != 11) return false; - add = 0; - for (int i = 0; i < 10; i++) { - add += int.parse(cpf[i]) * (11 - i); - } - rev = 11 - (add % 11); - if (rev == 10 || rev == 11) rev = 0; - if (rev != int.parse(cpf[10])) return false; + for (int i = 0; i < 10; i++) { + if (cpf == '$i' * 11) return false; + } + + int add = 0; + for (int i = 0; i < 9; i++) { + add += int.parse(cpf[i]) * (10 - i); + } + int rev = 11 - (add % 11); + if (rev == 10 || rev == 11) rev = 0; + if (rev != int.parse(cpf[9])) return false; - return true; + add = 0; + for (int i = 0; i < 10; i++) { + add += int.parse(cpf[i]) * (11 - i); } + rev = 11 - (add % 11); + if (rev == 10 || rev == 11) rev = 0; + if (rev != int.parse(cpf[10])) return false; + + return true; } diff --git a/lib/src/validations/valid_creditcard_validation.dart b/lib/src/validations/valid_creditcard_validation.dart index 55a5398..a7d5395 100644 --- a/lib/src/validations/valid_creditcard_validation.dart +++ b/lib/src/validations/valid_creditcard_validation.dart @@ -38,31 +38,113 @@ extension ValidCreditCardValidation on SimpleValidationBuilder { return ValidationException(message: currentMessage, code: currentCode); }); } +} - bool _validateCreditCard(String number) { - // Remove non-numeric characters - number = number.replaceAll(RegExp(r'[^0-9]'), ''); - if (number.isEmpty || number.length < 13 || number.length > 19) { - return false; - } +extension ValidCreditCardNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] is a valid credit card number. + /// + /// This method uses the Luhn algorithm to verify the validity of a credit card number. + /// It checks the length of the number and ensures it passes the Luhn check. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Invalid credit card number". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.creditCard, key: 'creditCard') // user.creditCard is nullable + /// .validCreditCard(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder validCreditCard( + {String? message, String? code}) { + return use((value, entity) { + if (value != null && _validateCreditCard(value)) return null; - int sum = 0; - bool alternate = false; + final currentCode = code ?? Language.code.validCreditCard; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); - for (int i = number.length - 1; i >= 0; i--) { - int n = int.parse(number[i]); + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} - if (alternate) { - n *= 2; - if (n > 9) { - n -= 9; - } - } +extension ValidCreditCardOrNullableValidation + on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] is a valid credit card number or [null]. + /// + /// This method uses the Luhn algorithm to verify the validity of a credit card number. + /// It checks the length of the number and ensures it passes the Luhn check. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Invalid credit card number". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.creditCard, key: 'creditCard') + /// .validCreditCardOrNull(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder validCreditCardOrNull( + {String? message, String? code}) { + return use((value, entity) { + if (value == null) return null; + if (_validateCreditCard(value)) return null; + + final currentCode = code ?? Language.code.validCreditCard; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); - sum += n; - alternate = !alternate; + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} + +bool _validateCreditCard(String number) { + // Remove non-numeric characters + number = number.replaceAll(RegExp(r'[^0-9]'), ''); + if (number.isEmpty || number.length < 13 || number.length > 19) { + return false; + } + + int sum = 0; + bool alternate = false; + + for (int i = number.length - 1; i >= 0; i--) { + int n = int.parse(number[i]); + + if (alternate) { + n *= 2; + if (n > 9) { + n -= 9; + } } - return (sum % 10 == 0); + sum += n; + alternate = !alternate; } + + return (sum % 10 == 0); } diff --git a/lib/src/validations/valid_email_validation.dart b/lib/src/validations/valid_email_validation.dart index f064c1a..3adb802 100644 --- a/lib/src/validations/valid_email_validation.dart +++ b/lib/src/validations/valid_email_validation.dart @@ -41,3 +41,82 @@ extension ValidEmailValidation on SimpleValidationBuilder { }); } } + +extension ValidEmailNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] is a valid email address. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Invalid email address". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.email, key: 'email') // user.email is nullable + /// .validEmail(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder validEmail({String? message, String? code}) { + return use((value, entity) { + if (value != null && + RegExp(r'^[\w\.-]+@[a-zA-Z\d\.-]+\.[a-zA-Z]{2,}$').hasMatch(value)) { + return null; + } + + final currentCode = code ?? Language.code.validEmail; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} + +extension ValidEmailOrNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [String?] is a valid email address. + /// + /// [message] is the error message returned if the validation fails. Defaults to "Invalid email address". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.email, key: 'email') + /// .validEmailOrNull(); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// + SimpleValidationBuilder validEmailOrNull( + {String? message, String? code}) { + return use((value, entity) { + if (value == null) return null; + if (RegExp(r'^[\w\.-]+@[a-zA-Z\d\.-]+\.[a-zA-Z]{2,}$').hasMatch(value)) { + return null; + } + + final currentCode = code ?? Language.code.validEmail; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }); + } +} diff --git a/test/src/validations/valid_cpf_validation_test.dart b/test/src/validations/valid_cpf_validation_test.dart index cfa8417..2a05465 100644 --- a/test/src/validations/valid_cpf_validation_test.dart +++ b/test/src/validations/valid_cpf_validation_test.dart @@ -9,8 +9,6 @@ void main() { validator .ruleFor((e) => e.cpf, key: 'cpf') // - .isNotNull() - .notEmpty() .validCPF(); final user = UserModel()..cpf = null; @@ -23,6 +21,6 @@ void main() { final error = result.exceptions.first; - expect(error.message, "'cpf' must not be null."); + expect(error.message, "'cpf' is not a valid CPF."); }); } From afcd27e3b42fb29a7a3f5aef8fb66442cfce3c25 Mon Sep 17 00:00:00 2001 From: Wellington Santos Date: Mon, 16 Dec 2024 14:20:43 -0300 Subject: [PATCH 3/4] fix: add tests for nullable --- README.md | 4 +- example/pubspec.lock | 2 +- .../validations/is_not_null_validation.dart | 4 +- lib/src/validations/range_validation.dart | 93 +++++++++++++++++++ lib/src/validations/valid_cep_validation.dart | 3 +- test/mocks/mocks.dart | 31 +++++++ ...sive_between_datetime_validation_test.dart | 18 ++++ ...greater_than_datetime_validation_test.dart | 15 +++ ..._or_equal_to_datetime_validation_test.dart | 15 +++ .../greater_than_validation_test.dart | 13 +++ ...sive_between_datetime_validation_test.dart | 17 ++++ .../validations/is_empty_validation_test.dart | 13 +++ .../is_not_null_validation_test.dart | 14 +++ .../less_than_datetime_validation_test.dart | 15 +++ ..._or_equal_to_datetime_validation_test.dart | 15 +++ .../less_then_validation_test.dart | 13 +++ .../matches_pattern_validation_test.dart | 13 +++ .../max_length_validation_test.dart | 13 +++ test/src/validations/max_validation_test.dart | 13 +++ .../min_length_validation_test.dart | 13 +++ test/src/validations/min_validation_test.dart | 13 +++ .../must_have_lowercase_validation_test.dart | 13 +++ .../must_have_number_validation_test.dart | 13 +++ ...ave_special_character_validation_test.dart | 13 +++ .../must_have_uppercase_validation_test.dart | 13 +++ .../not_empty_validation_test.dart | 13 +++ .../validations/range_validation_test.dart | 14 +++ .../valid_cep_validation_test.dart | 14 +++ .../valid_cnpj_validation_test.dart | 14 +++ .../valid_cpf_validation_test.dart | 14 +++ .../valid_creditcard_validation_test.dart | 14 +++ .../valid_email_validation_test.dart | 14 +++ 32 files changed, 494 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e2865d8..fd28df9 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,8 @@ Here’s a complete list of available validators you can use: - **inclusiveBetween**: Checks if the datetime value is between two datetime values, including both bounds. - **exclusiveBetween**: Checks if the datetime value is between two datetime values, excluding both bounds. +**Observation: for almost all validators, there is an equivalent with the `OrNull` suffix. Example: `validEmailOrNull`** + ## Usage with Flutter If you’re using the `lucid_validation` package in a Flutter app, integrating with `TextFormField` is straightforward. @@ -200,7 +202,7 @@ Example: ```dart ruleFor((user) => user.phoneNumber, key: 'phoneNumber') .when((user) => user.requiresPhoneNumber) - .must((value) => value.isNotEmpty, 'Phone number is required', 'phone_required') + .isEmpty() .must((value) => value.length == 10, 'Phone number must be 10 digits', 'phone_length'); ``` diff --git a/example/pubspec.lock b/example/pubspec.lock index 5293f04..746e424 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -126,7 +126,7 @@ packages: path: ".." relative: true source: path - version: "1.0.1" + version: "1.1.0" matcher: dependency: transitive description: diff --git a/lib/src/validations/is_not_null_validation.dart b/lib/src/validations/is_not_null_validation.dart index 1eaf12c..76bdfc2 100644 --- a/lib/src/validations/is_not_null_validation.dart +++ b/lib/src/validations/is_not_null_validation.dart @@ -4,7 +4,7 @@ part of 'validations.dart'; /// /// This extension adds an `isNotNull` method that can be used to ensure that a value /// is not null. -extension IsNotNullValidation on SimpleValidationBuilder { +extension IsNotNullValidation on SimpleValidationBuilder { /// Adds a validation rule that checks if the value is not null. /// /// [message] is the error message returned if the validation fails. @@ -22,7 +22,7 @@ extension IsNotNullValidation on SimpleValidationBuilder { /// String format args: /// - **{PropertyName}**: The name of the property. /// - SimpleValidationBuilder isNotNull({String? message, String? code}) { + SimpleValidationBuilder isNotNull({String? message, String? code}) { return useNotNull( (value, entity) { if (value != null) return null; diff --git a/lib/src/validations/range_validation.dart b/lib/src/validations/range_validation.dart index 7afa1e9..71c2bc0 100644 --- a/lib/src/validations/range_validation.dart +++ b/lib/src/validations/range_validation.dart @@ -49,3 +49,96 @@ extension RangeValidation on SimpleValidationBuilder { ); } } + +extension RangeNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [num?] is within the range of [min] and [max]. + /// + /// [min] and [max] define the acceptable range for the number. + /// [message] is the error message returned if the validation fails. Defaults to "Must be between $min and $max". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.age, key: 'age') // user.age is nullable + /// .range(18, 65); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{From}**: The minimum value of the range. + /// - **{To}**: The maximum value of the range. + /// - **{PropertyValue}**: The value of the property. + /// + SimpleValidationBuilder range(num min, num max, + {String? message, String? code}) { + return use( + (value, entity) { + if (value != null && value >= min && value <= max) return null; + + final currentCode = code ?? Language.code.range; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'From': '$min', + 'To': '$max', + 'PropertyValue': '$value', + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} + +extension RangeOrNullableValidation on SimpleValidationBuilder { + /// Adds a validation rule that checks if the [num?] is within the range of [min] and [max]. + /// + /// [min] and [max] define the acceptable range for the number. + /// [message] is the error message returned if the validation fails. Defaults to "Must be between $min and $max". + /// [code] is an optional error code for translation purposes. + /// + /// Returns the [LucidValidationBuilder] to allow for method chaining. + /// + /// Example: + /// ```dart + /// ... + /// ruleFor((user) => user.age, key: 'age') // user.age is nullable + /// .range(18, 65); + /// ``` + /// + /// String format args: + /// - **{PropertyName}**: The name of the property. + /// - **{From}**: The minimum value of the range. + /// - **{To}**: The maximum value of the range. + /// - **{PropertyValue}**: The value of the property. + /// + SimpleValidationBuilder rangeOrNull(num min, num max, + {String? message, String? code}) { + return use( + (value, entity) { + if (value == null) return null; + if (value >= min && value <= max) return null; + + final currentCode = code ?? Language.code.range; + final currentMessage = LucidValidation.global.languageManager.translate( + currentCode, + parameters: { + 'PropertyName': label.isNotEmpty ? label : key, + 'From': '$min', + 'To': '$max', + 'PropertyValue': '$value', + }, + defaultMessage: message, + ); + + return ValidationException(message: currentMessage, code: currentCode); + }, + ); + } +} diff --git a/lib/src/validations/valid_cep_validation.dart b/lib/src/validations/valid_cep_validation.dart index 9e98fcb..4d31456 100644 --- a/lib/src/validations/valid_cep_validation.dart +++ b/lib/src/validations/valid_cep_validation.dart @@ -62,8 +62,9 @@ extension ValidCEPNullableValidation on SimpleValidationBuilder { /// SimpleValidationBuilder validCEP({String? message, String? code}) { return use((value, entity) { - if (value != null && RegExp(r'^\d{5}-?\d{3}$').hasMatch(value)) + if (value != null && RegExp(r'^\d{5}-?\d{3}$').hasMatch(value)) { return null; + } final currentCode = code ?? Language.code.validCEP; final currentMessage = LucidValidation.global.languageManager.translate( diff --git a/test/mocks/mocks.dart b/test/mocks/mocks.dart index 4ab893b..f26fce9 100644 --- a/test/mocks/mocks.dart +++ b/test/mocks/mocks.dart @@ -11,6 +11,16 @@ class UserModel { String? cpf = ''; } +class UserNullableModel { + String? email = ''; + String? password = ''; + String? confirmPassword = ''; + String? description; + int? age = 0; + String phone = ''; + String? cpf = ''; +} + class UserValidator extends LucidValidator { UserValidator() { ruleFor((user) => user.email, key: 'email') // @@ -107,6 +117,11 @@ class Address { }); } +class AddressNullable { + String? country; + String? postcode; +} + class AddressValidator extends LucidValidator
{ AddressValidator() { ruleFor((address) => address.country, key: 'country') // @@ -129,6 +144,12 @@ class Customer { }); } +class CustomerNullable { + String? name; + Address? address; + String? cnpj; +} + class CustomerValidator extends LucidValidator { final addressValidator = AddressValidator(); @@ -151,10 +172,20 @@ class EventModel { DateTime dateEvent = DateTime.now(); } +class EventNullableModel { + DateTime? start = DateTime.now(); + DateTime? end = DateTime.now(); + DateTime? dateEvent = DateTime.now(); +} + class CreditCardModel { String number = ''; } +class CreditCardNullableModel { + String? number; +} + class CreditCardValidator extends LucidValidator { CreditCardValidator() { ruleFor((card) => card.number, key: 'number') // diff --git a/test/src/validations/exclusive_between_datetime_validation_test.dart b/test/src/validations/exclusive_between_datetime_validation_test.dart index 2927200..464474b 100644 --- a/test/src/validations/exclusive_between_datetime_validation_test.dart +++ b/test/src/validations/exclusive_between_datetime_validation_test.dart @@ -66,4 +66,22 @@ void main() { expect(result.isValid, true); }); + + test( + 'must return valid exclusively between validation when the event date is null', + () { + final validator = TestLucidValidator(); + final now = DateTime.now(); + final afterTomorrow = now.add(Duration(days: 2)); + + validator + .ruleFor((event) => event.dateEvent, key: 'dateEvent') // + .exclusiveBetweenOrNull(start: now, end: afterTomorrow); + + final event = EventNullableModel()..dateEvent = null; + + final result = validator.validate(event); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/greater_than_datetime_validation_test.dart b/test/src/validations/greater_than_datetime_validation_test.dart index 18acd60..880e2ed 100644 --- a/test/src/validations/greater_than_datetime_validation_test.dart +++ b/test/src/validations/greater_than_datetime_validation_test.dart @@ -21,4 +21,19 @@ void main() { expect(result.exceptions.first.message, "'start' must be greater than date '${now.toString()}'."); }); + + test('greater than validation or null ...', () { + final validator = TestLucidValidator(); + final now = DateTime.now(); + + validator + .ruleFor((event) => event.start, key: 'start') // + .greaterThanOrNull(now); + + final event = EventNullableModel()..start = null; + + final result = validator.validate(event); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/greater_than_or_equal_to_datetime_validation_test.dart b/test/src/validations/greater_than_or_equal_to_datetime_validation_test.dart index e1005b7..91b5856 100644 --- a/test/src/validations/greater_than_or_equal_to_datetime_validation_test.dart +++ b/test/src/validations/greater_than_or_equal_to_datetime_validation_test.dart @@ -36,4 +36,19 @@ void main() { expect(result.isValid, true); }); + + test('greater than or equal to validation or null ...', () { + final validator = TestLucidValidator(); + final now = DateTime.now(); + + validator + .ruleFor((event) => event.start, key: 'start') // + .greaterThanOrEqualToOrNull(now); + + final event = EventNullableModel()..start = null; + + final result = validator.validate(event); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/greater_than_validation_test.dart b/test/src/validations/greater_than_validation_test.dart index 68fc846..0274662 100644 --- a/test/src/validations/greater_than_validation_test.dart +++ b/test/src/validations/greater_than_validation_test.dart @@ -18,4 +18,17 @@ void main() { expect(result.exceptions.length, 1); expect(result.exceptions.first.message, "'age' must be greater than '17'."); }); + + test('greater than validation or null ...', () { + final validator = TestLucidValidator(); + validator + .ruleFor((user) => user.age, key: 'age') // + .greaterThanOrNull(17); + + final user = UserNullableModel()..age = null; + + final result = validator.validate(user); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/inclusive_between_datetime_validation_test.dart b/test/src/validations/inclusive_between_datetime_validation_test.dart index f58ed51..6241bbe 100644 --- a/test/src/validations/inclusive_between_datetime_validation_test.dart +++ b/test/src/validations/inclusive_between_datetime_validation_test.dart @@ -23,4 +23,21 @@ void main() { expect(result.exceptions.first.message, "'dateEvent' must be greater than or equal to '${tomorrow.toString()}' date and less than or equal to '${afterTomorrow.toString()}' date."); }); + + test('inclusive between validation or null...', () { + final validator = TestLucidValidator(); + final now = DateTime.now(); + final tomorrow = now.add(Duration(days: 1)); + final afterTomorrow = now.add(Duration(days: 2)); + + validator + .ruleFor((event) => event.dateEvent, key: 'dateEvent') // + .inclusiveBetweenOrNull(start: tomorrow, end: afterTomorrow); + + final event = EventNullableModel()..dateEvent = null; + + final result = validator.validate(event); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/is_empty_validation_test.dart b/test/src/validations/is_empty_validation_test.dart index 1d3a6b3..05afafd 100644 --- a/test/src/validations/is_empty_validation_test.dart +++ b/test/src/validations/is_empty_validation_test.dart @@ -20,4 +20,17 @@ void main() { expect(result.exceptions.first.message, "'email' must be empty."); }); + + test('is empty validation or null...', () { + final validator = TestLucidValidator(); + validator + .ruleFor((user) => user.email, key: 'email') // + .isEmptyOrNull(); + + final user = UserNullableModel()..email = null; + + final result = validator.validate(user); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/is_not_null_validation_test.dart b/test/src/validations/is_not_null_validation_test.dart index cadd69a..62f1150 100644 --- a/test/src/validations/is_not_null_validation_test.dart +++ b/test/src/validations/is_not_null_validation_test.dart @@ -22,4 +22,18 @@ void main() { expect(error.message, r"'description' must not be null."); }); + + test('is not null validation is valid...', () { + final validator = TestLucidValidator(); + validator + .ruleFor((e) => e.description, key: 'description') // + .isNotNull() + .maxLength(4); + + final user = UserModel()..description = 'desc'; + + final result = validator.validate(user); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/less_than_datetime_validation_test.dart b/test/src/validations/less_than_datetime_validation_test.dart index 0871321..996c3c1 100644 --- a/test/src/validations/less_than_datetime_validation_test.dart +++ b/test/src/validations/less_than_datetime_validation_test.dart @@ -22,4 +22,19 @@ void main() { expect(result.exceptions.first.message, "'end' must be less than date '${now.toString()}'."); }); + + test('less than validation ...', () { + final validator = TestLucidValidator(); + final now = DateTime.now(); + + validator + .ruleFor((event) => event.end, key: 'end') // + .lessThanOrNull(now); + + final event = EventNullableModel()..end = null; + + final result = validator.validate(event); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/less_than_or_equal_to_datetime_validation_test.dart b/test/src/validations/less_than_or_equal_to_datetime_validation_test.dart index caa035b..bfbc653 100644 --- a/test/src/validations/less_than_or_equal_to_datetime_validation_test.dart +++ b/test/src/validations/less_than_or_equal_to_datetime_validation_test.dart @@ -37,4 +37,19 @@ void main() { expect(result.isValid, true); }); + + test('less than or equal to validation or null ...', () { + final validator = TestLucidValidator(); + final now = DateTime.now(); + + validator + .ruleFor((event) => event.end, key: 'end') // + .lessThanOrEqualToOrNull(now); + + final event = EventNullableModel()..end = null; + + final result = validator.validate(event); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/less_then_validation_test.dart b/test/src/validations/less_then_validation_test.dart index 8d44ab0..5bd5cca 100644 --- a/test/src/validations/less_then_validation_test.dart +++ b/test/src/validations/less_then_validation_test.dart @@ -18,4 +18,17 @@ void main() { expect(result.exceptions.length, 1); expect(result.exceptions.first.message, "'age' must be less than '17'."); }); + + test('less than validation or null...', () { + final validator = TestLucidValidator(); + validator + .ruleFor((user) => user.age, key: 'age') // + .lessThanOrNull(17); + + final user = UserNullableModel()..age = null; + + final result = validator.validate(user); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/matches_pattern_validation_test.dart b/test/src/validations/matches_pattern_validation_test.dart index f0ef0aa..bcc42e9 100644 --- a/test/src/validations/matches_pattern_validation_test.dart +++ b/test/src/validations/matches_pattern_validation_test.dart @@ -19,4 +19,17 @@ void main() { expect(result.exceptions.first.message, "'email' is not in the correct format."); }); + + test('matches pattern validation or null...', () { + final validator = TestLucidValidator(); + validator + .ruleFor((user) => user.email, key: 'email') // + .matchesPatternOrNull(r'^[\w\.-]+@[a-zA-Z\d\.-]+\.[a-zA-Z]{2,}$'); + + final user = UserNullableModel()..email = null; + + final result = validator.validate(user); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/max_length_validation_test.dart b/test/src/validations/max_length_validation_test.dart index 8568d6b..9ff84a3 100644 --- a/test/src/validations/max_length_validation_test.dart +++ b/test/src/validations/max_length_validation_test.dart @@ -19,4 +19,17 @@ void main() { expect(result.exceptions.first.message, "The length of 'password' must be 8 characters or fewer. You entered 9 characters."); }); + + test('max length validation or null...', () { + final validator = TestLucidValidator(); + validator + .ruleFor((user) => user.password, key: 'password') // + .maxLengthOrNull(8); + + final user = UserNullableModel()..password = null; + + final result = validator.validate(user); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/max_validation_test.dart b/test/src/validations/max_validation_test.dart index 85fa14c..2aac23f 100644 --- a/test/src/validations/max_validation_test.dart +++ b/test/src/validations/max_validation_test.dart @@ -19,4 +19,17 @@ void main() { expect(result.exceptions.first.message, "'age' must be less than or equal to 18. You entered 20."); }); + + test('max validation or null...', () { + final validator = TestLucidValidator(); + validator + .ruleFor((user) => user.age, key: 'age') // + .maxOrNull(18); + + final user = UserNullableModel()..age = null; + + final result = validator.validate(user); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/min_length_validation_test.dart b/test/src/validations/min_length_validation_test.dart index d5ffd19..6c555e4 100644 --- a/test/src/validations/min_length_validation_test.dart +++ b/test/src/validations/min_length_validation_test.dart @@ -19,4 +19,17 @@ void main() { expect(result.exceptions.first.message, "The length of 'password' must be at least 8 characters. You entered 6 characters."); }); + + test('max length validation or null...', () { + final validator = TestLucidValidator(); + validator + .ruleFor((user) => user.password, key: 'password') // + .minLengthOrNull(8); + + final user = UserNullableModel()..password = null; + + final result = validator.validate(user); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/min_validation_test.dart b/test/src/validations/min_validation_test.dart index 7eaf8a2..a7e6822 100644 --- a/test/src/validations/min_validation_test.dart +++ b/test/src/validations/min_validation_test.dart @@ -19,4 +19,17 @@ void main() { expect(result.exceptions.first.message, "'age' must be greater than or equal to 18. You entered 17."); }); + + test('min validation or null...', () { + final validator = TestLucidValidator(); + validator + .ruleFor((user) => user.age, key: 'age') // + .minOrNull(18); + + final user = UserNullableModel()..age = null; + + final result = validator.validate(user); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/must_have_lowercase_validation_test.dart b/test/src/validations/must_have_lowercase_validation_test.dart index c2544e9..b8dbcfa 100644 --- a/test/src/validations/must_have_lowercase_validation_test.dart +++ b/test/src/validations/must_have_lowercase_validation_test.dart @@ -19,4 +19,17 @@ void main() { expect(result.exceptions.first.message, "'password' must have at least one lowercase letter."); }); + + test('must have lowercase validation or null...', () { + final validator = TestLucidValidator(); + validator + .ruleFor((user) => user.password, key: 'password') // + .mustHaveLowercaseOrNull(); + + final user = UserNullableModel()..password = null; + + final result = validator.validate(user); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/must_have_number_validation_test.dart b/test/src/validations/must_have_number_validation_test.dart index 780fba4..b62cc21 100644 --- a/test/src/validations/must_have_number_validation_test.dart +++ b/test/src/validations/must_have_number_validation_test.dart @@ -19,4 +19,17 @@ void main() { expect(result.exceptions.first.message, "'password' must have at least one digit ('0'-'9')."); }); + + test('must have number validation or null ...', () { + final validator = TestLucidValidator(); + validator + .ruleFor((user) => user.password, key: 'password') // + .mustHaveNumberOrNull(); + + final user = UserNullableModel()..password = null; + + final result = validator.validate(user); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/must_have_special_character_validation_test.dart b/test/src/validations/must_have_special_character_validation_test.dart index 8654f10..990a753 100644 --- a/test/src/validations/must_have_special_character_validation_test.dart +++ b/test/src/validations/must_have_special_character_validation_test.dart @@ -19,4 +19,17 @@ void main() { expect(result.exceptions.first.message, "'password' must have at least one non-alphanumeric character."); }); + + test('must have special character validation or null...', () { + final validator = TestLucidValidator(); + validator + .ruleFor((user) => user.password, key: 'password') // + .mustHaveSpecialCharacterOrNull(); + + final user = UserNullableModel()..password = null; + + final result = validator.validate(user); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/must_have_uppercase_validation_test.dart b/test/src/validations/must_have_uppercase_validation_test.dart index 259ad4e..7669bd2 100644 --- a/test/src/validations/must_have_uppercase_validation_test.dart +++ b/test/src/validations/must_have_uppercase_validation_test.dart @@ -19,4 +19,17 @@ void main() { expect(result.exceptions.first.message, "'password' must have at least one uppercase letter."); }); + + test('must have uppercase validation or null...', () { + final validator = TestLucidValidator(); + validator + .ruleFor((user) => user.password, key: 'password') // + .mustHaveUppercaseOrNull(); + + final user = UserNullableModel()..password = null; + + final result = validator.validate(user); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/not_empty_validation_test.dart b/test/src/validations/not_empty_validation_test.dart index a3ee384..afac2ea 100644 --- a/test/src/validations/not_empty_validation_test.dart +++ b/test/src/validations/not_empty_validation_test.dart @@ -18,4 +18,17 @@ void main() { expect(result.exceptions.length, 1); expect(result.exceptions.first.message, "'password' must not be empty."); }); + + test('not empty validation or null...', () { + final validator = TestLucidValidator(); + validator + .ruleFor((user) => user.password, key: 'password') // + .notEmptyOrNull(); + + final user = UserNullableModel()..password = null; + + final result = validator.validate(user); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/range_validation_test.dart b/test/src/validations/range_validation_test.dart index f4b5238..2f6823a 100644 --- a/test/src/validations/range_validation_test.dart +++ b/test/src/validations/range_validation_test.dart @@ -23,4 +23,18 @@ void main() { expect(error.message, "'age' must be between 18 and 60. You entered 17."); }); + + test('range validation ...', () { + final validator = TestLucidValidator(); + + validator + .ruleFor((e) => e.age, key: 'age') // + .rangeOrNull(18, 60); + + var user = UserNullableModel()..age = null; + + final result = validator.validate(user); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/valid_cep_validation_test.dart b/test/src/validations/valid_cep_validation_test.dart index 208b65c..83d360e 100644 --- a/test/src/validations/valid_cep_validation_test.dart +++ b/test/src/validations/valid_cep_validation_test.dart @@ -26,4 +26,18 @@ void main() { expect(error.message, "'postcode' is not a valid CEP."); }); + + test('valid cep validation or null ...', () { + final validator = TestLucidValidator(); + + validator + .ruleFor((e) => e.postcode, key: 'postcode') // + .validCEPOrNull(); + + var customer = AddressNullable(); + + final result = validator.validate(customer); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/valid_cnpj_validation_test.dart b/test/src/validations/valid_cnpj_validation_test.dart index da72aaf..ccddd3a 100644 --- a/test/src/validations/valid_cnpj_validation_test.dart +++ b/test/src/validations/valid_cnpj_validation_test.dart @@ -30,4 +30,18 @@ void main() { expect(error.message, "'cnpj' is not a valid CNPJ."); }); + + test('valid cnpj validation ...', () { + final validator = TestLucidValidator(); + + validator + .ruleFor((e) => e.cnpj, key: 'cnpj') // + .validCNPJOrNull(); + + var customer = CustomerNullable()..cnpj = null; + + final result = validator.validate(customer); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/valid_cpf_validation_test.dart b/test/src/validations/valid_cpf_validation_test.dart index 2a05465..731d225 100644 --- a/test/src/validations/valid_cpf_validation_test.dart +++ b/test/src/validations/valid_cpf_validation_test.dart @@ -23,4 +23,18 @@ void main() { expect(error.message, "'cpf' is not a valid CPF."); }); + + test('valid cpf validation or null ...', () { + final validator = TestLucidValidator(); + + validator + .ruleFor((e) => e.cpf, key: 'cpf') // + .validCPFOrNull(); + + final user = UserNullableModel()..cpf = null; + + final result = validator.validate(user); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/valid_creditcard_validation_test.dart b/test/src/validations/valid_creditcard_validation_test.dart index de3359b..592936c 100644 --- a/test/src/validations/valid_creditcard_validation_test.dart +++ b/test/src/validations/valid_creditcard_validation_test.dart @@ -23,4 +23,18 @@ void main() { expect(error.message, "'number' is not a valid credit card number."); }); + + test('valid creditcard validation or null ...', () { + final validator = TestLucidValidator(); + + validator + .ruleFor((e) => e.number, key: 'number') // + .validCreditCardOrNull(); + + final creditCard = CreditCardNullableModel()..number = null; + + final result = validator.validate(creditCard); + + expect(result.isValid, true); + }); } diff --git a/test/src/validations/valid_email_validation_test.dart b/test/src/validations/valid_email_validation_test.dart index 627d174..a6bfc38 100644 --- a/test/src/validations/valid_email_validation_test.dart +++ b/test/src/validations/valid_email_validation_test.dart @@ -23,4 +23,18 @@ void main() { expect(error.message, "'E-mail' is not a valid email address."); }); + + test('valid email validation ...', () { + final validator = TestLucidValidator(); + + validator + .ruleFor((e) => e.email, key: 'email', label: 'E-mail') // + .validEmailOrNull(); + + final user = UserNullableModel()..email = null; + + final result = validator.validate(user); + + expect(result.isValid, true); + }); } From 176700fc0a2b30e037bf447781775f039c8b002a Mon Sep 17 00:00:00 2001 From: Wellington Santos Date: Thu, 19 Dec 2024 09:30:31 -0300 Subject: [PATCH 4/4] update version --- CHANGELOG.md | 4 ++++ pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d9915c..43f9fa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.2.0 + +* Added support nullable + ## 1.1.0 * Added Label diff --git a/pubspec.yaml b/pubspec.yaml index 2578a59..e50536e 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: 1.1.0 +version: 1.2.0 environment: sdk: ">=3.0.0 <4.0.0"