diff --git a/akka-http-tests/src/test/scala/akka/http/server/BasicRouteSpecs.scala b/akka-http-tests/src/test/scala/akka/http/server/BasicRouteSpecs.scala index 309e52d149..3d84d38f70 100644 --- a/akka-http-tests/src/test/scala/akka/http/server/BasicRouteSpecs.scala +++ b/akka-http-tests/src/test/scala/akka/http/server/BasicRouteSpecs.scala @@ -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 { diff --git a/akka-http/src/main/scala/akka/http/server/Directive.scala b/akka-http/src/main/scala/akka/http/server/Directive.scala index e7c21a534b..c867b82be4 100644 --- a/akka-http/src/main/scala/akka/http/server/Directive.scala +++ b/akka-http/src/main/scala/akka/http/server/Directive.scala @@ -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)) + } +} \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/server/RequestContext.scala b/akka-http/src/main/scala/akka/http/server/RequestContext.scala index 35b83ec445..6a0c7476d7 100644 --- a/akka-http/src/main/scala/akka/http/server/RequestContext.scala +++ b/akka-http/src/main/scala/akka/http/server/RequestContext.scala @@ -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. */ diff --git a/akka-http/src/main/scala/akka/http/server/RequestContextImpl.scala b/akka-http/src/main/scala/akka/http/server/RequestContextImpl.scala index 8ab54782f5..d61d3fd971 100644 --- a/akka-http/src/main/scala/akka/http/server/RequestContextImpl.scala +++ b/akka-http/src/main/scala/akka/http/server/RequestContextImpl.scala @@ -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)) diff --git a/akka-http/src/main/scala/akka/http/server/RouteConcatenation.scala b/akka-http/src/main/scala/akka/http/server/RouteConcatenation.scala index 937140d425..9ce235f68d 100644 --- a/akka-http/src/main/scala/akka/http/server/RouteConcatenation.scala +++ b/akka-http/src/main/scala/akka/http/server/RouteConcatenation.scala @@ -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) + } + } + } } } diff --git a/akka-http/src/main/scala/akka/http/server/RouteResult.scala b/akka-http/src/main/scala/akka/http/server/RouteResult.scala index dba69d853f..bf12eb0563 100644 --- a/akka-http/src/main/scala/akka/http/server/RouteResult.scala +++ b/akka-http/src/main/scala/akka/http/server/RouteResult.scala @@ -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 } diff --git a/akka-http/src/main/scala/akka/http/server/StandardRoute.scala b/akka-http/src/main/scala/akka/http/server/StandardRoute.scala index 84adb40eb7..09d5e20b8c 100644 --- a/akka-http/src/main/scala/akka/http/server/StandardRoute.scala +++ b/akka-http/src/main/scala/akka/http/server/StandardRoute.scala @@ -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 } } \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/server/directives/BasicDirectives.scala b/akka-http/src/main/scala/akka/http/server/directives/BasicDirectives.scala index 57774d6225..5f7b8a971d 100644 --- a/akka-http/src/main/scala/akka/http/server/directives/BasicDirectives.scala +++ b/akka-http/src/main/scala/akka/http/server/directives/BasicDirectives.scala @@ -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) } \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/server/directives/ExecutionDirectives.scala b/akka-http/src/main/scala/akka/http/server/directives/ExecutionDirectives.scala index 49161dc427..53f768b4d1 100644 --- a/akka-http/src/main/scala/akka/http/server/directives/ExecutionDirectives.scala +++ b/akka-http/src/main/scala/akka/http/server/directives/ExecutionDirectives.scala @@ -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)) + } } } diff --git a/akka-http/src/main/scala/akka/http/server/directives/FutureDirectives.scala b/akka-http/src/main/scala/akka/http/server/directives/FutureDirectives.scala index 168f9d7ead..073b6cb392 100644 --- a/akka-http/src/main/scala/akka/http/server/directives/FutureDirectives.scala +++ b/akka-http/src/main/scala/akka/http/server/directives/FutureDirectives.scala @@ -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) + } + } } } diff --git a/akka-http/src/main/scala/akka/http/server/directives/MiscDirectives.scala b/akka-http/src/main/scala/akka/http/server/directives/MiscDirectives.scala index 714a6e0e8a..9eef1fa050 100644 --- a/akka-http/src/main/scala/akka/http/server/directives/MiscDirectives.scala +++ b/akka-http/src/main/scala/akka/http/server/directives/MiscDirectives.scala @@ -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 } } diff --git a/akka-http/src/main/scala/akka/http/server/package.scala b/akka-http/src/main/scala/akka/http/server/package.scala index 6aed6ff846..ab19fd1b0c 100644 --- a/akka-http/src/main/scala/akka/http/server/package.scala +++ b/akka-http/src/main/scala/akka/http/server/package.scala @@ -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) - } - } } \ No newline at end of file