使用 Scala Cats 验证多个 ADT
Validation of multiple ADTs with Scala Cats Validated
我正在尝试验证 scala 中的配置。首先,我将配置 json 转换为相应的大小写 class,然后对其进行验证。因为我想慢慢失败,所以我收集了所有失败的验证,而不是在第一次失败的验证后返回。我打算使用 cats 库 Cats validation.
提供的应用函子
我面临的问题是 link 中显示的表单验证适用于简单情况 class
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)
在这种情况下,对字段 'username' 的验证独立于对 'firstName' 的验证。但是如果
- 我不得不强加一些同时采用 'firstName' 和 'userName' 的类型验证(假设两个 Levenstein 距离应该 <= 某个数字)。
- case class 不是由简单的原语 (Int,String) 组成,而是有其他 case classes 作为它的成员。例如上面提到的用户案例class。
一般来说,应用函子的方法是否适合这种情况?我是否应该收集所有失败的验证?
PS: 说的不对还请见谅,我是scala新手
基于猫验证示例
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
方法都被调用,并且当所有 return Valid[_]
应用 mapN
函数时。
我正在尝试验证 scala 中的配置。首先,我将配置 json 转换为相应的大小写 class,然后对其进行验证。因为我想慢慢失败,所以我收集了所有失败的验证,而不是在第一次失败的验证后返回。我打算使用 cats 库 Cats validation.
提供的应用函子我面临的问题是 link 中显示的表单验证适用于简单情况 class
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)
在这种情况下,对字段 'username' 的验证独立于对 'firstName' 的验证。但是如果
- 我不得不强加一些同时采用 'firstName' 和 'userName' 的类型验证(假设两个 Levenstein 距离应该 <= 某个数字)。
- case class 不是由简单的原语 (Int,String) 组成,而是有其他 case classes 作为它的成员。例如上面提到的用户案例class。
一般来说,应用函子的方法是否适合这种情况?我是否应该收集所有失败的验证?
PS: 说的不对还请见谅,我是scala新手
基于猫验证示例
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
方法都被调用,并且当所有 return Valid[_]
应用 mapN
函数时。