package ch.epfl.bluebrain.nexus.sourcing.akka import akka.actor.ActorSystem import akka.pattern.ask import akka.util.Timeout import cats.effect.{ContextShift, Effect, IO, Timer} import cats.syntax.all._ import ch.epfl.bluebrain.nexus.sourcing.akka.Msg._ import retry.CatsEffect._ import retry.syntax.all._ import retry.{RetryDetails, RetryPolicy} import scala.reflect.ClassTag /** * Interface between the client and the actor for sending messages to an Akka Actor. * * @param name name of the actor * @param selection an actor selection strategy for a name and an identifier * @param askTimeout the time to wait for actor ask queries */ abstract private[akka] class AkkaActorIntermediator[F[_]: Timer]( name: String, selection: ActorRefSelection[F], askTimeout: Timeout )(implicit F: Effect[F], as: ActorSystem, policy: RetryPolicy[F]) { implicit private[akka] val contextShift: ContextShift[IO] = IO.contextShift(as.dispatcher) implicit private[akka] def noop[A]: (A, RetryDetails) => F[Unit] = retry.noop[F, A] implicit private val timeout: Timeout = askTimeout private[akka] def send[M <: Msg, Reply, A](id: String, msg: M, f: Reply => A)(implicit Reply: ClassTag[Reply] ): F[A] = selection(name, id).flatMap { ref => val future = IO(ref ? msg) val fa = IO.fromFuture(future).to[F] fa.flatMap[A] { case Reply(value) => F.pure(f(value)) case te: TypeError => F.raiseError(te) case um: UnexpectedMsgId => F.raiseError(um) case cet: CommandEvaluationTimeout[_] => F.raiseError(cet) case cee: CommandEvaluationError[_] => F.raiseError(cee) case other => F.raiseError(TypeError(id, Reply.runtimeClass.getSimpleName, other)) } .retryingOnAllErrors[Throwable] } }