imapError message not returned when type decoded inside an Option
We have some codecs which bootstrap the String codec and convert values to our own internal enumeratum values.
The schema models the data as string or null, which we attempt to model as Option[OurEnumType].
The decoding works fine, except when an unexpected String comes through. The error message presented is
Exhausted alternatives for type org.apache.avro.util.Utf8 - rather than the underlying failure with our custom message.
Broadly our codec looks a bit like the following:
case class MyThing(value: String)
object MyThing {
def fromString(s: String): Option[MyThing] = if (s=="thing") Some(MyThing(s)) else None
implicit val codec: Codec[MyThing] =
Codec.string.imapError(str => fromString(str).toRight(AvroError(s"$str is not what we want")))(_.value)
}
Is there a way to make the underlying error message bubble up? This issue led to a bit of head scratching because the stack trace didn't identify where the problem was either.
I managed to get something which emits the correct error message with the following, but it would be nice to have something in the API to support this, similar to how Circe works
implicit val myThingCodec: Codec[Option[MyThing]] =
Codec
.option[String]
.imapError[Option[MyThing]] ({
case Some(str) => fromString(str).map(Some(_)).toRight(AvroError(s"$str not what we want"))
case None => Right(None)
})(maybeMyThing => maybeMyThing.map(myThing => myThing.value))
This is really a special case of a more general issue, which is that union decoders don't propagate specific errors when all alternatives fail. It could be nice to expose the reason why each alternative failed.
That said, even if we don't do that it might also be worth special-casing the Option codec so that it reports the underlying error when failing to decode a non-null value.