Clean Architecture 中的表单验证

Form validation in Clean Architecture

输入验证是一种业务逻辑,因此我们应该将此过程隐藏在领域层中。 正如所讨论的 here

我是这样做的

登录验证器

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();
    }
  }
}

并且我为每个输入字段创建了一个失败 class

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);
} 

并且我在用例中使用验证器

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);
    });
  }
}

最后我在演示器中处理演示逻辑,它实现了登录用例的输出端口

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);
}

我做什么 我为每个输入字段创建一个失败 class 和列表中所有字段的 return 个失败,在表示逻辑中我按类型检查输入失败

我的问题: 如果我有一个大表格(例如:15 个字段)怎么办,我应该为每个字段创建一个失败 class 吗? 有没有更好的方法来处理验证?

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?

每个可能发生的错误都必须是可识别的。通过专用 classes、故障代码或常量。您选择哪一个取决于您要提供给客户端的错误上下文信息。

我也不会将消息放在失败对象中,因为由演示者来选择错误类型的演示。向用户显示错误的方式在很大程度上取决于用户界面。也许错误不需要字符串,它只是以图标或彩色标记等形式呈现。交互器不应创建特定于语言的字符串。这是用户界面的一部分。

在简单的情况下,您可能只需要一个错误代码或常量,如 401,演示者将其转换为 Login failed。当然,对于这种情况,您也可以使用通用错误对象。

class失败{ 内部代码; }

在其他情况下,您可能希望显示更详细的错误消息,例如 E-mails under the domain '@somedomain.com' are not allowed to login.。在这种情况下,您应该使用错误对象而不是仅使用简单的代码来提供详细信息。例如

class 登录域失败 { 字符串本地部分; 字符串域名 }

演示者然后可以使用此信息生成失败字符串或以其他方式显示错误,例如在输入字段中突出显示域名或您能想到的任何内容。