htp #19678 add variadic concat route combinator

* add variadic route concatenation function

* add `concat` to `~` warning bubble in docs

* fix grammar of warning bubble, and clarify
This commit is contained in:
Alan Johnson 2016-04-05 09:36:46 -04:00 committed by Konrad Malawski
parent 09d5072f2c
commit b3c85512a3
4 changed files with 51 additions and 4 deletions

View file

@ -138,8 +138,8 @@ transformations, both (or either) on the request and on the response side.
Composing Directives Composing Directives
-------------------- --------------------
.. note:: Gotcha: forgetting the ``~`` (tilde) character in between directives can often result in perfectly valid .. note:: Gotcha: forgetting the ``~`` (tilde) character in between directives can result in perfectly valid
Scala code that compiles but lead to your composed directive only containing the up to where ``~`` is missing. Scala code that compiles but does not work as expected. What would be intended as a single expression would actually be multiple expressions, and only the final one would be used as the result of the parent directive. Alternatively, you might choose to use the ``concat`` combinator. ``concat(a, b, c)`` is the same as ``a ~ b ~ c``.
As you have seen from the examples presented so far the "normal" way of composing directives is nesting. As you have seen from the examples presented so far the "normal" way of composing directives is nesting.
Let's take a look at this concrete example: Let's take a look at this concrete example:

View file

@ -34,7 +34,6 @@ class StreamUtilsSpec extends AkkaSpec {
Await.ready(whenCompleted, 3.seconds).value shouldBe Some(Failure(ex)) Await.ready(whenCompleted, 3.seconds).value shouldBe Some(Failure(ex))
} }
"downstream cancels" in { "downstream cancels" in {
val (newSource, whenCompleted) = StreamUtils.captureTermination(Source(List(1, 2, 3))) val (newSource, whenCompleted) = StreamUtils.captureTermination(Source(List(1, 2, 3)))

View file

@ -35,6 +35,42 @@ class BasicRouteSpecs extends RoutingSpec {
} }
} }
"routes created by the 'sequence' directive" should {
"reject with zero arguments, since no routes were matched" in {
Get() ~> {
concat()
} ~> check { rejections shouldEqual Seq() }
}
"yield the first sub route if it succeeded" in {
Get() ~> {
concat(
get { complete("first") },
get { complete("second") })
} ~> check { responseAs[String] shouldEqual "first" }
}
"yield the second sub route if the first did not succeed" in {
Get() ~> {
concat(
post { complete("first") },
get { complete("second") })
} ~> check { responseAs[String] shouldEqual "second" }
}
"collect rejections from both sub routes" in {
Delete() ~> {
concat(
get { completeOk },
put { completeOk })
} ~> check { rejections shouldEqual Seq(MethodRejection(GET), MethodRejection(PUT)) }
}
"clear rejections that have already been 'overcome' by previous directives" in {
Put() ~> {
concat(
put { parameter('yeah) { echoComplete } },
get { completeOk })
} ~> check { rejection shouldEqual MissingQueryParamRejection("yeah") }
}
}
"Route conjunction" should { "Route conjunction" should {
val stringDirective = provide("The cat") val stringDirective = provide("The cat")
val intDirective = provide(42) val intDirective = provide(42)

View file

@ -4,6 +4,7 @@
package akka.http.scaladsl.server package akka.http.scaladsl.server
import akka.http.scaladsl.server.Directives.reject
import akka.http.scaladsl.util.FastFuture import akka.http.scaladsl.util.FastFuture
import akka.http.scaladsl.util.FastFuture._ import akka.http.scaladsl.util.FastFuture._
@ -18,6 +19,17 @@ trait RouteConcatenation {
*/ */
implicit def enhanceRouteWithConcatenation(route: Route): RouteConcatenation.RouteWithConcatenation = implicit def enhanceRouteWithConcatenation(route: Route): RouteConcatenation.RouteWithConcatenation =
new RouteConcatenation.RouteWithConcatenation(route: Route) new RouteConcatenation.RouteWithConcatenation(route: Route)
/**
* Tries the supplied routes in sequence, returning the result of the first route that doesn't reject the request.
* This is an alternative to direct usage of the infix ~ operator. The ~ can be prone to programmer error, because if
* it is omitted, the program will still be syntactically correct, but will not actually attempt to match multiple
* routes, as intended.
*
* @param routes subroutes to concatenate
* @return the concatenated route
*/
def concat(routes: Route*): Route = routes.foldLeft[Route](reject)(_ ~ _)
} }
object RouteConcatenation extends RouteConcatenation { object RouteConcatenation extends RouteConcatenation {