如何使用Cats验证来验证Option值?

时间:2019-02-13 09:26:28

标签: scala validation scala-cats

我正在尝试更改使用cats验证的代码,例如:

  case class Example(text: String, image: String)
  case class ValidExample(text: String, image: String)

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

  def validText(text: String) = if (text.nonEmpty) text.valid else invalid(-1)
  def validImage(image: String) = if (image.endsWith(".png")) image.valid else invalid(-1)
  val e = Example("test", "test.png")
  (validText(e.text), validImage(e.image)).mapN(ValidExample)

哪个工作正常。

但是我的更改要求image字段为Option,例如:

  case class Example(text: String, image: Option[String])
  case class ValidExample(text: String, image: Option[String])

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

  def validText(text: String) = if (text.nonEmpty) text.valid else invalid(-1)
  def validImage(image: String) = if (image.endsWith(".png")) image.valid else invalid(-1)
  val e = Example("test", Some("test.png"))
  (validText(e.text), e.image.map(validImage)).mapN(ValidExample)

mapN失败,因为类型突然不同,它说:

value mapN is not a member of (cats.data.Validated[Int,String], Option[cats.data.Validated[Int,String]])

我希望它仅验证值是否存在。 因此,如果存在值,则应将其作为验证结果的一部分,否则将忽略该字段。 我知道有一些组合验证的方法,但是在我的真实代码中,比这样的事情要复杂得多。

有没有一种简单的方法可以做到这一点? 我在文档或搜索中都找不到任何相关信息。

感谢帮助!

2 个答案:

答案 0 :(得分:5)

答案是traverseas usual:):

scala> val exampleWithImage = Example("test", Some("test.png"))
exampleWithImage: Example = Example(test,Some(test.png))

scala> val exampleWithoutImage = Example("test", None)
exampleWithoutImage: Example = Example(test,None)

scala> val badExampleWithImage = Example("test", Some("foo"))
badExampleWithImage: Example = Example(test,Some(foo))

scala> exampleWithImage.image.traverse(validImage)
res1: cats.data.Validated[Int,Option[String]] = Valid(Some(test.png))

scala> exampleWithoutImage.image.traverse(validImage)
res2: cats.data.Validated[Int,Option[String]] = Valid(None)

scala> badExampleWithImage.image.traverse(validImage)
res3: cats.data.Validated[Int,Option[String]] = Invalid(-1)

因此,traverse上的Option似乎可以满足您的要求:它将验证Some的内容,并忽略None(即将其作为有效值传递)。

因此您可以编写以下内容,将map替换为traverse

scala> (validText(e.text), e.image.traverse(validImage)).mapN(ValidExample)
res4: cats.data.Validated[Int,ValidExample] = Valid(ValidExample(test,Some(test.png)))

您完成了。

答案 1 :(得分:2)

sequence上的Option[Validated[String]]上呼叫e.image.map(validImage)

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

case class Example(text: String, image: Option[String])
case class ValidExample(text: String, image: Option[String])

def validText(text: String) = if (text.nonEmpty) text.valid else invalid(-1)
def validImage(image: String) = if (image.endsWith(".png")) image.valid else invalid(-1)
val e = Example("test", Some("test.png"))
println((validText(e.text), e.image.map(validImage).sequence).mapN(ValidExample))

产生

Valid(ValidExample(test,Some(test.png)))