Skip to content

Instantly share code, notes, and snippets.

@kciesielski
Created December 13, 2023 09:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kciesielski/ca7e7ee213d44e7a8e7c0d6a6a4c79af to your computer and use it in GitHub Desktop.
Save kciesielski/ca7e7ee213d44e7a8e7c0d6a6a4c79af to your computer and use it in GitHub Desktop.
TapirAkkaDecodingLogging
package sttp.tapir.examples
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Route
import sttp.tapir._
import sttp.tapir.json.circe._
import io.circe.generic.auto._
import sttp.tapir.generic.auto._
import sttp.tapir.server.akkahttp.AkkaHttpServerOptions
import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter
import sttp.tapir.server.interceptor.DecodeFailureContext
import sttp.monad.{FutureMonad, MonadError}
import org.slf4j.{Logger, LoggerFactory}
import java.util.UUID
import sttp.tapir.server.model.ValuedEndpointOutput
import sttp.tapir.server.interceptor.exception.{ExceptionContext, ExceptionHandler, ExceptionInterceptor}
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
import sttp.client3._
import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler
import sttp.tapir.server.interceptor.decodefailure.DecodeFailureHandler
import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler.FailureMessages
case class HelloRequestSubField(name: String)
case class ErrorMessage(value: String)
case class HelloRequest(field1: String, field2: String, field3: HelloRequestSubField)
object HelloWorldAkkaServer extends App {
implicit val actorSystem: ActorSystem = ActorSystem()
import actorSystem.dispatcher
val logger = LoggerFactory.getLogger(getClass)
val helloWorld: PublicEndpoint[HelloRequest, Unit, String, Any] =
endpoint.post.in("hello").in(jsonBody[HelloRequest]).out(stringBody)
implicit val customServerOptions: AkkaHttpServerOptions =
AkkaHttpServerOptions.customiseInterceptors
.decodeFailureHandler(new DecodeFailureHandler[Future] {
override def apply(ctx:DecodeFailureContext)(implicit monad: MonadError[Future]) = {
logger.error(s"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%")
ctx.failingInput match {
// when defining how a decode failure should be handled, we need
// to describe the output to be used, and
// a value for this output
case _: EndpointIO.Body[_, _] =>
// see this function and then to failureSourceMessage function
// to find out which types of decode errors are present
val failureMessage = FailureMessages.failureMessage(ctx)
logger.error(s"$failureMessage")
// warning - log working incorrect when there are several endpoints
// with different methods
DefaultDecodeFailureHandler.apply[Future](ctx)
case _ =>
logger.error("##########################")
DefaultDecodeFailureHandler.apply[Future](ctx)
}
}})
.exceptionHandler(ExceptionHandler[Future] { ctx =>
logger.error("##########################################")
// defining exception id for the exception to make search in logs easier.
val exceptionId = UUID.randomUUID()
logger.error(
s"Intercepted exception ${ctx.e} while processing request, " +
s"exception id: $exceptionId"
)
Future.successful(
Some(
ValuedEndpointOutput[ErrorMessage](jsonBody[ErrorMessage], ErrorMessage(s"Internal Server Error, exception id: $exceptionId"))
)
)
})
.options
val helloWorldRoute: Route =
AkkaHttpServerInterpreter(customServerOptions).toRoute(helloWorld.serverLogicSuccess(req => Future.successful(s"Hello, $req!")))
// starting the server
val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(helloWorldRoute).map { _ =>
// testing
val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend()
val result: String = basicRequest
.response(asStringAlways)
.post(uri"http://localhost:8080/hello")
.body(
"""{"field1": "field1 value", "field_wrong_name": "34", "field3": { "name": "field3 name"}}"""
)
.send(backend)
.body
println("Got result: " + result)
}
Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment