Skip to content

Composing Directives

Mathias edited this page Mar 8, 2012 · 7 revisions

As you have seen from the examples presented so far the "normal" way of composing directives is nesting. Let's take another look at the example from the Directives page:

val route: Route =
  path("order" / HexIntNumber) { id =>
    get {
      completeWith {
        "Received GET request for order " + id
      }
    } ~
    put {
      completeWith {
        "Received PUT request for order " + id
      }
    }
  }

Here the get and put directives are chained together with the ~ operator to form a higher-level route that serves as the inner route of the path directive. To make this structure more explicit you could also write the whole thing as such:

def innerRoute(id: Int): Route =
  get {
    completeWith {
      "Received GET request for order " + id
    }
  } ~
  put {
    completeWith {
      "Received PUT request for order " + id
    }
  }

val route: Route = path("order" / HexIntNumber) { id => innerRoute(id) }

What you can't see from this snippet is that directives are not implemented as simple methods but rather as stand-alone objects (of a SprayRoute subtype). This gives you more flexibility when composing directives. For example you can also use the | operator on directives. Here is yet another way to write the example:

val route =
  path("order" / HexIntNumber) { id =>
    (get | put) { ctx =>
      ctx.complete("Received " + ctx.request.method + " request for order " + id)
    }
  }

If you have a larger route structure where the (get | put) snippet appears several times you could also factor it out like this:

val getOrPut = get | put
val route =
  path("order" / HexIntNumber) { id =>
    getOrPut { ctx =>
      ctx.complete("Received " + ctx.request.method + " request for order " + id)
    }
  }

As an alternative to nesting you can also use the & operator:

val getOrPut = get | put
val route =
  (path("order" / HexIntNumber) & getOrPut) { id => ctx =>
    ctx.complete("Received " + ctx.request.method + " request for order " + id)
  }

And once again, you can factor things out if you want:

val orderGetOrPut = path("order" / HexIntNumber) & (get | put)
val route =
  orderGetOrPut { id => ctx =>
    ctx.complete("Received " + ctx.request.method + " request for order " + id)
  }

This type of combining directives with the | and & operators as well as "saving" more complex directive configurations as a val works across the board, with almost all directives taking inner routes. Thereby spray makes sure that all extractions work as expected and logical constraints are enforced at compile-time. For example you cannot | a directive producing an extraction with one that doesn't:

val route = path("order" / IntNumber) | get // doesn't compile

Also the extraction types have to match up:

val route = path("order" / IntNumber) | path("order" / DoubleNumber)   // doesn't compile
val route = path("order" / IntNumber) | parameter('order.as[Int])      // ok

When you combine directives producing extractions with the & operator all extractions will be properly gathered up:

val order = path("order" / IntNumber) & parameters('oem, 'expired ?)
val route =
  order { (orderId, oem, expired) =>
    ...
  }

Summing up, directives offer a great way of constructing your web service logic from small building blocks in a plug and play fashion while maintaining DRYness and full type-safety. If the large range of predefined directives does not fully satisfy your needs you can also very easily create Custom Directives.

Clone this wiki locally