How can I validate Option values with Cats validation?
The answer is traverse
, as 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)
So it looks like traverse
on Option
does what you want: it validates the contents of Some
, and ignores None
(i.e. passes it through as valid).
So you could write the following, replacing map
with traverse
:
scala> (validText(e.text), e.image.traverse(validImage)).mapN(ValidExample)
res4: cats.data.Validated[Int,ValidExample] = Valid(ValidExample(test,Some(test.png)))
And you're done.
Call sequence
on Option[Validated[String]]
, at place 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))
produces
Valid(ValidExample(test,Some(test.png)))