Skip to content

Commit

Permalink
Added must and mustWith
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobaraujo7 committed Aug 24, 2024
1 parent 5ae9eb3 commit b6dd984
Show file tree
Hide file tree
Showing 35 changed files with 231 additions and 121 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.0.4

* Added must and mustWith

## 0.0.3

* Added Cascade Mode
Expand Down
22 changes: 13 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ Note, the validate method returns a list of errors with all validation exception

Here’s a complete list of available validators you can use:

- **must**: custom validation.
- **mustWith**: custom validation with entity.
- **equalTo**: checks if value is equal to another value.
- **greaterThan**: Checks if number is greater than minimum value.
- **lessThan**: Checks if the number is less than max value.
Expand Down Expand Up @@ -114,14 +116,15 @@ Here’s a complete list of available validators you can use:

If you’re using the `lucid_validation` package in a Flutter app, integrating with `TextFormField` is straightforward.

Use the `byField('key')` for this:
Use the `byField(entity, 'key')` for this:

```dart
import 'package:flutter/material.dart';
import 'package:lucid_validation/lucid_validation.dart';
class LoginForm extends StatelessWidget {
final validator = UserValidation();
final validator = CredentialsValidation();
final credentials = CredentialsModel();
@override
Widget build(BuildContext context) {
Expand All @@ -130,11 +133,11 @@ class LoginForm extends StatelessWidget {
children: [
TextFormField(
decoration: const InputDecoration(hintText: 'Email'),
validator: validator.byField('email'),
validator: validator.byField(credentials, 'email'),
),
TextFormField(
decoration: const InputDecoration(hintText: 'Password'),
validator: validator.byField('password'),
validator: validator.byField(credentials, 'password'),
obscureText: true,
),
],
Expand All @@ -157,12 +160,13 @@ You can apply CascadeMode to your validation chain using the cascaded method:

```dart
return notEmpty() //
.cascade(CascadeMode.stopOnFirstFailure); // change cascade mode
.minLength(5, message: 'Must be at least 8 characters long')
.mustHaveLowercase()
.mustHaveUppercase()
.mustHaveNumbers()
.mustHaveSpecialCharacter()
.cascaded(CascadeMode.stopOnFirstFailure); // change cascade mode
```


Expand All @@ -171,17 +175,17 @@ You can apply CascadeMode to your validation chain using the cascaded method:
You can easily extend the functionality of `LucidValidation` by creating your own custom rules using `extensions`. Here’s an example of how to create a validation for phone numbers:

```dart
extension CustomValidPhoneValidator on LucidValidationBuilder<String> {
extension CustomValidPhoneValidator on LucidValidationBuilder<String, dynamic> {
LucidValidationBuilder<String> customValidPhone({String message = 'Invalid phone number format'}) {
return registerRule(
(value) => RegExp(r'^\(?(\d{2})\)?\s?9?\d{4}-?\d{4}\$').hasMatch(value),
return matchesPattern(
r'^\(?(\d{2})\)?\s?9?\d{4}-?\d{4}\$',
message,
'invalid_phone_format',
);
}
}
extension CustomValidPasswordValidator on LucidValidationBuilder<String> {
extension CustomValidPasswordValidator on LucidValidationBuilder<String, dynamic> {
LucidValidationBuilder<String> customValidPassword() {
return notEmpty()
.minLength(8)
Expand Down
10 changes: 7 additions & 3 deletions example/lib/domain/dtos/register_param_dto.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
class RegisterParamDto {
String email;
String password;
String phone;
String password;
String confirmPassword;

RegisterParamDto({
required this.email,
required this.password,
required this.phone,
required this.password,
required this.confirmPassword,
});

factory RegisterParamDto.empty() => RegisterParamDto(email: '', password: '', phone: '');
factory RegisterParamDto.empty() => RegisterParamDto(email: '', password: '', phone: '', confirmPassword: '');

setEmail(String value) => email = value;

setPassword(String value) => password = value;

setConfirmPassword(String value) => confirmPassword = value;

setPhone(String value) => phone = value;
}
10 changes: 5 additions & 5 deletions example/lib/domain/validations/extensions.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:lucid_validation/lucid_validation.dart';

extension CustomValidPasswordValidator on LucidValidationBuilder<String> {
LucidValidationBuilder<String> customValidPassword() {
extension CustomValidPasswordValidator on LucidValidationBuilder<String, dynamic> {
LucidValidationBuilder<String, dynamic> customValidPassword() {
return notEmpty() //
.minLength(5)
.mustHaveLowercase()
Expand All @@ -11,8 +11,8 @@ extension CustomValidPasswordValidator on LucidValidationBuilder<String> {
}
}

extension CustomValidPhoneValidator on LucidValidationBuilder<String> {
LucidValidationBuilder<String> customValidPhone(String message) {
return registerRule((value) => RegExp(r'^\(?(\d{2})\)?\s?9?\d{4}-?\d{4}$').hasMatch(value), message, 'invalid_phone');
extension CustomValidPhoneValidator on LucidValidationBuilder<String, dynamic> {
LucidValidationBuilder<String, dynamic> customValidPhone(String message) {
return must((value) => RegExp(r'^\(?(\d{2})\)?\s?9?\d{4}-?\d{4}$').hasMatch(value), message, 'invalid_phone');
}
}
4 changes: 4 additions & 0 deletions example/lib/domain/validations/register_param_validation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ class RegisterParamValidation extends LucidValidation<RegisterParamDto> {
ruleFor((registerParamDto) => registerParamDto.password, key: 'password') //
.customValidPassword();

ruleFor((registerParamDto) => registerParamDto.confirmPassword, key: 'confirmPassword') //
.customValidPassword()
.equalTo((registerParamDto) => registerParamDto.password, message: 'Password and confirm password must match');

ruleFor((registerParamDto) => registerParamDto.phone, key: 'phone') //
.customValidPhone('Phone invalid format');
}
Expand Down
4 changes: 2 additions & 2 deletions example/lib/presentation/login_page/login_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class _LoginPageState extends State<LoginPage> {
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
onChanged: loginParamDto.setEmail,
validator: validator.byField('email'),
validator: validator.byField(loginParamDto, 'email'),
decoration: const InputDecoration(
hintText: 'Email',
),
Expand All @@ -59,7 +59,7 @@ class _LoginPageState extends State<LoginPage> {
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
onChanged: loginParamDto.setPassword,
validator: validator.byField('password'),
validator: validator.byField(loginParamDto, 'password'),
obscureText: true,
decoration: const InputDecoration(
hintText: 'Password',
Expand Down
26 changes: 21 additions & 5 deletions example/lib/presentation/register_page/register_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,25 +63,41 @@ class _RegisterPageState extends State<RegisterPage> {
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 24),
),
const SizedBox(height: 24),
TextField(
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
onChanged: registerParamDto.setEmail,
validator: validator.byField(registerParamDto, 'email'),
decoration: const InputDecoration(
hintText: 'Email',
),
),
const SizedBox(height: 12),
TextField(
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: validator.byField(registerParamDto, 'phone'),
onChanged: registerParamDto.setPhone,
decoration: const InputDecoration(
hintText: '(11) 99999-9999',
),
),
const SizedBox(height: 12),
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: validator.byField(registerParamDto, 'password'),
onChanged: registerParamDto.setPassword,
obscureText: true,
decoration: const InputDecoration(
hintText: 'Password',
),
),
const SizedBox(height: 12),
TextField(
onChanged: registerParamDto.setPhone,
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: validator.byField(registerParamDto, 'confirmPassword'),
onChanged: registerParamDto.setConfirmPassword,
obscureText: true,
decoration: const InputDecoration(
hintText: '(11) 99999-9999',
hintText: 'Confirm Password',
),
),
const SizedBox(height: 12),
Expand Down
14 changes: 7 additions & 7 deletions lib/src/lucid_validation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ part 'validator_builder.dart';

class _PropSelector<E, TProp> {
final TProp Function(E entity) selector;
final LucidValidationBuilder<TProp> builder;
final LucidValidationBuilder<TProp, E> builder;

_PropSelector({required this.selector, required this.builder});
}
Expand All @@ -27,8 +27,8 @@ abstract class LucidValidation<E> {
/// final validator = UserValidation();
/// validator.ruleFor((user) => user.email).validEmail();
/// ```
LucidValidationBuilder<TProp> ruleFor<TProp>(TProp Function(E entity) func, {String key = ''}) {
final builder = LucidValidationBuilder<TProp>(key: key);
LucidValidationBuilder<TProp, E> ruleFor<TProp>(TProp Function(E entity) func, {String key = ''}) {
final builder = LucidValidationBuilder<TProp, E>(key: key);
final propSelector = _PropSelector<E, TProp>(selector: func, builder: builder);

_propSelectors.add(propSelector);
Expand All @@ -46,7 +46,7 @@ abstract class LucidValidation<E> {
/// final emailValidator = validator.byField('email');
/// String? validationResult = emailValidator('[email protected]');
/// ```
String? Function(String?)? byField(String key) {
String? Function(String?)? byField(E entity, String key) {
final propSelector = _propSelectors
.where(
(propSelector) => propSelector.builder.key == key,
Expand All @@ -58,9 +58,9 @@ abstract class LucidValidation<E> {
return (value) {
if (value == null) return null;
final builder = propSelector.builder;
final rules = builder._rules.cast<RuleFunc<String>>();
final rules = builder._rules.cast<RuleFunc<String, E>>();
for (var rule in rules) {
final result = rule(value);
final result = rule(value, entity);

if (!result.isValid) {
return result.error.message;
Expand Down Expand Up @@ -92,7 +92,7 @@ abstract class LucidValidation<E> {
final mode = propSelector.builder._mode;

for (var rule in propSelector.builder._rules) {
final result = rule(propValue);
final result = rule(propValue, entity);

if (!result.isValid) {
errors.add(result.error);
Expand Down
29 changes: 22 additions & 7 deletions lib/src/validator_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ enum CascadeMode {
/// Builder class used to define validation rules for a specific property type [TProp].
///
/// [TProp] represents the type of the property being validated.
typedef RuleFunc<TProp> = ValidatorResult Function(dynamic value);
typedef RuleFunc<TProp, Entity> = ValidatorResult Function(dynamic value, dynamic entity);

class LucidValidationBuilder<TProp> {
class LucidValidationBuilder<TProp, Entity> {
final String key;
final List<RuleFunc<TProp>> _rules = [];
final List<RuleFunc<TProp, Entity>> _rules = [];
var _mode = CascadeMode.continueExecution;

/// Creates a [LucidValidationBuilder] instance with an optional [key].
Expand All @@ -30,10 +30,10 @@ class LucidValidationBuilder<TProp> {
/// Example:
/// ```dart
/// final builder = LucidValidationBuilder<String>(key: 'username');
/// builder.registerRule((username) => username.isNotEmpty, 'Username cannot be empty');
/// builder.must((username) => username.isNotEmpty, 'Username cannot be empty');
/// ```
LucidValidationBuilder<TProp> registerRule(bool Function(TProp value) validator, String message, String code) {
ValidatorResult callback(value) => ValidatorResult(
LucidValidationBuilder<TProp, Entity> must(bool Function(TProp value) validator, String message, String code) {
ValidatorResult callback(value, entity) => ValidatorResult(
isValid: validator(value),
error: ValidatorError(
message: message,
Expand All @@ -47,8 +47,23 @@ class LucidValidationBuilder<TProp> {
return this;
}

LucidValidationBuilder<TProp, Entity> mustWith(bool Function(TProp value, Entity enntity) validator, String message, String code) {
ValidatorResult callback(value, entity) => ValidatorResult(
isValid: validator(value, entity),
error: ValidatorError(
message: message,
key: key,
code: code,
),
);

_rules.add(callback);

return this;
}

/// Changes the cascade mode for this validation.
LucidValidationBuilder<TProp> cascaded(CascadeMode mode) {
LucidValidationBuilder<TProp, Entity> cascade(CascadeMode mode) {
_mode = mode;
return this;
}
Expand Down
15 changes: 9 additions & 6 deletions lib/src/validators/equal_validator_validator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ part of 'validators.dart';
///
/// This extension adds an `equalTo` method that can be used to ensure that a value
/// is equal to a specific value.
extension EqualValidator<T> on LucidValidationBuilder<T> {
extension EqualValidator<T, E> on LucidValidationBuilder<T, E> {
/// Adds a validation rule that checks if the value is equal to [comparison].
///
/// [comparison] is the value that the field must match.
/// [predicate] is a function that returns the value to compare against.
/// [message] is the error message returned if the validation fails. Defaults to "Must be equal to $comparison".
/// [code] is an optional error code for translation purposes.
///
Expand All @@ -18,10 +18,13 @@ extension EqualValidator<T> on LucidValidationBuilder<T> {
/// final builder = LucidValidationBuilder<String>(key: 'confirmPassword');
/// builder.equalTo('password123');
/// ```
LucidValidationBuilder<T> equalTo(T comparison, {String message = r'Must be equal to $comparison', String code = 'equal_to_error'}) {
return registerRule(
(value) => value == comparison,
message.replaceAll(r'$comparison', comparison.toString()),
LucidValidationBuilder<T, E> equalTo(T Function(E entity) predicate, {String message = r'Must be equal', String code = 'equal_error'}) {
return mustWith(
(value, entity) {
final comparison = predicate(entity);
return value == comparison;
},
message,
code,
);
}
Expand Down
6 changes: 3 additions & 3 deletions lib/src/validators/greater_than_validator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ part of 'validators.dart';
///
/// This extension adds a `greaterThan` method that can be used to ensure that a number
/// is greater than a specified value.
extension GreaterThanValidator on LucidValidationBuilder<num> {
extension GreaterThanValidator on LucidValidationBuilder<num, dynamic> {
/// Adds a validation rule that checks if the [num] is greater than [minValue].
///
/// [minValue] is the value that the number must be greater than.
Expand All @@ -18,8 +18,8 @@ extension GreaterThanValidator on LucidValidationBuilder<num> {
/// final builder = LucidValidationBuilder<num>(key: 'age');
/// builder.greaterThan(18);
/// ```
LucidValidationBuilder<num> greaterThan(num minValue, {String message = r'Must be greater than $minValue', String code = 'greater_than'}) {
return registerRule(
LucidValidationBuilder<num, dynamic> greaterThan(num minValue, {String message = r'Must be greater than $minValue', String code = 'greater_than'}) {
return must(
(value) => value > minValue,
message.replaceAll('$minValue', minValue.toString()),
code,
Expand Down
6 changes: 3 additions & 3 deletions lib/src/validators/is_empty_validator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ part of 'validators.dart';
///
/// This extension adds an `isEmpty` method that can be used to ensure that a string
/// is empty.
extension IsEmptyValidator on LucidValidationBuilder<String> {
extension IsEmptyValidator on LucidValidationBuilder<String, dynamic> {
/// Adds a validation rule that checks if the [String] is empty.
///
/// [message] is the error message returned if the validation fails. Defaults to "Must be empty".
Expand All @@ -17,8 +17,8 @@ extension IsEmptyValidator on LucidValidationBuilder<String> {
/// final builder = LucidValidationBuilder<String>(key: 'field');
/// builder.isEmpty();
/// ```
LucidValidationBuilder<String> isEmpty({String message = 'Must be empty', String code = 'must_be_empty'}) {
return registerRule(
LucidValidationBuilder<String, dynamic> isEmpty({String message = 'Must be empty', String code = 'must_be_empty'}) {
return must(
(value) => value.isEmpty,
message,
code,
Expand Down
Loading

0 comments on commit b6dd984

Please sign in to comment.