Case Class Extraction
This documentation is for release 0.9.0 (from 03/2012), which is built against Scala 2.9.1 and Akka 1.3.1 (see Requirements for more information). Most likely, this is not the place you want to look for information. Please turn to the main spray site at http://spray.io for more information about other available versions.
. . .
The value extraction performed by spray directives is a nice way of providing your application or route logic with interesting request properties, all with proper type-safety and error handling. However, in some case you might want even more. Consider this example:
case class Color(red: Int, green: Int, blue: Int)
val route =
path("color") {
parameters('red as[Int], 'green as[Int], 'blue as[Int]) { (red, green, blue) =>
val color = Color(red, green, blue)
... // route working with the Color instance
}
}
Here you are employing a parameters
directives to extract three Int
values, which are used to construct an
instance of the Color
case class. So far so good. However, if the classes you'd like to work with have more than
just a few parameters the overhead introduced by capturing the arguments as extractions only to feed them into the
class constructor directly afterwards can somewhat clutter up your route definitions.
If your classes are case classes spray supports an even shorter and more concise syntax. You can also write the example above like this:
case class Color(red: Int, green: Int, blue: Int)
val route =
path("color") {
parameters('red as[Int], 'green as[Int], 'blue as[Int])
.as(Color) { color =>
... // route working with the Color instance
}
}
You can postfix any directive with extractions with an as(...)
call. The as
method takes a Deserializer
instance
as argument, which is essentially a function Either[DeserializationError, T]
. Usually you don't supply this
deserializer directly but rather rely on implicit conversions that construct the proper deserializer from the apply
method of your cases classes companion object. Most of the time it's enough to simply say
as(CaseClassCompanionObject)
, which performs all the magic with the help of the Scala compiler (without any reflection
involved and without any further requirements on your case class that is!). The only requirement is that the directive
you attach the as
call to produces the right number of extractions, with the right types and in the right order.
If you'd like to construct a case class instance from extractions produced by several directives you can first join
the directives with the &
operator before using the as
call:
case class Color(name: String, red: Int, green: Int, blue: Int)
val route =
(path("color" / PathElement) &
parameters('red as[Int], 'green as[Int], 'blue as[Int]))
.as(Color) { color =>
... // route working with the Color instance
}
Here the Color
class has gotten another member, name
, which is supplied not as a parameter but as a path element.
By joining the path
and parameters
directives with &
you create a directive extracting 4 values, which directly
fit the member list of the Color
case class. Therefore you can use the as
call to directly create a Color
instance from the extractions.
Generally, when you have routes that work with, say, more than 3 extractions it's a good idea to introduce a case class for these and use sprays case class extraction. Especially since it supports an nice feature: validation.
In many cases your web service needs to verify input parameters according to some logic before actually working with
them. E.g. in the example above the restriction might be that all color component values must be between 0 and 255.
You could get this done with a few validate
directives but this would quickly become cumbersome and hard to read.
If you use case class extraction you can put the verification logic into the constructor of your case class, where it should be:
case class Color(name: String, red: Int, green: Int, blue: Int) {
require(!name.isEmpty, "color name must not be empty")
require(0 <= red && red <= 255, "red color component must be between 0 and 255")
require(0 <= green && green <= 255, "green color component must be between 0 and 255")
require(0 <= blue && blue <= 255, "blue color component must be between 0 and 255")
}
If you write your validations like this sprays case class extraction logic will properly pick up all error messages
and generate a ValidationRejection if something goes wrong. By default, ValidationRejections
trigger a
corresponding 400 Bad Request
error response, if no subsequent route successfully handles the request.
There is one quirk to look out for when using case class extraction: If you create an explicit companion object for your
case class, no matter whether you actually add any members to it or not, the syntax presented above will not (quite)
work anymore. Instead of as(Color)
you will then have to say as(Color.apply)
. This behavior appears as if it's not
really intended, we will try to work with the guys at TypeSafe to fix this.
- Home
- Requirements
- ... Getting Started
- ... Key Concepts
- ...... Request Lifecycle
- ...... Routes
- ...... Directives
- ...... Composing Directives
- ...... Rejections
- ...... Marshalling and Unmarshalling
- ... Predefined Directives
- ...... Method Filters
- ...... Path Filters
- ...... Parameter Filters
- ...... Form-Field Filters
- ...... Marshalling/Unmarshalling
- ...... Caching
- ...... Detach
- ...... Encoding/Decoding
- ...... Authentication/Authorization
- ...... File and Resource Directives
- ...... Misc Directives
- ... Advanced Topics
- ...... Case Class Extraction
- ...... Custom Directives
- ...... Custom Media Types
- ...... Custom Error Responses
- ... Configuration
- ... Testing
- ... Example Projects
- spray-client
- Patch Policy
- Credits
- Sponsors