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?
Hello
In my application I'm using AkkaHttpBackend which is
SttpBackend[Future,...]. In the logic of the application I'm usingcats.effect.IO. The sttp3 provides new syntax for my use case, which ismapK, so I was happy to use that while I was migrating from sttp2 to sttp3. After mapping I haveSttpBackend[IO, Any], so my expectation was: if I invoke.sendthen I have gotIO[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:
after wrapping
ioBackendusingDeferringSttpBackendthe test is passing.after thinking about it for a while I understand why it is happening, because
futureToIOis impure.my questions are:
SttpBackend[IO,...]I'm expecting it to be referential transparent.SttpBackend[Future, P]toSttpBackend[IO, P]"DeferringSttpBackendin the public api so everyone can use it?