Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

converting Future backend to cats' IO backend, referential transparency and defer #907

Open
bwiercinski opened this issue Mar 25, 2021 · 1 comment
Labels
enhancement todo issue is still valid and waiting for contributor

Comments

@bwiercinski
Copy link

Hello
In my application I'm using AkkaHttpBackend which is SttpBackend[Future,...]. In the logic of the application I'm using cats.effect.IO. The sttp3 provides new syntax for my use case, which is mapK, so I was happy to use that while I was migrating from sttp2 to sttp3. After mapping I have SttpBackend[IO, Any], so my expectation was: if I invoke .send then I have got IO[Response[...]] and since IO is referential transparent I can use the benefits of that in my application.

The bellow example shows that it is not true:

import cats.effect._
import cats.~>
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AsyncWordSpec
import sttp.capabilities.Effect
import sttp.client3._
import sttp.client3.asynchttpclient.future.AsyncHttpClientFutureBackend
import sttp.client3.impl.cats.implicits._
import sttp.model.Uri
import scala.concurrent._

class DeferringFutureBackendSpec extends AsyncWordSpec with Matchers {
  implicit val contextShift: ContextShift[IO] = IO.contextShift(ExecutionContext.global)

  def createBackend(): SttpBackend[IO, Any] = {
    val futureToIO = λ[Future ~> IO](future => Async.fromFuture(IO(future)))
    val ioToFuture = λ[IO ~> Future](_.unsafeToFuture())
    val mockBackend: SttpBackend[Future, Any] = AsyncHttpClientFutureBackend.stub().whenAnyRequest.thenRespondCyclic("1", "2")
    val ioBackend: SttpBackend[IO, Any] = mockBackend.mapK(futureToIO, ioToFuture)

    ioBackend
    // new DeferringSttpBackend(ioBackend) // this will work
  }

  class DeferringSttpBackend(delegate: SttpBackend[IO, Any]) extends DelegateSttpBackend(delegate) {
    override def send[T, R >: Effect[IO]](request: Request[T, R]): IO[Response[T]] = Sync[IO].defer(delegate.send(request))
    override def close(): IO[Unit] = Sync[IO].defer(delegate.close())
  }

  "defer action for each request" in {
    val backend: SttpBackend[IO, Any] = createBackend()
    val sendAction: IO[Response[String]] = backend.send(basicRequest.response(asStringAlways).get(Uri("localhost")))
    for {
      a <- sendAction
      b <- sendAction
    } yield {
      a.body shouldBe "1"
      b.body shouldBe "2" // TestFailedException: expected "2" actual "1"
    }
  }.unsafeToFuture()
}

after wrapping ioBackend using DeferringSttpBackend the test is passing.

after thinking about it for a while I understand why it is happening, because futureToIO is impure.

my questions are:

  • If you would see this code for the first time what behavior would you expect. If I'm using SttpBackend[IO,...] I'm expecting it to be referential transparent.
  • Is this a bug or bad configuration?
  • Is this worth mentioning in the documentation? Maybe add a new example called "converting SttpBackend[Future, P] to SttpBackend[IO, P]"
  • Is there a place for DeferringSttpBackend in the public api so everyone can use it?
@adamw
Copy link
Member

adamw commented Mar 25, 2021

Nice catch :) I think the problem here is that you can't use FunctionK to transform a Future into an IO, as it doesn't use by-name parameters. And that's equally true for sttp client and any other usage.

At least documenting how to convert a future-based backend into any cast-effect-supported effect is certainly a good idea. I'd probably just implement the backend, though, as a backend wrapper, without using .mapK (but copying & improving its implementation - the MappedKSttpBackend).

@Pask423 Pask423 added enhancement todo issue is still valid and waiting for contributor labels Mar 1, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement todo issue is still valid and waiting for contributor
Projects
None yet
Development

No branches or pull requests

3 participants