Sttp Client 3 with ZLayer type mismatch

362 views Asked by At

I use ZLayer and Sttp Client(async to create simple http requester application but I found type mismatch error that I couldn't solve it. Can anyone tell me why I'm getting the type mismatch error?

I use these versions of scala & libraries.

java -> 8.282.08.1-amzn

scala -> s.13.5

dev.zio -> 1.0.7

com.softwaremill.sttp.client3 -> 3.3.0

type mismatch;
 found   : zio.ZLayer[sttp.client3.asynchttpclient.zio.SttpClient,Nothing,ZlayerAndSttp.HttpBin]
    (which expands to)  zio.ZLayer[zio.Has[sttp.client3.SttpBackend[zio.Task,sttp.capabilities.zio.ZioStreams with sttp.capabilities.WebSockets]],Nothing,zio.Has[ZlayerAndSttp.HttpBin.Service]]
 required: zio.ZLayer[ZlayerAndSttp.HttpBin,?,?]
    (which expands to)  zio.ZLayer[zio.Has[ZlayerAndSttp.HttpBin.Service],?,?]
    program.provideCustomLayer((AsyncHttpClientZioBackend.layer() >>> HttpBin.live) >>> HttpBin.live)

Here's the whole code

import zio._
import sttp.client3._
import sttp.client3.circe._
import sttp.client3.asynchttpclient.zio._
import io.circe.generic.auto._
import zio.console.Console


object ZlayerAndSttp extends App {

  case class HttpBinResponse(origin: String, headers: Map[String, String])

  type HttpBin = Has[HttpBin.Service]
  object HttpBin {
    trait Service {
      def sendRequest: ZIO[HttpBin with SttpClient, Throwable, HttpBinResponse]
    }

    val live: ZLayer[SttpClient, Nothing, HttpBin] = ZLayer.succeed(new Service {
      override def sendRequest: ZIO[HttpBin with SttpClient, Throwable, HttpBinResponse] = {
        val request = basicRequest
          .get(uri"https://httpbin.org/get")
          .response(asJson[HttpBinResponse])
        sendR(request).map(_.body).absolve.map(res => HttpBinResponse(res.origin, res.headers))
      }
    })

    def sendRequest: ZIO[HttpBin with SttpClient, Throwable, HttpBinResponse] = ZIO.accessM(_.get.sendRequest)
  }

  val request = basicRequest
    .get(uri"https://httpbin.org/get")
    .response(asJson[HttpBinResponse])

  override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = {
    val program = for {
      result <- HttpBin.sendRequest
      _ <- console.putStrLn(s"${result.origin}, ${result.headers}")
    } yield ()
    program.provideCustomLayer((AsyncHttpClientZioBackend.layer() >>> HttpBin.live) >>> HttpBin.live) // type mismatch
      .exitCode

    // ↓these lines of code run with no errors but I can't understand why
//    val program: ZIO[Console with SttpClient, Throwable, Unit] = for {
//      response <- send(request)
//      _ <- console.putStrLn(s"${response.body.toString}")
//    } yield ()
//    program.provideCustomLayer(AsyncHttpClientZioBackend.layer()).exitCode
  }

}
1

There are 1 answers

0
paulpdaniels On

You seem to be making your life a bit more complicated than needed.

What the end code looks like depends on what you want to achieve here. If you are trying to hide the use of Sttp behind the HttpBin interface, then your layer definition should instead look like:

    val live: ZLayer[SttpClient, Nothing, HttpBin] = 
      (for {
        client <- ZIO.environment[SttpClient]
      } yield new Service {
          override def sendRequest: ZIO[Any, Throwable, HttpBinResponse] = {
            val request = basicRequest
              .get(uri"https://httpbin.org/get")
              .response(asJson[HttpBinResponse])

            sendR(request)
              .map(_.body)
              .absolve
              .map(res => HttpBinResponse(res.origin, res.headers))
              .provide(client)
  }
      }).toLayer

Then your accessor method becomes:

def sendRequest: ZIO[HttpBin, Throwable, HttpBinResponse] = 
  ZIO.accessM(_.get.sendRequest)

And you can use it with:

  override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = {
    val program = for {
      result <- HttpBin.sendRequest
      _ <- console.putStrLn(s"${result.origin}, ${result.headers}")
    } yield ()

    program
      .provideCustomLayer(AsyncHttpClientZioBackend.layer() >>> HttpBin.live)
      .exitCode

The thing to note here is that with layer composition I am using just the vertical operator because HttpBin.live depends on a layer with SttpClient but we are "hiding" that fact from the calling method so you could create a test variant of HttpBin that didn't require Sttp if needed.

If you don't need the information hiding you can instead remove the intermediate layer altogether and just treat your sendRequest as a stand alone method.

object HttpBin {
  def sendRequest: ZIO[SttpClient, Throwable, HttpBinResponse] = {
     val request = basicRequest
       .get(uri"https://httpbin.org/get")
       .response(asJson[HttpBinResponse])

     sendR(request)
       .map(_.body)
       .absolve
       .map(res => HttpBinResponse(res.origin, res.headers))
}

Then you can just call this method and all you need to do is provide the SttpClient layer.