Skip to content

Commit

Permalink
Suspend the evaluation of the BackendStub.send() effect in the target…
Browse files Browse the repository at this point in the history
… monad (#2100)
  • Loading branch information
adamw committed Mar 7, 2024
1 parent bdb59ff commit dc95862
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ abstract class AbstractBackendStub[F[_], P](
withMatchers(matchers.orElse(wrappedPartial))
}

override def send[T](request: GenericRequest[T, P with Effect[F]]): F[Response[T]] =
override def send[T](request: GenericRequest[T, P with Effect[F]]): F[Response[T]] = monad.suspend {
Try(matchers.lift(request)) match {
case Success(Some(response)) =>
adjustExceptions(request)(tryAdjustResponseType(request.response, response.asInstanceOf[F[Response[T]]])(monad))
Expand All @@ -59,6 +59,7 @@ abstract class AbstractBackendStub[F[_], P](
}
case Failure(e) => adjustExceptions(request)(monad.error(e))
}
}

private def adjustExceptions[T](request: GenericRequest[_, _])(t: => F[T]): F[T] =
SttpClientException.adjustExceptions(monad)(t)(
Expand Down
38 changes: 37 additions & 1 deletion core/src/test/scala/sttp/client4/testing/BackendStubTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import sttp.client4.internal._
import sttp.client4.monad.IdMonad
import sttp.client4.ws.async._
import sttp.model._
import sttp.monad.{FutureMonad, TryMonad}
import sttp.monad.{FutureMonad, MonadError, TryMonad}
import sttp.ws.WebSocketFrame
import sttp.ws.testing.WebSocketStub

import java.io.ByteArrayInputStream
import java.util.concurrent.TimeoutException
import java.util.concurrent.atomic.AtomicInteger
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.util.{Failure, Success, Try}
Expand Down Expand Up @@ -359,6 +360,41 @@ class BackendStubTests extends AnyFlatSpec with Matchers with ScalaFutures {
result shouldBe Success(Right(1: Byte))
}

it should "evaluate side effects on each request" in {
// given
type Lazy[T] = () => T
object LazyMonad extends MonadError[Lazy] {
override def unit[T](t: T): Lazy[T] = () => t
override def map[T, T2](fa: Lazy[T])(f: T => T2): Lazy[T2] = () => f(fa())
override def flatMap[T, T2](fa: Lazy[T])(f: T => Lazy[T2]): Lazy[T2] = () => f(fa())()
override def error[T](t: Throwable): Lazy[T] = () => throw t
override protected def handleWrappedError[T](rt: Lazy[T])(h: PartialFunction[Throwable, Lazy[T]]): Lazy[T] =
() =>
try rt()
catch { case e if h.isDefinedAt(e) => h(e)() }
override def ensure[T](f: Lazy[T], e: => Lazy[Unit]): Lazy[T] = () =>
try f()
finally e()
}

val counter = new AtomicInteger(0)
val backend: Backend[Lazy] = BackendStub(LazyMonad).whenRequestMatchesPartial { case _ =>
counter.getAndIncrement()
Response.ok("ok")
}

// creating the "send effect" once ...
val result = basicRequest.get(uri"http://example.org").send(backend)

// when
// ... and then using it twice
result().body shouldBe Right("ok")
result().body shouldBe Right("ok")

// then
counter.get() shouldBe 2
}

private val testingStubWithFallback = SyncBackendStub
.withFallback(testingStub)
.whenRequestMatches(_.uri.path.startsWith(List("c")))
Expand Down

0 comments on commit dc95862

Please sign in to comment.