!htp #16101 improve basic directive infrastructure, add more scaladoc, fix smaller problems

This commit is contained in:
Mathias 2014-10-17 11:50:49 +02:00
parent 33ddabbc9f
commit 0033e74cae
12 changed files with 204 additions and 182 deletions

View file

@ -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 {

View file

@ -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,59 +35,74 @@ 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)
}
}
/**
* 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))
}
}

View file

@ -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.
*/

View file

@ -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))

View file

@ -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)
}
}
}
}
}

View file

@ -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
}

View file

@ -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 }
}

View file

@ -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)
}

View file

@ -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
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)
withRequestContext flatMap { ctx
recoverRejectionsWith { rejections
val filteredRejections = RejectionHandler.applyTransformations(rejections)
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))
}
}
}

View file

@ -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)
}
}
}
}

View file

@ -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
}
}

View file

@ -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)
}
}
}