circe Validation
Installation
libraryDependencies += "io.taig" %%% "circe-validation" % "0.3.1"
About
This repository is primarily an experimental exploration of validation handling with circe. My main concerns about the current implementation are:
ValidatingDecoder
lives inio.circe
to override the package scoped methoddecodeAccumulating
- Validation failures cannot be distinguished from circe’s
DecodingFailures
Usage
import cats.implicits._
import cats.data.Validated._
import cats.data.ValidatedNel
import io.circe.generic.semiauto._
import io.circe.parser._
import io.circe.Decoder
import io.circe.ValidatingDecoder
object Validation {
def email(value: String): ValidatedNel[String, String] =
if (value.contains("@")) valid(value)
else invalidNel("not an email")
def min(length: Int)(value: String): ValidatedNel[String, String] =
if (value.length < length) invalidNel(s"min $length")
else valid(value)
}
case class Name(value: String)
case class Email(value: String)
case class Person(name: Name, email: Email)
def liftName(value: String): ValidatedNel[String, Name] =
Validation.min(3)(value) map Name
def liftEmail(value: String): ValidatedNel[String, Email] =
Validation.email(value) |+| Validation.min(5)(value) map Email
implicit val decoderName: Decoder[Name] = ValidatingDecoder[String](liftName)
implicit val decoderEmail: Decoder[Email] = ValidatingDecoder[String](liftEmail)
implicit val decoderPerson: Decoder[Person] = deriveDecoder
val text = """{ "name":"Qt", "email":"foo" }"""
val Right(json) = parse(text)
val cursor = json.hcursor
// Default behavior is decoding without accumulation
val Left(decodingFailure) = Decoder[Person].apply(cursor)
val Invalid(accumulatedDecodingFailures) = Decoder[Person].decodeAccumulating(cursor)
decodingFailure.show
// res0: String = "DecodingFailure at .name: min 3"
accumulatedDecodingFailures.show
// res1: String = "NonEmptyList(DecodingFailure at .name: min 3, DecodingFailure at .email: not an email, DecodingFailure at .email: min 5)"