Skip to main content

FawnDecoder

Similar to circe Decoder again, this time we allow decoding to fail with a Throwable, and there is an extra emap method to help combining.

abstract class FawnDecoder[T] { decoder =>
def decode(s: String): Either[Throwable, T]

def emap[B](f: T => Either[Throwable, B]): FawnDecoder[B] =
(s: String) => decoder.decode(s).flatMap(f)
}

Also provided is ApplicativeError, which comes along with Applicative and Semigroupal, letting you do things like:

orElse combination of decoders​

case class SimpleError(msg: String) extends Throwable(msg) with NoStackTrace

val strict: FawnDecoder[Boolean] = new FawnDecoder[Boolean] {
def decode(s: String) = s match {
case "true" => true.asRight
case "false" => false.asRight
case other => SimpleError(s"'$other' is not a valid boolean").asLeft
}
}
// strict: FawnDecoder[Boolean] = repl.MdocSession$App0$$anon$1@3864bd8f
val lessStrict: FawnDecoder[Boolean] = new FawnDecoder[Boolean] {
def decode(s: String) = s match {
case "y" => true.asRight
case "n" => false.asRight
case other => SimpleError(s"'$other' is not a valid boolean").asLeft
}
}
// lessStrict: FawnDecoder[Boolean] = repl.MdocSession$App0$$anon$2@261ed679

val combined = strict.orElse(lessStrict)
// combined: FawnDecoder[Boolean] = com.meltwater.fawn.codec.FawnDecoder$$anon$1$$anonfun$handleErrorWith$4@3aaae6cd

combined.decode("true")
// res1: Either[Throwable, Boolean] = Right(true)
combined.decode("y")
// res2: Either[Throwable, Boolean] = Right(true)
combined.decode("oops")
// res3: Either[Throwable, Boolean] = Left(repl.MdocSession$App0$SimpleError: 'oops' is not a valid boolean)