Using as an sttp client

Add the dependency:

"com.softwaremill.sttp.tapir" %% "tapir-sttp-client" % "1.4.0"

To make requests using an endpoint definition using the sttp client, import:

import sttp.tapir.client.sttp.SttpClientInterpreter

This objects contains a number of variants for creating a client call, where the first parameter is the endpoint description. The second is an optional URI - if this is None, the request will be relative.

Here’s a summary of the available method variants; R are the requirements of the endpoint, such as streaming or websockets:

  • toRequest(PublicEndpoint, Option[Uri]): returns a function, which represents decoding errors as the DecodeResult class.

    I => Request[DecodeResult[Either[E, O]], R]
    

    After providing the input parameters, a description of the request to be made is returned, with the input value encoded as appropriate request parameters: path, query, headers and body. This can be further customised and sent using any sttp backend. The response will then contain the decoded error or success values (note that this can be the body enriched with data from headers/status code).

  • toRequestThrowDecodeFailures(PublicEndpoint, Option[Uri]): returns a function, which will throw an exception or return a failed effect if decoding of the result fails

    I => Request[Either[E, O], R]
    
  • toRequestThrowErrors(PublicEndpoint, Option[Uri]): returns a function, which will throw an exception or return a failed effect if decoding of the result fails, or if the result is an error (as described by the endpoint)

    I => Request[O, R]
    

Next, there are toClient(PublicEndpoint, Option[Uri], SttpBackend[F, R]) methods (in the above variants), which send the request using the given backend. Hence in this case, the signature of the result is:

I => F[DecodeResult[Either[E, O]]]

Finally, for secure endpoints, there are toSecureClient and toSecureRequest families of methods. They return functions which first accept the security inputs, and then the regular inputs. For example:

// toSecureRequest(Endpoint, Option[Uri]) returns: 
A => I => Request[DecodeResult[Either[E, O]], R]

// toSecureClient(Endpoint, Option[Uri], SttpBackend) returns:
A => I => F[DecodeResult[Either[E, O]]]

See the runnable example for example usage.

Web sockets

To interpret a web socket endpoint, an additional streams-specific import is needed, so that the interpreter can convert sttp’s WebSocket instance into a pipe. This logic is looked up via the WebSocketToPipe implicit.

The required imports are as follows:

import sttp.tapir.client.sttp.ws.akkahttp._ // for akka-streams
import sttp.tapir.client.sttp.ws.fs2._      // for fs2
import sttp.tapir.client.sttp.ws.zio._      // for zio 2.x
import sttp.tapir.client.sttp.ws.zio1._     // for zio 1.x

No additional dependencies are needed (except for zio1, which needs the tapir-sttp-client-ws-zio1 dependency), as both of the above implementations are included in the main interpreter, with dependencies on akka-streams, fs2 and zio being marked as optional (hence these are not transitive).

Overwriting the response specification

The Request obtained from the .toRequest and .toSecureRequest families of methods, after being applied to the input, contains both the request data (URI, headers, body), and a description of how to handle the response - depending on the variant used, decoding the response into one of endpoint’s outputs.

If you’d like to skip that step, e.g. when testing redirects, it’s possible to overwrite the response handling description, for example:

import sttp.tapir._
import sttp.tapir.client.sttp.SttpClientInterpreter
import sttp.client3._

SttpClientInterpreter()
  .toRequest(endpoint.get.in("hello").in(query[String]("name")), Some(uri"http://localhost:8080"))
  .apply("Ann")
  .response(asStringAlways)

Scala.JS

In this case add the following dependencies (note the %%% instead of the usual %%):

"com.softwaremill.sttp.tapir" %%% "tapir-sttp-client" % "1.4.0"
"io.github.cquiroz" %%% "scala-java-time" % "2.2.0" // implementations of java.time classes for Scala.JS

The client interpreter also supports Scala.JS, the request must then be sent using the sttp client Scala.JS Fetch backend.

You can check the SttpClientTests for a working example.

Limitations

There are limitations existing on some clients that prevent the description generated by tapir from being decoded correctly. For security reasons the Set-Cookie header is not accessible from frontend JavaScript code.

It means that any endpoint containing a .out(setCookie("token")) will fail to be decoded on the client side when using Fetch. A solution is to use the setCookieOpt function instead an let the browser do its job when dealing with cookies.