pekko/akka-http/src/main/scala/akka/http/scaladsl/server/Directive.scala

161 lines
6.5 KiB
Scala
Raw Normal View History

/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.scaladsl.server
import scala.collection.immutable
import akka.http.scaladsl.server.directives.RouteDirectives
import akka.http.scaladsl.server.util._
import akka.http.scaladsl.util.FastFuture
import akka.http.scaladsl.util.FastFuture._
/**
* A directive that provides a tuple of values of type `L` to create an inner route.
*/
abstract class Directive[L](implicit val ev: Tuple[L]) {
/**
* Calls the inner route with a tuple of extracted values of type `L`.
*
* `tapply` is short for "tuple-apply". Usually, you will use the regular `apply` method instead,
* which is added by an implicit conversion (see `Directive.addDirectiveApply`).
*/
def tapply(f: L Route): Route
/**
* Joins two directives into one which runs the second directive if the first one rejects.
*/
def |[R >: L](that: Directive[R]): Directive[R] =
recover(rejections directives.BasicDirectives.mapRejections(rejections ++ _) & that)(that.ev)
/**
* Joins two directives into one which extracts the concatenation of its base directive extractions.
* NOTE: Extraction joining is an O(N) operation with N being the number of extractions on the right-side.
*/
def &(magnet: ConjunctionMagnet[L]): magnet.Out = magnet(this)
/**
* Converts this directive into one which, instead of a tuple of type ``L``, creates an
* instance of type ``A`` (which is usually a case class).
*/
def as[A](constructor: ConstructFromTuple[L, A]): Directive1[A] = tmap(constructor)
/**
* Maps over this directive using the given function, which can produce either a tuple or any other value
* (which will then we wrapped into a [[Tuple1]]).
*/
def tmap[R](f: L R)(implicit tupler: Tupler[R]): Directive[tupler.Out] =
Directive[tupler.Out] { inner tapply { values inner(tupler(f(values))) } }(tupler.OutIsTuple)
/**
* Flatmaps this directive using the given function.
*/
def tflatMap[R: Tuple](f: L Directive[R]): Directive[R] =
Directive[R] { inner tapply { values f(values) tapply inner } }
/**
* Creates a new [[Directive0]], which passes if the given predicate matches the current
* extractions or rejects with the given rejections.
*/
def trequire(predicate: L Boolean, rejections: Rejection*): Directive0 =
tfilter(predicate, rejections: _*).tflatMap(_ Directive.Empty)
/**
* Creates a new directive of the same type, which passes if the given predicate matches the current
* extractions or rejects with the given rejections.
*/
def tfilter(predicate: L Boolean, rejections: Rejection*): Directive[L] =
Directive[L] { inner tapply { values ctx if (predicate(values)) inner(values)(ctx) else ctx.reject(rejections: _*) } }
/**
* Creates a new directive that is able to recover from rejections that were produced by `this` Directive
* **before the inner route was applied**.
*/
def recover[R >: L: Tuple](recovery: immutable.Seq[Rejection] Directive[R]): Directive[R] =
Directive[R] { inner
ctx
import ctx.executionContext
@volatile var rejectedFromInnerRoute = false
tapply({ list c rejectedFromInnerRoute = true; inner(list)(c) })(ctx).fast.flatMap {
case RouteResult.Rejected(rejections) if !rejectedFromInnerRoute recovery(rejections).tapply(inner)(ctx)
case x FastFuture.successful(x)
}
}
/**
* Variant of `recover` that only recovers from rejections handled by the given PartialFunction.
*/
def recoverPF[R >: L: Tuple](recovery: PartialFunction[immutable.Seq[Rejection], Directive[R]]): Directive[R] =
recover { rejections recovery.applyOrElse(rejections, (rejs: Seq[Rejection]) RouteDirectives.reject(rejs: _*)) }
}
object Directive {
/**
* Constructs a directive from a function literal.
*/
def apply[T: Tuple](f: (T Route) Route): Directive[T] =
new Directive[T] { def tapply(inner: T Route) = f(inner) }
/**
* A Directive that always passes the request on to its inner route (i.e. does nothing).
*/
val Empty: Directive0 = Directive(_())
/**
* Adds `apply` to all Directives with 1 or more extractions,
* which allows specifying an n-ary function to receive the extractions instead of a Function1[TupleX, Route].
*/
implicit def addDirectiveApply[L](directive: Directive[L])(implicit hac: ApplyConverter[L]): hac.In Route =
f directive.tapply(hac(f))
/**
* Adds `apply` to Directive0. Note: The `apply` parameter is call-by-name to ensure consistent execution behavior
* with the directives producing extractions.
*/
implicit def addByNameNullaryApply(directive: Directive0): ( Route) Route =
r directive.tapply(_ r)
implicit class SingleValueModifiers[T](underlying: Directive1[T]) extends AnyRef {
def map[R](f: T R)(implicit tupler: Tupler[R]): Directive[tupler.Out] =
underlying.tmap { case Tuple1(value) f(value) }
def flatMap[R: Tuple](f: T Directive[R]): Directive[R] =
underlying.tflatMap { case Tuple1(value) f(value) }
def require(predicate: T Boolean, rejections: Rejection*): Directive0 =
underlying.filter(predicate, rejections: _*).tflatMap(_ Empty)
def filter(predicate: T Boolean, rejections: Rejection*): Directive1[T] =
underlying.tfilter({ case Tuple1(value) predicate(value) }, rejections: _*)
}
}
trait ConjunctionMagnet[L] {
type Out
def apply(underlying: Directive[L]): Out
}
object ConjunctionMagnet {
implicit def fromDirective[L, R](other: Directive[R])(implicit join: TupleOps.Join[L, R]): ConjunctionMagnet[L] { type Out = Directive[join.Out] } =
new ConjunctionMagnet[L] {
type Out = Directive[join.Out]
def apply(underlying: Directive[L]) =
Directive[join.Out] { inner
underlying.tapply { prefix other.tapply { suffix inner(join(prefix, suffix)) } }
}(Tuple.yes) // we know that join will only ever produce tuples
}
implicit def fromStandardRoute[L](route: StandardRoute) =
new ConjunctionMagnet[L] {
type Out = StandardRoute
def apply(underlying: Directive[L]) = StandardRoute(underlying.tapply(_ route))
}
implicit def fromRouteGenerator[T, R <: Route](generator: T R) =
new ConjunctionMagnet[Unit] {
type Out = RouteGenerator[T]
def apply(underlying: Directive0) = value underlying.tapply(_ generator(value))
}
}