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

1654 #1716

Merged
merged 5 commits into from
Feb 28, 2023
Merged

1654 #1716

Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package sttp.client3.armeria
haoqin marked this conversation as resolved.
Show resolved Hide resolved

import _root_.zio._
import sttp.capabilities.Effect
import sttp.capabilities.zio.ZioStreams
import sttp.client3._
import sttp.client3.impl.zio.{ExtendEnv, SttpClientStubbingBase}

package object zio {

// Forked from async-http-client-backend/zio
// - Removed WebSocket support

/** ZIO-environment service definition, which is an SttpBackend. */
type SttpClient = SttpClient.Service
haoqin marked this conversation as resolved.
Show resolved Hide resolved
type SttpClientStubbing = SttpClientStubbing.Service
haoqin marked this conversation as resolved.
Show resolved Hide resolved

object SttpClient {
type Service = SttpBackend[Task, ZioStreams]
}

/** Sends the request. Only requests for which the method & URI are specified can be sent.
*
* @return
* An effect resulting in a`Response`, containing the body, deserialized as specified by the request (see
* `RequestT.response`), if the request was successful (1xx, 2xx, 3xx response codes), or if there was a
* protocol-level failure (4xx, 5xx response codes).
*
* A failed effect, if an exception occurred when connecting to the target host, writing the request or reading the
* response.
*
* Known exceptions are converted to one of `SttpClientException`. Other exceptions are kept unchanged.
*/
def send[T](
request: Request[T, Effect[Task] with ZioStreams]
): ZIO[SttpClient, Throwable, Response[T]] =
ZIO.serviceWithZIO[SttpClient.Service](_.send(request))

/** A variant of `send` which allows the effects that are part of the response handling specification (when using
* websockets or resource-safe streaming) to use an `R` environment.
*/
def sendR[T, R](
request: Request[T, Effect[RIO[R, *]] with ZioStreams]
): ZIO[SttpClient with R, Throwable, Response[T]] =
ZIO.serviceWithZIO[SttpClient.Service](_.extendEnv[R].send(request))

object SttpClientStubbing extends SttpClientStubbingBase[Any, ZioStreams] {
override private[sttp] def serviceTag: Tag[SttpClientStubbing.Service] = implicitly
override private[sttp] def sttpBackendTag: Tag[SttpClient.Service] = implicitly
}

object stubbing {
import SttpClientStubbing.StubbingWhenRequest

def whenRequestMatches(p: Request[_, _] => Boolean): StubbingWhenRequest =
StubbingWhenRequest(p)

val whenAnyRequest: StubbingWhenRequest =
StubbingWhenRequest(_ => true)

def whenRequestMatchesPartial(
partial: PartialFunction[Request[_, _], Response[_]]
): URIO[SttpClientStubbing, Unit] =
ZIO.serviceWithZIO(_.whenRequestMatchesPartial(partial))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package sttp.client3.impl.zio

import sttp.capabilities.Effect
import sttp.client3.testing.SttpBackendStub
import sttp.client3.{Request, Response, SttpBackend}
import sttp.model.StatusCode
import sttp.monad.MonadError
import zio.{RIO, Ref, Tag, UIO, URIO, ZIO, ZLayer}

trait SttpClientStubbingBase[R, P] {

type SttpClientStubbing = Service
// the tag as viewed by the implementing object. Needs to be passed explicitly, otherwise Has[] breaks.
private[sttp] def serviceTag: Tag[Service]
private[sttp] def sttpBackendTag: Tag[SttpBackend[RIO[R, *], P]]

trait Service {
def whenRequestMatchesPartial(partial: PartialFunction[Request[_, _], Response[_]]): URIO[SttpClientStubbing, Unit]

private[zio] def update(f: SttpBackendStub[RIO[R, *], P] => SttpBackendStub[RIO[R, *], P]): UIO[Unit]
}

private[sttp] class StubWrapper(stub: Ref[SttpBackendStub[RIO[R, *], P]]) extends Service {
override def whenRequestMatchesPartial(
partial: PartialFunction[Request[_, _], Response[_]]
): URIO[SttpClientStubbing, Unit] =
update(_.whenRequestMatchesPartial(partial))

override private[zio] def update(f: SttpBackendStub[RIO[R, *], P] => SttpBackendStub[RIO[R, *], P]) = stub.update(f)
}

case class StubbingWhenRequest private[sttp] (p: Request[_, _] => Boolean) {
implicit val _serviceTag: Tag[Service] = serviceTag
val thenRespondOk: URIO[SttpClientStubbing, Unit] =
whenRequest(_.thenRespondOk())

def thenRespondNotFound(): URIO[SttpClientStubbing, Unit] =
whenRequest(_.thenRespondNotFound())

def thenRespondServerError(): URIO[SttpClientStubbing, Unit] =
whenRequest(_.thenRespondServerError())

def thenRespondWithCode(status: StatusCode, msg: String = ""): URIO[SttpClientStubbing, Unit] =
whenRequest(_.thenRespondWithCode(status, msg))

def thenRespond[T](body: T): URIO[SttpClientStubbing, Unit] =
whenRequest(_.thenRespond(body))

def thenRespond[T](resp: => Response[T]): URIO[SttpClientStubbing, Unit] =
whenRequest(_.thenRespond(resp))

def thenRespondCyclic[T](bodies: T*): URIO[SttpClientStubbing, Unit] =
whenRequest(_.thenRespondCyclic(bodies: _*))

def thenRespondCyclicResponses[T](responses: Response[T]*): URIO[SttpClientStubbing, Unit] =
whenRequest(_.thenRespondCyclicResponses(responses: _*))

def thenRespondF(resp: => RIO[R, Response[_]]): URIO[SttpClientStubbing, Unit] =
whenRequest(_.thenRespondF(resp))

def thenRespondF(resp: Request[_, _] => RIO[R, Response[_]]): URIO[SttpClientStubbing, Unit] =
whenRequest(_.thenRespondF(resp))

private def whenRequest(
f: SttpBackendStub[RIO[R, *], P]#WhenRequest => SttpBackendStub[RIO[R, *], P]
): URIO[SttpClientStubbing, Unit] = ZIO.serviceWith(_.update(stub => f(stub.whenRequestMatches(p))))
}

val layer: ZLayer[Any, Nothing, Service with SttpBackend[RIO[R, *], P]] = {
val monad = new RIOMonadAsyncError[R]
implicit val _serviceTag: Tag[Service] = serviceTag
implicit val _backendTag: Tag[SttpBackend[RIO[R, *], P]] = sttpBackendTag

val services = for {
stub <- Ref.make(SttpBackendStub[RIO[R, *], P](monad))
stubber = new StubWrapper(stub)
proxy = new SttpBackend[RIO[R, *], P] {
override def send[T, RR >: P with Effect[RIO[R, *]]](request: Request[T, RR]): RIO[R, Response[T]] = {
stub.get flatMap (_.send(request))
}
override def close(): RIO[R, Unit] = stub.get flatMap (_.close())
override def responseMonad: MonadError[RIO[R, *]] = monad
}
} yield (stubber, proxy)

ZLayer.fromZIO(services.map(_._1)) ++ ZLayer.fromZIO(services.map(_._2))
}
}