!htp #16101 improve basic directive infrastructure, add more scaladoc, fix smaller problems
This commit is contained in:
parent
33ddabbc9f
commit
0033e74cae
12 changed files with 204 additions and 182 deletions
|
|
@ -77,7 +77,7 @@ class BasicRouteSpecs extends RoutingSpec {
|
|||
}
|
||||
}
|
||||
"Route disjunction" should {
|
||||
"work" in {
|
||||
"work in the happy case" in {
|
||||
val route = sealRoute((path("abc") | path("def")) {
|
||||
completeOk
|
||||
})
|
||||
|
|
@ -92,6 +92,15 @@ class BasicRouteSpecs extends RoutingSpec {
|
|||
status shouldEqual StatusCodes.NotFound
|
||||
}
|
||||
}
|
||||
"don't apply alternative if inner route rejects" in {
|
||||
object MyRejection extends Rejection
|
||||
val route = (path("abc") | post) {
|
||||
reject(MyRejection)
|
||||
}
|
||||
Get("/abc") ~> route ~> check {
|
||||
rejection shouldEqual MyRejection
|
||||
}
|
||||
}
|
||||
}
|
||||
"Case class extraction with Directive.as" should {
|
||||
"extract one argument" in {
|
||||
|
|
|
|||
|
|
@ -4,49 +4,16 @@
|
|||
|
||||
package akka.http.server
|
||||
|
||||
import scala.concurrent.Future
|
||||
import scala.collection.immutable
|
||||
import akka.http.server.directives.RouteDirectives
|
||||
import akka.http.server.util._
|
||||
|
||||
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]) =
|
||||
new ConjunctionMagnet[L] {
|
||||
type Out = Directive[join.Out]
|
||||
def apply(underlying: Directive[L]): Out =
|
||||
new Directive[join.Out]()(Tuple.yes /* we know that join will only ever produce tuples*/ ) {
|
||||
def tapply(f: join.Out ⇒ Route) =
|
||||
underlying.tapply { prefix ⇒
|
||||
other.tapply { suffix ⇒
|
||||
f(join(prefix, suffix))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
implicit def fromStandardRoute[L](route: StandardRoute) =
|
||||
new ConjunctionMagnet[L] {
|
||||
type Out = StandardRoute
|
||||
def apply(underlying: Directive[L]): Out = StandardRoute(underlying.tapply(_ ⇒ route))
|
||||
}
|
||||
|
||||
implicit def fromRouteGenerator[T, R <: Route](generator: T ⇒ R) = new ConjunctionMagnet[Unit] {
|
||||
type Out = RouteGenerator[T]
|
||||
def apply(underlying: Directive0): Out = { value ⇒
|
||||
underlying.tapply(_ ⇒ generator(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
import akka.http.util.FastFuture
|
||||
import 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]) { self ⇒
|
||||
sealed abstract class Directive[L] private (implicit val ev: Tuple[L]) {
|
||||
|
||||
/**
|
||||
* Calls the inner route with a tuple of extracted values of type `L`.
|
||||
|
|
@ -56,6 +23,9 @@ abstract class Directive[L](implicit val ev: Tuple[L]) { self ⇒
|
|||
*/
|
||||
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)
|
||||
|
||||
|
|
@ -65,38 +35,51 @@ abstract class Directive[L](implicit val ev: Tuple[L]) { self ⇒
|
|||
*/
|
||||
def &(magnet: ConjunctionMagnet[L]): magnet.Out = magnet(this)
|
||||
|
||||
def as[T](constructor: ConstructFromTuple[L, T]): Directive1[T] =
|
||||
tmap(constructor(_))
|
||||
/**
|
||||
* 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] =
|
||||
new Directive[tupler.Out]()(tupler.OutIsTuple) {
|
||||
def tapply(g: tupler.Out ⇒ Route) = self.tapply { values ⇒ g(tupler(f(values))) }
|
||||
}
|
||||
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] =
|
||||
new Directive[R] {
|
||||
def tapply(g: R ⇒ Route) = self.tapply { values ⇒ f(values).tapply(g) }
|
||||
}
|
||||
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] =
|
||||
new Directive[L] {
|
||||
def tapply(f: L ⇒ Route) =
|
||||
self.tapply { values ⇒ ctx ⇒ if (predicate(values)) f(values)(ctx) else ctx.reject(rejections: _*) }
|
||||
}
|
||||
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] =
|
||||
new Directive[R] {
|
||||
def tapply(inner: R ⇒ Route) = { ctx ⇒
|
||||
self.tapply(list ⇒ c ⇒ inner(list)(c))(ctx).recoverRejectionsWith {
|
||||
rejections ⇒ recovery(rejections).tapply(inner)(ctx)
|
||||
}(ctx.executionContext)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,20 +87,22 @@ abstract class Directive[L](implicit val ev: Tuple[L]) { self ⇒
|
|||
* 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 ⇒
|
||||
if (recovery isDefinedAt rejections) recovery(rejections)
|
||||
else RouteDirectives.reject(rejections: _*)
|
||||
}
|
||||
recover { rejections ⇒ recovery.applyOrElse(rejections, (rejs: Seq[Rejection]) ⇒ RouteDirectives.reject(rejs: _*)) }
|
||||
}
|
||||
|
||||
object Directive {
|
||||
|
||||
/**
|
||||
* Constructs a directive from a function literal.
|
||||
* Note: [[Directive]] itself is sealed to keep the type monomorphic.
|
||||
*/
|
||||
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).
|
||||
*/
|
||||
object Empty extends Directive0 {
|
||||
def tapply(inner: Unit ⇒ Route) = inner(())
|
||||
}
|
||||
val Empty: Directive0 = Directive(_())
|
||||
|
||||
/**
|
||||
* Adds `apply` to all Directives with 1 or more extractions,
|
||||
|
|
@ -146,12 +131,32 @@ object Directive {
|
|||
def filter(predicate: T ⇒ Boolean, rejections: Rejection*): Directive1[T] =
|
||||
underlying.tfilter({ case Tuple1(value) ⇒ predicate(value) }, rejections: _*)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Directive0 that maps the result produced by the inner route with the given function.
|
||||
*/
|
||||
def mapResult(f: (RequestContext, Future[RouteResult]) ⇒ Future[RouteResult]): Directive0 =
|
||||
new Directive0 {
|
||||
def tapply(inner: Unit ⇒ Route): Route = ctx ⇒ f(ctx, inner(())(ctx))
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ trait RequestContext {
|
|||
/**
|
||||
* The default ExecutionContext to be used for scheduling asynchronous logic related to this request.
|
||||
*/
|
||||
def executionContext: ExecutionContext
|
||||
implicit def executionContext: ExecutionContext
|
||||
|
||||
/**
|
||||
* The default LoggingAdapter to be used for logging messages related to this request.
|
||||
|
|
@ -58,6 +58,11 @@ trait RequestContext {
|
|||
*/
|
||||
def withRequest(req: HttpRequest): RequestContext
|
||||
|
||||
/**
|
||||
* Returns a copy of this context with the new HttpRequest.
|
||||
*/
|
||||
def withExecutionContext(ec: ExecutionContext): RequestContext
|
||||
|
||||
/**
|
||||
* Returns a copy of this context with the HttpRequest transformed by the given function.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -28,13 +28,11 @@ private[http] class RequestContextImpl(
|
|||
|
||||
override def complete(trm: ToResponseMarshallable): Future[RouteResult] =
|
||||
trm(request)(executionContext)
|
||||
.fast.map(res ⇒ RouteResult.complete(res))(executionContext)
|
||||
.fast.recover {
|
||||
case RejectionError(rej) ⇒ RouteResult.rejected(rej :: Nil)
|
||||
}(executionContext)
|
||||
.fast.map(res ⇒ RouteResult.Complete(res))(executionContext)
|
||||
.fast.recover { case RejectionError(rej) ⇒ RouteResult.Rejected(rej :: Nil) }(executionContext)
|
||||
|
||||
override def reject(rejections: Rejection*): Future[RouteResult] =
|
||||
FastFuture.successful(RouteResult.rejected(rejections.toVector))
|
||||
FastFuture.successful(RouteResult.Rejected(rejections.toVector))
|
||||
|
||||
override def fail(error: Throwable): Future[RouteResult] =
|
||||
FastFuture.failed(error)
|
||||
|
|
@ -42,6 +40,9 @@ private[http] class RequestContextImpl(
|
|||
override def withRequest(req: HttpRequest): RequestContext =
|
||||
copy(request = req)
|
||||
|
||||
override def withExecutionContext(ec: ExecutionContext): RequestContext =
|
||||
copy(executionContext = ec)
|
||||
|
||||
override def withRequestMapped(f: HttpRequest ⇒ HttpRequest): RequestContext =
|
||||
copy(request = f(request))
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
package akka.http.server
|
||||
|
||||
import akka.http.util.FastFuture
|
||||
import FastFuture._
|
||||
|
||||
trait RouteConcatenation {
|
||||
|
||||
implicit def enhanceRouteWithConcatenation(route: Route) = new RouteConcatenation(route: Route)
|
||||
|
|
@ -13,10 +16,17 @@ trait RouteConcatenation {
|
|||
* Returns a Route that chains two Routes. If the first Route rejects the request the second route is given a
|
||||
* chance to act upon the request.
|
||||
*/
|
||||
def ~(other: Route): Route = ctx ⇒
|
||||
route(ctx).recoverRejectionsWith(outerRejections ⇒
|
||||
other(ctx).recoverRejections(innerRejections ⇒
|
||||
RouteResult.rejected(outerRejections ++ innerRejections))(ctx.executionContext))(ctx.executionContext)
|
||||
def ~(other: Route): Route = { ctx ⇒
|
||||
import ctx.executionContext
|
||||
route(ctx).fast.flatMap {
|
||||
case x: RouteResult.Complete ⇒ FastFuture.successful(x)
|
||||
case RouteResult.Rejected(outerRejections) ⇒
|
||||
other(ctx).fast.map {
|
||||
case x: RouteResult.Complete ⇒ x
|
||||
case RouteResult.Rejected(innerRejections) ⇒ RouteResult.Rejected(outerRejections ++ innerRejections)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ import akka.http.model.HttpResponse
|
|||
sealed trait RouteResult
|
||||
|
||||
object RouteResult {
|
||||
final case class Complete private[RouteResult] (response: HttpResponse) extends RouteResult
|
||||
final case class Rejected private[RouteResult] (rejections: immutable.Seq[Rejection]) extends RouteResult
|
||||
private[http] def complete(response: HttpResponse) = Complete(response)
|
||||
private[http] def rejected(rejections: immutable.Seq[Rejection]) = Rejected(rejections)
|
||||
final case class Complete(response: HttpResponse) extends RouteResult
|
||||
final case class Rejected(rejections: immutable.Seq[Rejection]) extends RouteResult
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,5 @@ object StandardRoute {
|
|||
* Converts the StandardRoute into a directive that never passes the request to its inner route
|
||||
* (and always returns its underlying route).
|
||||
*/
|
||||
implicit def toDirective[L: Tuple](route: StandardRoute): Directive[L] =
|
||||
new Directive[L] {
|
||||
def tapply(f: L ⇒ Route) = route
|
||||
}
|
||||
implicit def toDirective[L: Tuple](route: StandardRoute) = Directive[L] { _ ⇒ route }
|
||||
}
|
||||
|
|
@ -5,18 +5,17 @@
|
|||
package akka.http.server
|
||||
package directives
|
||||
|
||||
import akka.http.util.FastFuture
|
||||
import FastFuture._
|
||||
|
||||
import scala.concurrent.{ Future, ExecutionContext }
|
||||
import scala.collection.immutable
|
||||
import akka.http.server.util.Tuple
|
||||
import akka.http.util.FastFuture
|
||||
import akka.http.model._
|
||||
import FastFuture._
|
||||
|
||||
trait BasicDirectives {
|
||||
|
||||
def mapInnerRoute(f: Route ⇒ Route): Directive0 = new Directive0 {
|
||||
def tapply(inner: Unit ⇒ Route) = f(inner(()))
|
||||
}
|
||||
def mapInnerRoute(f: Route ⇒ Route): Directive0 =
|
||||
Directive { inner ⇒ f(inner(())) }
|
||||
|
||||
def mapRequestContext(f: RequestContext ⇒ RequestContext): Directive0 =
|
||||
mapInnerRoute { inner ⇒ ctx ⇒ inner(f(ctx)) }
|
||||
|
|
@ -24,29 +23,38 @@ trait BasicDirectives {
|
|||
def mapRequest(f: HttpRequest ⇒ HttpRequest): Directive0 =
|
||||
mapRequestContext(_ withRequestMapped f)
|
||||
|
||||
def mapRouteResultFuture(f: Future[RouteResult] ⇒ Future[RouteResult]): Directive0 =
|
||||
Directive { inner ⇒ ctx ⇒ f(inner(())(ctx)) }
|
||||
|
||||
def mapRouteResult(f: RouteResult ⇒ RouteResult): Directive0 =
|
||||
Directive.mapResult { (ctx, result) ⇒
|
||||
result.fast.map(f)(ctx.executionContext)
|
||||
}
|
||||
Directive { inner ⇒ ctx ⇒ inner(())(ctx).fast.map(f)(ctx.executionContext) }
|
||||
|
||||
def mapRouteResultWith(f: RouteResult ⇒ Future[RouteResult]): Directive0 =
|
||||
Directive { inner ⇒ ctx ⇒ inner(())(ctx).fast.flatMap(f)(ctx.executionContext) }
|
||||
|
||||
def mapRouteResultPF(f: PartialFunction[RouteResult, RouteResult]): Directive0 =
|
||||
mapRouteResult(f.applyOrElse(_, akka.http.util.identityFunc[RouteResult]))
|
||||
|
||||
def mapRouteResultWithPF(f: PartialFunction[RouteResult, Future[RouteResult]]): Directive0 =
|
||||
mapRouteResultWith(f.applyOrElse(_, FastFuture.successful[RouteResult]))
|
||||
|
||||
def recoverRejections(f: immutable.Seq[Rejection] ⇒ RouteResult): Directive0 =
|
||||
mapRouteResultPF { case RouteResult.Rejected(rejections) ⇒ f(rejections) }
|
||||
|
||||
def recoverRejectionsWith(f: immutable.Seq[Rejection] ⇒ Future[RouteResult]): Directive0 =
|
||||
mapRouteResultWithPF { case RouteResult.Rejected(rejections) ⇒ f(rejections) }
|
||||
|
||||
def mapRejections(f: immutable.Seq[Rejection] ⇒ immutable.Seq[Rejection]): Directive0 =
|
||||
Directive.mapResult { (ctx, result) ⇒
|
||||
result.recoverRejections(rejs ⇒ RouteResult.rejected(f(rejs)))(ctx.executionContext)
|
||||
}
|
||||
recoverRejections(rejections ⇒ RouteResult.Rejected(f(rejections)))
|
||||
|
||||
def mapResponse(f: HttpResponse ⇒ HttpResponse): Directive0 =
|
||||
Directive.mapResult { (ctx, result) ⇒
|
||||
result.mapResponse(r ⇒ RouteResult.complete(f(r)))(ctx.executionContext)
|
||||
}
|
||||
mapRouteResultPF { case RouteResult.Complete(response) ⇒ RouteResult.Complete(f(response)) }
|
||||
|
||||
def mapResponseEntity(f: ResponseEntity ⇒ ResponseEntity): Directive0 =
|
||||
mapResponse(_.mapEntity(f))
|
||||
mapResponse(_ mapEntity f)
|
||||
|
||||
def mapResponseHeaders(f: immutable.Seq[HttpHeader] ⇒ immutable.Seq[HttpHeader]): Directive0 =
|
||||
mapResponse(_.mapHeaders(f))
|
||||
mapResponse(_ mapHeaders f)
|
||||
|
||||
/**
|
||||
* A Directive0 that always passes the request on to its inner route
|
||||
|
|
@ -62,9 +70,8 @@ trait BasicDirectives {
|
|||
/**
|
||||
* Injects the given values into a directive.
|
||||
*/
|
||||
def tprovide[L: Tuple](values: L): Directive[L] = new Directive[L] {
|
||||
def tapply(f: L ⇒ Route) = f(values)
|
||||
}
|
||||
def tprovide[L: Tuple](values: L): Directive[L] =
|
||||
Directive { _(values) }
|
||||
|
||||
/**
|
||||
* Extracts a single value using the given function.
|
||||
|
|
@ -75,9 +82,8 @@ trait BasicDirectives {
|
|||
/**
|
||||
* Extracts a number of values using the given function.
|
||||
*/
|
||||
def textract[L: Tuple](f: RequestContext ⇒ L): Directive[L] = new Directive[L] {
|
||||
def tapply(inner: L ⇒ Route) = ctx ⇒ inner(f(ctx))(ctx)
|
||||
}
|
||||
def textract[L: Tuple](f: RequestContext ⇒ L): Directive[L] =
|
||||
Directive { inner ⇒ ctx ⇒ inner(f(ctx))(ctx) }
|
||||
|
||||
/**
|
||||
* Adds a TransformationRejection cancelling all rejections equal to the given one
|
||||
|
|
@ -120,10 +126,28 @@ trait BasicDirectives {
|
|||
* Extracts the complete request URI.
|
||||
*/
|
||||
def requestUri: Directive1[Uri] = BasicDirectives._requestUri
|
||||
|
||||
/**
|
||||
* Runs its inner route with the given alternative [[ExecutionContext]].
|
||||
*/
|
||||
def withExecutionContext(ec: ExecutionContext): Directive0 =
|
||||
mapRequestContext(_ withExecutionContext ec)
|
||||
|
||||
/**
|
||||
* Extracts the [[ExecutionContext]] from the [[RequestContext]].
|
||||
*/
|
||||
def withExecutionContext: Directive1[ExecutionContext] = BasicDirectives._withExecutionContext
|
||||
|
||||
/**
|
||||
* Extracts the [[RequestContext]] itself.
|
||||
*/
|
||||
def withRequestContext: Directive1[RequestContext] = BasicDirectives._withRequestContext
|
||||
}
|
||||
|
||||
object BasicDirectives extends BasicDirectives {
|
||||
private val _unmatchedPath: Directive1[Uri.Path] = extract(_.unmatchedPath)
|
||||
private val _requestInstance: Directive1[HttpRequest] = extract(_.request)
|
||||
private val _requestUri: Directive1[Uri] = extract(_.request.uri)
|
||||
private val _withExecutionContext: Directive1[ExecutionContext] = extract(_.executionContext)
|
||||
private val _withRequestContext: Directive1[RequestContext] = extract(akka.http.util.identityFunc)
|
||||
}
|
||||
|
|
@ -5,6 +5,9 @@
|
|||
package akka.http.server
|
||||
package directives
|
||||
|
||||
import akka.http.util.FastFuture
|
||||
import FastFuture._
|
||||
|
||||
trait ExecutionDirectives {
|
||||
import BasicDirectives._
|
||||
|
||||
|
|
@ -13,11 +16,11 @@ trait ExecutionDirectives {
|
|||
* [[akka.http.server.ExceptionHandler]].
|
||||
*/
|
||||
def handleExceptions(handler: ExceptionHandler): Directive0 =
|
||||
Directive.mapResult { (ctx, result) ⇒
|
||||
def handleError = handler andThen (_(ctx.withContentNegotiationDisabled))
|
||||
result.recoverWith {
|
||||
case error if handler isDefinedAt error ⇒ handleError(error)
|
||||
}(ctx.executionContext)
|
||||
withRequestContext flatMap { ctx ⇒
|
||||
import ctx.executionContext
|
||||
mapRouteResultFuture {
|
||||
_.fast.recoverWith(handler andThen (_(ctx.withContentNegotiationDisabled)))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -25,16 +28,16 @@ trait ExecutionDirectives {
|
|||
* [[akka.http.server.RejectionHandler]].
|
||||
*/
|
||||
def handleRejections(handler: RejectionHandler): Directive0 =
|
||||
Directive.mapResult { (ctx, result) ⇒
|
||||
result.recoverRejectionsWith {
|
||||
case rejections ⇒
|
||||
withRequestContext flatMap { ctx ⇒
|
||||
recoverRejectionsWith { rejections ⇒
|
||||
val filteredRejections = RejectionHandler.applyTransformations(rejections)
|
||||
if (handler isDefinedAt filteredRejections)
|
||||
handler(filteredRejections)(ctx.withContentNegotiationDisabled).recoverRejections { r ⇒
|
||||
sys.error(s"The RejectionHandler for $rejections must not itself produce rejections (received $r)!")
|
||||
}(ctx.executionContext)
|
||||
else ctx.reject(filteredRejections: _*)
|
||||
}(ctx.executionContext)
|
||||
if (handler isDefinedAt filteredRejections) {
|
||||
val errorMsg = "The RejectionHandler for %s must not itself produce rejections (received %s)!"
|
||||
recoverRejections(r ⇒ sys.error(errorMsg.format(filteredRejections, r))) {
|
||||
handler(filteredRejections)
|
||||
}(ctx.withContentNegotiationDisabled)
|
||||
} else FastFuture.successful(RouteResult.Rejected(filteredRejections))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ package directives
|
|||
|
||||
import scala.concurrent.Future
|
||||
import scala.util.{ Failure, Success, Try }
|
||||
|
||||
import akka.http.marshalling.ToResponseMarshaller
|
||||
import akka.http.server.util.Tupler
|
||||
import akka.http.util.FastFuture
|
||||
import FastFuture._
|
||||
|
||||
import akka.http.marshalling.ToResponseMarshaller
|
||||
import akka.http.server.util.Tupler
|
||||
// format: OFF
|
||||
|
||||
trait FutureDirectives {
|
||||
|
||||
|
|
@ -21,9 +21,9 @@ trait FutureDirectives {
|
|||
* completion with the future's value as an extraction of type ``Try[T]``.
|
||||
*/
|
||||
def onComplete[T](future: ⇒ Future[T]): Directive1[Try[T]] =
|
||||
new Directive1[Try[T]] {
|
||||
def tapply(f: Tuple1[Try[T]] ⇒ Route): Route = ctx ⇒
|
||||
future.fast.transformWith(t ⇒ f(Tuple1(t))(ctx))(ctx.executionContext)
|
||||
Directive { inner ⇒ ctx ⇒
|
||||
import ctx.executionContext
|
||||
future.fast.transformWith(t ⇒ inner(Tuple1(t))(ctx))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -34,7 +34,7 @@ trait FutureDirectives {
|
|||
* If type ``T`` is already a Tuple it is directly expanded into the respective
|
||||
* number of extractions.
|
||||
*/
|
||||
def onSuccess(magnet: OnSuccessMagnet): Directive[magnet.Out] = magnet.get
|
||||
def onSuccess(magnet: OnSuccessMagnet): Directive[magnet.Out] = magnet.directive
|
||||
|
||||
/**
|
||||
* "Unwraps" a ``Future[T]`` and runs its inner route when the future has failed
|
||||
|
|
@ -43,34 +43,40 @@ trait FutureDirectives {
|
|||
* (This directive therefore requires a marshaller for the futures type to be
|
||||
* implicitly available.)
|
||||
*/
|
||||
def completeOrRecoverWith(magnet: CompleteOrRecoverWithMagnet): Directive1[Throwable] = magnet
|
||||
def completeOrRecoverWith(magnet: CompleteOrRecoverWithMagnet): Directive1[Throwable] = magnet.directive
|
||||
}
|
||||
|
||||
object FutureDirectives extends FutureDirectives
|
||||
|
||||
trait OnSuccessMagnet {
|
||||
type Out
|
||||
def get: Directive[Out]
|
||||
def directive: Directive[Out]
|
||||
}
|
||||
|
||||
object OnSuccessMagnet {
|
||||
implicit def apply[T](future: ⇒ Future[T])(implicit tupler: Tupler[T]) =
|
||||
new Directive[tupler.Out]()(tupler.OutIsTuple) with OnSuccessMagnet {
|
||||
new OnSuccessMagnet {
|
||||
type Out = tupler.Out
|
||||
def get = this
|
||||
def tapply(f: Out ⇒ Route) = ctx ⇒ future.fast.flatMap(t ⇒ f(tupler(t))(ctx))(ctx.executionContext)
|
||||
val directive = Directive[tupler.Out] { inner ⇒ ctx ⇒
|
||||
import ctx.executionContext
|
||||
future.fast.flatMap(t ⇒ inner(tupler(t))(ctx))
|
||||
}(tupler.OutIsTuple)
|
||||
}
|
||||
}
|
||||
|
||||
trait CompleteOrRecoverWithMagnet extends Directive1[Throwable]
|
||||
trait CompleteOrRecoverWithMagnet {
|
||||
def directive: Directive1[Throwable]
|
||||
}
|
||||
|
||||
object CompleteOrRecoverWithMagnet {
|
||||
implicit def apply[T](future: ⇒ Future[T])(implicit m: ToResponseMarshaller[T]) =
|
||||
new CompleteOrRecoverWithMagnet {
|
||||
def tapply(f: Tuple1[Throwable] ⇒ Route) = ctx ⇒
|
||||
val directive = Directive[Tuple1[Throwable]] { inner ⇒ ctx ⇒
|
||||
import ctx.executionContext
|
||||
future.fast.transformWith {
|
||||
case Success(res) ⇒ ctx.complete(res)
|
||||
case Failure(error) ⇒ f(Tuple1(error))(ctx)
|
||||
}(ctx.executionContext)
|
||||
case Failure(error) ⇒ inner(Tuple1(error))(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,10 @@
|
|||
package akka.http.server
|
||||
package directives
|
||||
|
||||
import scala.reflect.{ classTag, ClassTag }
|
||||
import akka.http.model._
|
||||
import akka.parboiled2.CharPredicate
|
||||
import headers._
|
||||
import MediaTypes._
|
||||
import RouteResult._
|
||||
|
||||
trait MiscDirectives {
|
||||
import BasicDirectives._
|
||||
import RouteDirectives._
|
||||
|
||||
/**
|
||||
|
|
@ -21,9 +16,7 @@ trait MiscDirectives {
|
|||
* its inner Route. If the condition fails the route is rejected with a [[spray.routing.ValidationRejection]].
|
||||
*/
|
||||
def validate(check: ⇒ Boolean, errorMsg: String): Directive0 =
|
||||
new Directive0 {
|
||||
def tapply(f: Unit ⇒ Route) = if (check) f() else reject(ValidationRejection(errorMsg))
|
||||
}
|
||||
Directive { inner ⇒ if (check) inner() else reject(ValidationRejection(errorMsg)) }
|
||||
|
||||
/**
|
||||
* Directive extracting the IP of the client from either the X-Forwarded-For, Remote-Address or X-Real-IP header
|
||||
|
|
@ -54,9 +47,7 @@ object MiscDirectives extends MiscDirectives {
|
|||
import BasicDirectives._
|
||||
import HeaderDirectives._
|
||||
import RouteDirectives._
|
||||
import CharPredicate._
|
||||
|
||||
private val validJsonpChars = AlphaNum ++ '.' ++ '_' ++ '$'
|
||||
import RouteResult._
|
||||
|
||||
private val _clientIP: Directive1[RemoteAddress] =
|
||||
headerValuePF { case `X-Forwarded-For`(Seq(address, _*)) ⇒ address } |
|
||||
|
|
@ -71,7 +62,7 @@ object MiscDirectives extends MiscDirectives {
|
|||
|
||||
private val _rejectEmptyResponse: Directive0 =
|
||||
mapRouteResult {
|
||||
case Complete(response) if response.entity.isKnownEmpty ⇒ rejected(Nil)
|
||||
case Complete(response) if response.entity.isKnownEmpty ⇒ Rejected(Nil)
|
||||
case x ⇒ x
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,7 @@
|
|||
|
||||
package akka.http
|
||||
|
||||
import scala.collection.immutable
|
||||
|
||||
import scala.concurrent.{ ExecutionContext, Future }
|
||||
|
||||
import akka.http.util.FastFuture
|
||||
import FastFuture._
|
||||
|
||||
import akka.http.model.HttpResponse
|
||||
import scala.concurrent.Future
|
||||
|
||||
package object server {
|
||||
|
||||
|
|
@ -28,24 +21,4 @@ package object server {
|
|||
def Route(f: Route): Route = f
|
||||
|
||||
def FIXME = throw new RuntimeException("Not yet implemented")
|
||||
|
||||
private[http] implicit class EnhanceFutureRouteResult(val result: Future[RouteResult]) extends AnyVal {
|
||||
def mapResponse(f: HttpResponse ⇒ RouteResult)(implicit ec: ExecutionContext): Future[RouteResult] =
|
||||
mapResponseWith(response ⇒ FastFuture.successful(f(response)))
|
||||
|
||||
def mapResponseWith(f: HttpResponse ⇒ Future[RouteResult])(implicit ec: ExecutionContext): Future[RouteResult] =
|
||||
result.fast.flatMap {
|
||||
case RouteResult.Complete(response) ⇒ f(response)
|
||||
case r: RouteResult.Rejected ⇒ FastFuture.successful(r)
|
||||
}
|
||||
|
||||
def recoverRejections(f: immutable.Seq[Rejection] ⇒ RouteResult)(implicit ec: ExecutionContext): Future[RouteResult] =
|
||||
recoverRejectionsWith(rej ⇒ FastFuture.successful(f(rej)))
|
||||
|
||||
def recoverRejectionsWith(f: immutable.Seq[Rejection] ⇒ Future[RouteResult])(implicit ec: ExecutionContext): Future[RouteResult] =
|
||||
result.fast.flatMap {
|
||||
case c: RouteResult.Complete ⇒ FastFuture.successful(c)
|
||||
case RouteResult.Rejected(rejections) ⇒ f(rejections)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue