Created
September 5, 2023 08:43
-
-
Save kciesielski/96151668c66be7392d39072ab9f251f4 to your computer and use it in GitHub Desktop.
Tapir + ZIO + interceptor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.softwaremill | |
import sttp.model.StatusCode | |
import sttp.monad.MonadError | |
import sttp.monad.syntax._ | |
import sttp.tapir.AttributeKey | |
import sttp.tapir.server.interceptor.* | |
import sttp.tapir.server.interpreter.BodyListener | |
import sttp.tapir.server.model.ServerResponse | |
import sttp.tapir.server.ziohttp.{ZioHttpInterpreter, ZioHttpServerOptions} | |
import zio.http.* | |
import zio.logging.LogFormat | |
import zio.logging.backend.SLF4J | |
import zio.* | |
case class Permission(value: String) | |
case class PermissionError(msg: String) | |
type Eff[A] = RIO[SessionContext, A] | |
def enforcePermission(permission: Permission): ZIO[SessionContext, PermissionError, Unit] = | |
ZIO.succeed { println(s"Enforcing permission: $permission") }.flatMap(_ => ZIO.fail(PermissionError("Failed to resolve permissions!"))) | |
class PermissionsEndpointInterceptor() extends EndpointInterceptor[Eff] { | |
override def apply[B](responder: Responder[Eff, B], decodeHandler: EndpointHandler[Eff, B]): EndpointHandler[Eff, B] = | |
new EndpointHandler[Eff, B] { | |
override def onDecodeSuccess[A, U, I](ctx: DecodeSuccessContext[Eff, A, U, I])(implicit | |
monad: MonadError[Eff], | |
bodyListener: BodyListener[Eff, B] | |
): Eff[ServerResponse[B]] = { | |
(ctx.endpoint | |
.attribute(AttributeKey[Permission]) match { | |
case None => | |
ZIO.unit | |
case Some(endpointPermission) => | |
enforcePermission(endpointPermission) | |
}) | |
.flatMap(_ => | |
decodeHandler | |
.onDecodeSuccess(ctx) | |
) | |
.catchSome { | |
case p: PermissionError => | |
ZIO.succeed(ServerResponse[B](StatusCode.Unauthorized, Nil, None, None)) | |
} | |
.orDieWith(permError => new RuntimeException(s"Unexpected permission error? $permError")) | |
} | |
override def onSecurityFailure[A]( | |
ctx: SecurityFailureContext[Eff, A] | |
)(implicit monad: MonadError[Eff], bodyListener: BodyListener[Eff, B]): Eff[ServerResponse[B]] = | |
decodeHandler.onSecurityFailure(ctx) | |
override def onDecodeFailure( | |
ctx: DecodeFailureContext | |
)(implicit monad: MonadError[Eff], bodyListener: BodyListener[Eff, B]): Eff[Option[ServerResponse[B]]] = | |
decodeHandler.onDecodeFailure(ctx) | |
} | |
} | |
class SessionContext() | |
object Main extends ZIOAppDefault: | |
override val bootstrap: ZLayer[ZIOAppArgs, Any, Any] = SLF4J.slf4j(LogLevel.Debug, LogFormat.default) | |
override def run: ZIO[Any with ZIOAppArgs with Scope, Any, Any] = | |
val serverOptions: ZioHttpServerOptions[SessionContext] = | |
ZioHttpServerOptions.default[SessionContext].prependInterceptor(new PermissionsEndpointInterceptor()) | |
val app: HttpApp[SessionContext, Throwable] = ZioHttpInterpreter(serverOptions).toHttp(Endpoints.all) | |
val port = sys.env.get("HTTP_PORT").flatMap(_.toIntOption).getOrElse(8080) | |
val logConfig = RequestHandlerMiddlewares.requestLogging(logRequestBody = true, logResponseBody = true) | |
val appWithLogging = app.withDefaultErrorResponse @@ logConfig | |
( | |
for | |
actualPort <- Server.serve(appWithLogging) | |
_ <- Console.printLine(s"Server started at http://localhost:${actualPort}. Press ENTER key to exit.") | |
_ <- Console.readLine | |
yield () | |
).provide( | |
Server.defaultWithPort(port), | |
ZLayer.succeed(new SessionContext()) | |
).exitCode |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment