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

sttp.tapir.server.ServerEndpoint.scala Maven / Gradle / Ivy

There is a newer version: 1.11.9
Show newest version
package sttp.tapir.server

import sttp.monad.MonadError
import sttp.monad.syntax._
import sttp.tapir.typelevel.ErasureSameAsType
import sttp.tapir._

import scala.reflect.ClassTag

/** An [[Endpoint]] together with functions implementing the endpoint's security and main logic.
  *
  * @tparam R
  *   Requirements: The capabilities that are required by this endpoint's inputs/outputs. `Any`, if no requirements.
  * @tparam F
  *   The effect type constructor used in the provided server logic.
  */
abstract class ServerEndpoint[-R, F[_]] extends EndpointInfoOps[R] with EndpointMetaOps { outer =>

  /** Security input parameter types (abbreviated as `A`). */
  type SECURITY_INPUT

  /** The type of the value returned by the security logic, e.g. a user (abbreviated as `U`). */
  type PRINCIPAL

  /** Input parameter types (abbreviated as `I`). */
  type INPUT

  /** Error output parameter types (abbreviated as `E`). */
  type ERROR_OUTPUT

  /** Output parameter types (abbreviated as `O`). */
  type OUTPUT

  def endpoint: Endpoint[SECURITY_INPUT, INPUT, ERROR_OUTPUT, OUTPUT, R]
  def securityLogic: MonadError[F] => SECURITY_INPUT => F[Either[ERROR_OUTPUT, PRINCIPAL]]
  def logic: MonadError[F] => PRINCIPAL => INPUT => F[Either[ERROR_OUTPUT, OUTPUT]]

  override type ThisType[-_R] = ServerEndpoint.Full[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, _R, F]
  override def securityInput: EndpointInput[SECURITY_INPUT] = endpoint.securityInput
  override def input: EndpointInput[INPUT] = endpoint.input
  override def errorOutput: EndpointOutput[ERROR_OUTPUT] = endpoint.errorOutput
  override def output: EndpointOutput[OUTPUT] = endpoint.output
  override def info: EndpointInfo = endpoint.info
  override private[tapir] def withInfo(
      info: EndpointInfo
  ): ServerEndpoint.Full[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, R, F] =
    ServerEndpoint(endpoint.info(info), securityLogic, logic)

  override protected def showType: String = "ServerEndpoint"

  /** Prepends an additional input to this endpoint. This is useful when adding a context path to endpoints, e.g.
    * `serverEndpoint.prependSecurityIn("api" / "v1")`.
    *
    * The given input can't map to any values in the request, hence its type is `EndpointInput[Unit]`; it has to be "netural" so that the
    * server logic function can remain unchanged.
    *
    * The input is prepended to the security inputs, so that it is decoded before any other path-related inputs (either security or
    * regular).
    */
  def prependSecurityIn(additionalInput: EndpointInput[Unit]): ServerEndpoint[R, F] = new ServerEndpoint[R, F] {
    override type SECURITY_INPUT = outer.SECURITY_INPUT
    override type PRINCIPAL = outer.PRINCIPAL
    override type INPUT = outer.INPUT
    override type ERROR_OUTPUT = outer.ERROR_OUTPUT
    override type OUTPUT = outer.OUTPUT
    override def endpoint: Endpoint[SECURITY_INPUT, INPUT, ERROR_OUTPUT, OUTPUT, R] = outer.endpoint.prependSecurityIn(additionalInput)
    override def securityLogic: MonadError[F] => SECURITY_INPUT => F[Either[ERROR_OUTPUT, PRINCIPAL]] = outer.securityLogic
    override def logic: MonadError[F] => PRINCIPAL => INPUT => F[Either[ERROR_OUTPUT, OUTPUT]] = outer.logic
  }

  /** Prepends additional security logic to this endpoint. This is useful when adding security to file/resource-serving endpoints. The
    * additional security logic should return a `Right(())` to indicate success, or a `Left(E2)` to indicate failure; in this case, the
    * given error output will be used to create the response.
    *
    * The security inputs will become a tuple, containing first `additionalSecurityInput` combined with the current
    * `endpoint.securityInput`.
    *
    * The error output will consist of two variants: either the `securityErrorOutput` (the [[ClassTag]] requirement for `E2` is used to
    * create the [[oneOfVariant]]). In the absence of sum types, the resulting errors are typed as `Any`.
    *
    * The security logic is modified so that first `additionalSecurityLogic` is run, followed by the security logic defined so far.
    *
    * The type of the value returned by the combined security logic, or the regular logic remains unchanged.
    *
    * @tparam A2
    *   Type of the additional security input.
    * @tparam E2
    *   Type of the error output for the security logic.
    */
  def prependSecurity[A2, E2: ClassTag: ErasureSameAsType](
      additionalSecurityInput: EndpointInput[A2],
      securityErrorOutput: EndpointOutput[E2]
  )(
      additionalSecurityLogic: A2 => F[Either[E2, Unit]]
  ): ServerEndpoint[R, F] = prependSecurity_(additionalSecurityInput, securityErrorOutput)(_ => additionalSecurityLogic)

  /** See [[prependSecurity]]. */
  def prependSecurityPure[A2, E2: ClassTag: ErasureSameAsType](
      additionalSecurityInput: EndpointInput[A2],
      securityErrorOutput: EndpointOutput[E2]
  )(
      additionalSecurityLogic: A2 => Either[E2, Unit]
  ): ServerEndpoint[R, F] = prependSecurity_(additionalSecurityInput, securityErrorOutput)(implicit m => additionalSecurityLogic(_).unit)

