【问题标题】:Validation of multiple ADTs with Scala Cats Validated使用 Scala Cats Validated 验证多个 ADT
【发布时间】:2019-06-04 04:20:11
【问题描述】:

我正在尝试验证 scala 中的配置。首先,我将配置 json 转换为相应的案例类,然后验证它。因为我想慢慢失败,所以我收集所有失败的验证,而不是在第一次验证失败后返回。我打算使用猫库Cats validation提供的应用函子。

我面临的问题是链接中显示的表单验证适用于简单案例类

final case class RegistrationData(username: String, password: String, firstName: String, lastName: String, age: Int)

// Below is the code snippet for applying validation from the link   itself
 {
(validateUserName(username),
validatePassword(password),
validateFirstName(firstName),
validateLastName(lastName),
validateAge(age)).mapN(RegistrationData)}

// A more complex case for validations
final case class User(name:String,adds:List[Addresses])
final case class Address(street:String,lds:List[LandMark])
final case class LandMark(wellKnown:Boolean,street:String)

在这种情况下,字段“用户名”的验证独立于“名字”的验证。但是如果

  1. 我不得不对“firstName”和“userName”进行某种类型的验证(假设 Levenstein 距离应该是
  2. case 类不是由简单的原语 (Int,String) 组成,而是有其他 case 类作为其成员。例如上面提到的用户案例类。

一般来说,应用函子的方法适合这种情况吗?我什至应该收集所有失败的验证吗?

PS:如果提到不正确的地方,请原谅我,我是 scala 的新手。

【问题讨论】:

  • validateNames(firstName, userName) 有什么问题?参数的类型有什么关系?
  • @Dima validateNames(firstName, userName) 它会返回什么?它是否返回有效的用户名或有效的名字。对不起,你能详细说明一下吗?
  • 您选择它返回的内容。这取决于你。

标签: scala validation functor applicative scala-cats


【解决方案1】:

基于猫的验证示例

import cats.data._
import cats.data.Validated._
import cats.implicits._

final case class RegistrationData(name: Name, age: Int, workAge: Int)

final case class Name(firstName: String, lastName: String)

sealed trait DomainValidation {
  def errorMessage: String
}

case object FirstNameHasSpecialCharacters extends DomainValidation {
  def errorMessage: String =
    "First name cannot contain spaces, numbers or special characters."
}

case object LastNameHasSpecialCharacters extends DomainValidation {
  def errorMessage: String =
    "Last name cannot contain spaces, numbers or special characters."
}

case object AgeIsInvalid extends DomainValidation {
  def errorMessage: String =
    "You must be aged 18 and not older than 75 to use our services."
}

case object AgeIsLessThanWorkInvalid extends DomainValidation {
  def errorMessage: String =
    "You must be aged 18 and not older than 75 to use our services."
}

sealed trait FormValidatorNec {

  type ValidationResult[A] = ValidatedNec[DomainValidation, A]

  private def validateFirstName(firstName: String): ValidationResult[String] =
    if (firstName.matches("^[a-zA-Z]+$")) firstName.validNec
    else FirstNameHasSpecialCharacters.invalidNec

  private def validateLastName(lastName: String): ValidationResult[String] =
    if (lastName.matches("^[a-zA-Z]+$")) lastName.validNec
    else LastNameHasSpecialCharacters.invalidNec

  private def validateAge(age: Int,
                          workAge: Int): ValidationResult[(Int, Int)] = {
    if (age >= 18 && age <= 75 && workAge >= 0)
      if (age > workAge)
        (age, workAge).validNec
      else
        AgeIsLessThanWorkInvalid.invalidNec
    else
      AgeIsInvalid.invalidNec
  }

  def validateForm(firstName: String,
                   lastName: String,
                   age: Int,
                   workAge: Int): ValidationResult[RegistrationData] = {
    (
      (validateFirstName(firstName), validateLastName(lastName)).mapN(Name),
      validateAge(age, workAge)
    ).mapN {
      case (n, (a, w)) => RegistrationData(name = n, age = a, workAge = w)
    }
  }

}

object FormValidatorNec extends FormValidatorNec

println(FormValidatorNec.validateForm("firstname", "lastname", 40, 30))
println(FormValidatorNec.validateForm("firs2tname", "lastname", 20, 30))

查看fiddle

只有当元组(ValidationResult[_], ValidationResult[_], ...)中的数据为Valid时,才会调用mapN的函数。如果元组中的一个或多个元素是Invalid,则将它们收集到NotEmtpyChain 中。

总之,所有validate方法都被调用,当所有方法都返回Valid[_]时,mapN函数被应用。

【讨论】:

  • 谢谢。你能解释一件事吗:在 mapN 中,只有有效数据的情况。未验证如何处理? println(FormValidatorNec.validateForm("firs2tname", "lastname", 20, 30)) 的情况如何处理?
  • 我认为 mapN 的工作方式如下:它有一个 apply 方法,该方法采用柯里化函数来实例化 RegistrationData,并按顺序重复应用每个成员的 ValidationResult[]。我有点困惑,你能解释一下吗?
  • 检查我的编辑。现在有意义吗? mapN 应用于元组,而不是元组内的每个元素。
  • 是的,我明白了,只是想问更多更多的小事。请您开始聊天。
  • 不客气。不过不知道如何开始聊天:)
猜你喜欢
  • 2017-05-03
  • 1970-01-01
  • 2017-02-08
  • 2018-12-02
  • 2019-03-09
  • 1970-01-01
  • 2019-12-24
  • 2019-05-19
  • 2021-02-25
相关资源
最近更新 更多