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
--------------------
.. note:: Gotcha: forgetting the ``~`` (tilde) character in between directives can often result in perfectly valid
Scala code that compiles but lead to your composed directive only containing the up to where ``~`` is missing.
.. note:: Gotcha: forgetting the ``~`` (tilde) character in between directives can result in perfectly valid
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.
Let's take a look at this concrete example:

View file

@ -4,7 +4,7 @@
package akka.http.impl.util
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{Sink, Source}
import akka.stream.scaladsl.{ Sink, Source }
import akka.testkit.AkkaSpec
import scala.concurrent.Await
@ -34,7 +34,6 @@ class StreamUtilsSpec extends AkkaSpec {
Await.ready(whenCompleted, 3.seconds).value shouldBe Some(Failure(ex))
}
"downstream cancels" in {
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 {
val stringDirective = provide("The cat")
val intDirective = provide(42)

View file

@ -4,6 +4,7 @@
package akka.http.scaladsl.server
import akka.http.scaladsl.server.Directives.reject
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 =
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 {