All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sttp.tapir.server.vertx.cats.VertxCatsServerInterpreter.scala Maven / Gradle / Ivy

The newest version!
package sttp.tapir.server.vertx.cats

import cats.effect.std.Dispatcher
import cats.effect.{Async, Sync}
import cats.syntax.all._
import io.vertx.core.{Future, Handler}
import io.vertx.ext.web.{Route, Router, RoutingContext}
import sttp.capabilities.{Streams, WebSockets}
import sttp.capabilities.fs2.Fs2Streams
import sttp.monad.MonadError
import sttp.tapir.server.ServerEndpoint
import sttp.tapir.server.interceptor.RequestResult
import sttp.tapir.server.interpreter.{BodyListener, ServerInterpreter}
import sttp.tapir.server.vertx.{VertxBodyListener, VertxErrorHandler}
import sttp.tapir.server.vertx.cats.VertxCatsServerInterpreter.{CatsFFromVFuture, CatsRunAsync, VertxFutureToCatsF, monadError}
import sttp.tapir.server.vertx.decoders.{VertxRequestBody, VertxServerRequest}
import sttp.tapir.server.vertx.encoders.{VertxOutputEncoders, VertxToResponseBody}
import sttp.tapir.server.vertx.interpreters.{CommonServerInterpreter, FromVFuture, RunAsync}
import sttp.tapir.server.vertx.routing.PathMapping.extractRouteDefinition
import sttp.tapir.server.vertx.streams.ReadStreamCompatible
import sttp.tapir.server.vertx.cats.streams.fs2.fs2ReadStreamCompatible

import java.util.concurrent.atomic.AtomicReference

trait VertxCatsServerInterpreter[F[_]] extends CommonServerInterpreter with VertxErrorHandler {

  implicit def fa: Async[F]

  def vertxCatsServerOptions: VertxCatsServerOptions[F]

  /** Given a Router, creates and mounts a Route matching this endpoint, with default error handling
    * @return
    *   A function, that given a router, will attach this endpoint to it
    */
  def route(
      e: ServerEndpoint[Fs2Streams[F] with WebSockets, F]
  ): Router => Route = { router =>
    val readStreamCompatible = fs2ReadStreamCompatible(vertxCatsServerOptions)
    mountWithDefaultHandlers(e)(router, extractRouteDefinition(e.endpoint), vertxCatsServerOptions)
      .handler(endpointHandler(e, readStreamCompatible))
  }

  private def endpointHandler[S <: Streams[S]](
      e: ServerEndpoint[Fs2Streams[F] with WebSockets, F],
      readStreamCompatible: ReadStreamCompatible[S]
  ): Handler[RoutingContext] = {
    implicit val monad: MonadError[F] = monadError[F]
    implicit val bodyListener: BodyListener[F, RoutingContext => Future[Void]] =
      new VertxBodyListener[F](new CatsRunAsync(vertxCatsServerOptions.dispatcher))
    val fFromVFuture = new CatsFFromVFuture[F]
    val interpreter: ServerInterpreter[Fs2Streams[F] with WebSockets, F, RoutingContext => Future[Void], S] = new ServerInterpreter(
      _ => List(e),
      new VertxRequestBody(vertxCatsServerOptions, fFromVFuture)(readStreamCompatible),
      new VertxToResponseBody(vertxCatsServerOptions)(readStreamCompatible),
      vertxCatsServerOptions.interceptors,
      vertxCatsServerOptions.deleteFile
    )

    new Handler[RoutingContext] {
      override def handle(rc: RoutingContext) = {
        val serverRequest = VertxServerRequest(rc)

        val result = interpreter(serverRequest)
          .flatMap {
            // in vertx, endpoints are attempted to be decoded individually; if this endpoint didn't match - another one might
            case RequestResult.Failure(_)         => Async[F].delay(rc.next())
            case RequestResult.Response(response) => fFromVFuture(VertxOutputEncoders(response).apply(rc)).void
          }
          .handleErrorWith(handleError(rc, _).asF.void)

        // we obtain the cancel token only after the effect is run, so we need to pass it to the exception handler
        // via a mutable ref; however, before this is done, it's possible an exception has already been reported;
        // if so, we need to use this fact to cancel the operation nonetheless
        type CancelToken = () => scala.concurrent.Future[Unit]
        val cancelRef = new AtomicReference[Option[Either[Throwable, CancelToken]]](None)

        rc.response.exceptionHandler { (t: Throwable) =>
          cancelRef.getAndSet(Some(Left(t))).collect { case Right(t) =>
            t()
          }
          ()
        }

        val cancelToken = vertxCatsServerOptions.dispatcher.unsafeRunCancelable(result)
        cancelRef.getAndSet(Some(Right(cancelToken))).collect { case Left(_) =>
          cancelToken()
        }

        ()
      }
    }
  }
}

object VertxCatsServerInterpreter {
  def apply[F[_]](dispatcher: Dispatcher[F])(implicit _fa: Async[F]): VertxCatsServerInterpreter[F] = {
    new VertxCatsServerInterpreter[F] {
      override implicit def fa: Async[F] = _fa
      override def vertxCatsServerOptions: VertxCatsServerOptions[F] = VertxCatsServerOptions.default[F](dispatcher)(fa)
    }
  }

  def apply[F[_]](serverOptions: VertxCatsServerOptions[F])(implicit _fa: Async[F]): VertxCatsServerInterpreter[F] = {
    new VertxCatsServerInterpreter[F] {
      override implicit def fa: Async[F] = _fa
      override def vertxCatsServerOptions: VertxCatsServerOptions[F] = serverOptions
    }
  }

  private[cats] def monadError[F[_]](implicit F: Sync[F]): MonadError[F] = new MonadError[F] {
    override def unit[T](t: T): F[T] = F.pure(t)
    override def map[T, T2](fa: F[T])(f: T => T2): F[T2] = F.map(fa)(f)
    override def flatMap[T, T2](fa: F[T])(f: T => F[T2]): F[T2] = F.flatMap(fa)(f)
    override def error[T](t: Throwable): F[T] = F.raiseError(t)
    override protected def handleWrappedError[T](rt: F[T])(h: PartialFunction[Throwable, F[T]]): F[T] = F.recoverWith(rt)(h)
    override def eval[T](t: => T): F[T] = F.delay(t)
    override def suspend[T](t: => F[T]): F[T] = F.defer(t)
    override def flatten[T](ffa: F[F[T]]): F[T] = F.flatten(ffa)
    override def ensure[T](f: F[T], e: => F[Unit]): F[T] = F.guaranteeCase(f)(_ => e)
    override def blocking[T](t: => T): F[T] = F.blocking(t)
  }

  private[cats] class CatsFFromVFuture[F[_]: Async] extends FromVFuture[F] {
    def apply[T](f: => Future[T]): F[T] = f.asF
  }

  private[cats] class CatsRunAsync[F[_]: Async](dispatcher: Dispatcher[F]) extends RunAsync[F] {
    override def apply[T](f: => F[T]): Unit = dispatcher.unsafeRunAndForget(f)
  }

  implicit class VertxFutureToCatsF[A](f: => Future[A]) {
    def asF[F[_]: Async]: F[A] = {
      Async[F].async { cb =>
        Sync[F].delay {
          f.onComplete({ handler =>
            if (handler.succeeded()) {
              cb(Right(handler.result()))
            } else {
              cb(Left(handler.cause()))
            }
          })
          Some(().pure[F])
        }
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy