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

Make ZioHttpResponseBody public #3107

Merged
merged 1 commit into from
Aug 16, 2023

Conversation

duxet
Copy link
Contributor

@duxet duxet commented Aug 14, 2023

Small change to make it possible to eg. log server response body using Tapir interceptor. Right now it cannot be done without using some hacks as ZioRawHttpResponseBody trait (and case classes which extend this trait) are marked as private.

@kciesielski
Copy link
Member

@duxet Could you show an example how you would like to use interceptors with these types? Maybe you could leverage zio-http's RequestHandlerMiddlewares.requestLogging(logResponseBody = true) instead?

@duxet
Copy link
Contributor Author

duxet commented Aug 16, 2023

Sure. There's some interceptor code that I already use (though without response body logging due to this ZioHttpResponseBody access limitation):

class LoggingInterceptor[R, E]() extends RequestInterceptor[({ type T[A] = ZIO[R, E, A] })#T] {
  type RequestZIO[A] = ZIO[R, E, A]

  override def apply[R2, B](
    responder: Responder[RequestZIO, B],
    requestHandler: EndpointInterceptor[RequestZIO] => RequestHandler[RequestZIO, R2, B]
  ): RequestHandler[RequestZIO, R2, B] =
    RequestHandler.from { (request, endpoints, monad) =>
      implicit val m: MonadError[RequestZIO] = monad

      logRequest(request) *>
      requestHandler(EndpointInterceptor.noop).apply(request, endpoints).tap {
        case RequestResult.Response(response) =>
          logResponse(request, response)
      }
    }

  private def logRequest(request: ServerRequest): UIO[Unit] =
    for {
      body <- request.underlying match {
               case r: zio.http.Request => r.body.asString.map(_.some).orElseSucceed(None)
               case _                   => ZIO.none
             }
      _ <- ZIO.log(
            s"-> ${request.protocol} ${request.method} ${request.uri}"
          ) @@ LogAnnotations.HttpRequest(request, body)
    } yield ()

  private def logResponse(request: ServerRequest, response: ServerResponse[_]): UIO[Unit] =
    for {
      body <- response.body match {
               case Some(b: sttp.tapir.server.ziohttp.ZioRawHttpResponseBody) => ???
               case _                                                         => ZIO.none
             }
      _ <- ZIO.log(
            s"<- ${request.protocol} ${response.code} ${request.method} ${request.uri}"
          ) @@ LogAnnotations.HttpResponse(response, body)
    } yield ()
}

I wanted to keep request/response logging on the Tapir level as I already use another Tapir interceptor to create server spans with zio-opentelemetry and I wouldn't be able to access its tracing context on zio-http level.

@kciesielski
Copy link
Member

Makes sense, thanks :)

@kciesielski kciesielski merged commit 65519c0 into softwaremill:master Aug 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants