Input validation is a business logic so we should hide this process in the domain layer. as discussed here
I do it like this
Login validator
class LoginValidator extends Validator {
String email;
String password;
LoginValidator(this.email, this.password);
@override
void validate(Function() success, Function(List<Failure>) errors) {
List<Failure> failures = [];
if (email.trim().isEmpty) {
failures.add(const EmailValidationFailure('Email is required'));
} else if (!validator.isEmail(email)) {
failures.add(const EmailValidationFailure());
}
if (password.trim().isEmpty) {
failures.add(const PasswordValidationFailure('Password is required'));
}
if (failures.isNotEmpty) {
errors(failures);
} else {
success();
}
}
}
And I created a failure class for each input field
class EmailValidationFailure extends Failure {
const EmailValidationFailure([String message = 'Email is invalid'])
: super(message);
}
class PasswordValidationFailure extends Failure {
const PasswordValidationFailure([String message = 'Incorrect password'])
: super(message);
}
And I use the validator in the use case
class LoginUseCaseInteractor implements LoginUseCaseInputPort {
final AccountRepository _repository;
final LoginUseCaseOutputPort outputPort;
LoginUseCaseInteractor(this._repository, this.outputPort);
@override
void login(LoginParams params) {
LoginValidator(params.email, params.password).validate(() async {
outputPort.loading();
Result<bool> result = await _repository.login(params);
result.when(
success: (data) {
outputPort.success();
},
error: (error) {
outputPort.requestError(error);
},
);
}, (errors) {
outputPort.formValidationErrors(errors);
});
}
}
and finally I handle the presentation logic in the presenter which it implements the output port of the login use case
class LoginPresenter implements LoginUseCaseOutputPort {
Reader read;
LoginPresenter(this.read) : super(LoginState.initial());
@override
void formValidationErrors(List<Failure> errors) =>
state = LoginState.formValidationErrors(errors);
@override
void success() => read(setRootPresenterProvider.notifier).setMainPageAsRoot();
@override
void loading() => state = LoginState.loading();
@override
void requestError(Failure error) => state = LoginState.requestError(error);
}
What I do I create a failure class for each input field and return failures of all fields in a list and in the presentation logic I check the input failures by type
My Question: What if I have a large form (eg:15 fields), Should I create a failure class for each of them? Is there a better way to handle the validation?