ResponseInterceptor?

Hello,

Apologies in advance as I’m not too sure how to even phrase my question…

I’m using a RequestInterceptor to read a correlation id header and set it to a IOLocal so that it can be added to the MDC when logging, or forwarded to a STTP backend etc. That works great!

Now I haven’t found a way to attach that correlation id into a header for all endpoint responses. Is that possible?

Hope I make sense! Thank you for the help!

You should be able to transform the RequestResult, for example:

package sttp.tapir.examples2

import cats.effect.std.Dispatcher
import cats.effect.{IO, IOApp}
import sttp.model.Header
import sttp.monad.MonadError
import sttp.tapir.server.netty.cats.{NettyCatsServer, NettyCatsServerOptions}
import sttp.tapir._
import sttp.tapir.model.ServerRequest
import sttp.tapir.server.ServerEndpoint
import sttp.tapir.server.interceptor.{EndpointInterceptor, RequestHandler, RequestInterceptor, RequestResult, Responder}

object HelloWorldNettyCatsServer extends IOApp.Simple {
  val helloWorldEndpoint: PublicEndpoint[String, Unit, String, Any] =
    endpoint.get.in("hello").in(query[String]("name")).out(stringBody)

  // Just returning passed name with `Hello, ` prepended
  val helloWorldServerEndpoint = helloWorldEndpoint
    .serverLogic(name => IO.pure[Either[Unit, String]](Right(s"Hello, $name!")))

  // Creating handler for netty bootstrap
  override def run = Dispatcher
    .parallel[IO]
    .map(d =>
      NettyCatsServer
        .apply(
          NettyCatsServerOptions
            .default[IO](d)
            .appendInterceptor(
              new RequestInterceptor[IO] {
                override def apply[R, B](responder: Responder[IO, B], requestHandler: EndpointInterceptor[IO] => RequestHandler[IO, R, B])
                    : RequestHandler[IO, R, B] =
                  new RequestHandler[IO, R, B] {
                    override def apply(request: ServerRequest, endpoints: List[ServerEndpoint[R, IO]])(implicit
                        monad: MonadError[IO]
                    ): IO[RequestResult[B]] = {
                      val cid = request.header("CID").getOrElse("???")
                      println(cid)
                      requestHandler(EndpointInterceptor.noop).apply(request, endpoints).map {
                        case RequestResult.Response(response) =>
                          RequestResult.Response(response.addHeaders(List(Header("CID", cid))))
                        case f: RequestResult.Failure => f
                      }
                    }
                  }
              }
            )
        )
    )
    .use { server =>
      for {
        binding <- server.addEndpoint(helloWorldServerEndpoint).start()
        result <- IO.never.guarantee(binding.stop())
      } yield result
    }
}
curl -H "CID: abc" -v "http://localhost:8080/hello?name=adam"

> GET /hello?name=adam HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.6.0
> Accept: */*
> CID: abc
> 
< HTTP/1.1 200 OK
< server: tapir/1.10.9
< CID: abc
< Content-Type: text/plain; charset=UTF-8
< content-length: 12
< 
* Connection #0 to host localhost left intact
Hello, adam!%     
1 Like

Awesome, that’s perfect, thank you!