diff --git a/core/src/main/scala/dev/profunktor/fs2rabbit/effects/EnvelopeDecoder.scala b/core/src/main/scala/dev/profunktor/fs2rabbit/effects/EnvelopeDecoder.scala index 97b13197..debd4f97 100644 --- a/core/src/main/scala/dev/profunktor/fs2rabbit/effects/EnvelopeDecoder.scala +++ b/core/src/main/scala/dev/profunktor/fs2rabbit/effects/EnvelopeDecoder.scala @@ -15,13 +15,14 @@ */ package dev.profunktor.fs2rabbit.effects -import cats.{Applicative, ApplicativeThrow} +import cats.{Applicative, ApplicativeError, ApplicativeThrow, MonadError} import cats.data.Kleisli import dev.profunktor.fs2rabbit.model.{AmqpFieldValue, AmqpProperties, ExchangeName, RoutingKey} import dev.profunktor.fs2rabbit.model.AmqpFieldValue._ import cats.implicits._ -object EnvelopeDecoder { +object EnvelopeDecoder extends EnvelopeDecoderInstances { + def apply[F[_], A](implicit e: EnvelopeDecoder[F, A]): EnvelopeDecoder[F, A] = e def properties[F[_]: Applicative]: EnvelopeDecoder[F, AmqpProperties] = @@ -81,3 +82,16 @@ object EnvelopeDecoder { ): EnvelopeDecoder[F, Option[A]] = Kleisli(_.properties.headers.get(name).traverse(h => F.catchNonFatal(pf(h)))) } + +sealed trait EnvelopeDecoderInstances { + + implicit def decoderAttempt[F[_], E: ApplicativeError[F, *], A](implicit + decoder: EnvelopeDecoder[F, A] + ): EnvelopeDecoder[F, Either[E, A]] = + decoder.attempt + + implicit def decoderOption[F[_], E: ApplicativeError[F, *], A](implicit + decoder: EnvelopeDecoder[F, A] + ): EnvelopeDecoder[F, Option[A]] = + decoder.attempt.map(_.toOption) +} diff --git a/core/src/test/scala/dev/profunktor/fs2rabbit/effects/EnvelopeDecoderSpec.scala b/core/src/test/scala/dev/profunktor/fs2rabbit/effects/EnvelopeDecoderSpec.scala index 8d5e8ee5..341b1ef8 100644 --- a/core/src/test/scala/dev/profunktor/fs2rabbit/effects/EnvelopeDecoderSpec.scala +++ b/core/src/test/scala/dev/profunktor/fs2rabbit/effects/EnvelopeDecoderSpec.scala @@ -31,11 +31,14 @@ import cats.effect.unsafe.implicits.global class EnvelopeDecoderSpec extends AsyncFunSuite { + import EnvelopeDecoder._ // Available instances of EnvelopeDecoder for any ApplicativeError[F, Throwable] EnvelopeDecoder[Either[Throwable, *], String] EnvelopeDecoder[SyncIO, String] EnvelopeDecoder[EitherT[IO, String, *], String] EnvelopeDecoder[Try, String] + EnvelopeDecoder[Try, Option[String]] + EnvelopeDecoder[Try, Either[Throwable, String]] test("should decode a UTF-8 string") { val msg = "hello world!" @@ -86,4 +89,32 @@ class EnvelopeDecoderSpec extends AsyncFunSuite { .unsafeToFuture() } + test("should decode a UTF-8 string - Attempt") { + val msg = "hello world!" + val raw = msg.getBytes(StandardCharsets.UTF_8) + + EnvelopeDecoder[IO, Either[Throwable, String]] + .run( + AmqpEnvelope(DeliveryTag(0L), raw, AmqpProperties.empty, ExchangeName("test"), RoutingKey("test.route"), false) + ) + .flatMap { result => + IO(assert(result == Right(msg))) + } + .unsafeToFuture() + } + + test("should decode a UTF-8 string - Attempt option") { + val msg = "hello world!" + val raw = msg.getBytes(StandardCharsets.UTF_8) + + EnvelopeDecoder[IO, Option[String]] + .run( + AmqpEnvelope(DeliveryTag(0L), raw, AmqpProperties.empty, ExchangeName("test"), RoutingKey("test.route"), false) + ) + .flatMap { result => + IO(assert(result == Option(msg))) + } + .unsafeToFuture() + } + }