  private def prependSecurity_[A2, E2: ClassTag: ErasureSameAsType](
      additionalSecurityInput: EndpointInput[A2],
      securityErrorOutput: EndpointOutput[E2]
  )(
      additionalSecurityLogic: MonadError[F] => A2 => F[Either[E2, Unit]]
  ): ServerEndpoint[R, F] =
    new ServerEndpoint[R, F] {
      override type SECURITY_INPUT = (A2, outer.SECURITY_INPUT)
      override type PRINCIPAL = outer.PRINCIPAL
      override type INPUT = outer.INPUT
      override type ERROR_OUTPUT = Any
      override type OUTPUT = outer.OUTPUT

      override def endpoint: Endpoint[SECURITY_INPUT, INPUT, ERROR_OUTPUT, OUTPUT, R] =
        outer.endpoint
          .prependSecurityIn(additionalSecurityInput)
          .errorOutVariantsFromCurrent[Any](current => List(oneOfVariant[E2](securityErrorOutput), oneOfDefaultVariant(current)))

      override def securityLogic: MonadError[F] => SECURITY_INPUT => F[Either[ERROR_OUTPUT, PRINCIPAL]] = implicit m =>
        a =>
          additionalSecurityLogic(m)(a._1).flatMap {
            case Left(e2)  => (Left(e2): Either[ERROR_OUTPUT, PRINCIPAL]).unit
            case Right(()) => outer.securityLogic(m)(a._2).asInstanceOf[F[Either[ERROR_OUTPUT, PRINCIPAL]]] // avoiding .map(identity)
          }

      // we're widening the `E` type, the logic still will always return `outer.E`, so this cast is safe
      // instead, we could have written: `implicit m => u => i => outer.logic(m)(u)(i).map(identity)`
      override def logic: MonadError[F] => PRINCIPAL => INPUT => F[Either[ERROR_OUTPUT, OUTPUT]] =
        outer.logic.asInstanceOf[MonadError[F] => PRINCIPAL => INPUT => F[Either[ERROR_OUTPUT, OUTPUT]]]
    }
}

object ServerEndpoint {
  private def emptySecurityLogic[E, F[_]]: MonadError[F] => Unit => F[Either[E, Unit]] = implicit m =>
    _ => (Right(()): Either[E, Unit]).unit

  /** The full type of a server endpoint, capturing the types of all input/output parameters. Most of the time, the simpler
    * `ServerEndpoint[R, F]` can be used instead.
    */
  type Full[_SECURITY_INPUT, _PRINCIPAL, _INPUT, _ERROR_OUTPUT, _OUTPUT, -R, F[_]] = ServerEndpoint[R, F] {
    type SECURITY_INPUT = _SECURITY_INPUT
    type PRINCIPAL = _PRINCIPAL
    type INPUT = _INPUT
    type ERROR_OUTPUT = _ERROR_OUTPUT
    type OUTPUT = _OUTPUT
  }

  /** Create a public server endpoint, with an empty (no-op) security logic, which always succeeds. */
  def public[INPUT, ERROR_OUTPUT, OUTPUT, R, F[_]](
      endpoint: Endpoint[Unit, INPUT, ERROR_OUTPUT, OUTPUT, R],
      logic: MonadError[F] => INPUT => F[Either[ERROR_OUTPUT, OUTPUT]]
  ): ServerEndpoint.Full[Unit, Unit, INPUT, ERROR_OUTPUT, OUTPUT, R, F] =
    ServerEndpoint(endpoint, emptySecurityLogic, m => _ => logic(m))

  /** Create a server endpoint, with the given security and main logic functions, which match the shape defined by `endpoint`. */
  def apply[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, R, F[_]](
      endpoint: Endpoint[SECURITY_INPUT, INPUT, ERROR_OUTPUT, OUTPUT, R],
      securityLogic: MonadError[F] => SECURITY_INPUT => F[Either[ERROR_OUTPUT, PRINCIPAL]],
      logic: MonadError[F] => PRINCIPAL => INPUT => F[Either[ERROR_OUTPUT, OUTPUT]]
  ): ServerEndpoint.Full[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, R, F] = {
    type _SECURITY_INPUT = SECURITY_INPUT
    type _PRINCIPAL = PRINCIPAL
    type _INPUT = INPUT
    type _ERROR_OUTPUT = ERROR_OUTPUT
    type _OUTPUT = OUTPUT
    val e = endpoint
    val s = securityLogic
    val l = logic
    new ServerEndpoint[R, F] {
      override type SECURITY_INPUT = _SECURITY_INPUT
      override type PRINCIPAL = _PRINCIPAL
      override type INPUT = _INPUT
      override type ERROR_OUTPUT = _ERROR_OUTPUT
      override type OUTPUT = _OUTPUT
      override def endpoint: Endpoint[SECURITY_INPUT, INPUT, ERROR_OUTPUT, OUTPUT, R] = e
      override def securityLogic: MonadError[F] => SECURITY_INPUT => F[Either[ERROR_OUTPUT, PRINCIPAL]] = s
      override def logic: MonadError[F] => PRINCIPAL => INPUT => F[Either[ERROR_OUTPUT, OUTPUT]] = l
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy