Skip to content

Commit

Permalink
Construct FilterDirective where possible, to enable filtering.
Browse files Browse the repository at this point in the history
When a directive is constructed via `when ... orElse` or
`Directive#orElse`, we can filter a successful result and fall back to
the orElse directive. This allows for ad-hoc extention of directive
requirements in the `if` clause of a for expression.

Added a demonstration to DirectiveSpec.
  • Loading branch information
Nathan Hamblen committed Sep 8, 2014
1 parent ca98438 commit 9ed6a7f
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 7 deletions.
8 changes: 4 additions & 4 deletions directives/src/main/scala/directives/Directive.scala
Expand Up @@ -71,7 +71,7 @@ extends (HttpRequest[T] => Result[R, A]) {
Directive(r => run(r).flatMap(a => f(a)(r)))

def orElse[TT <: T, RR >: R, B >: A](next: => Directive[TT, RR, B]): Directive[TT, RR, B] =
Directive(r => run(r).orElse(next(r)))
new FilterDirective(r => run(r).orElse(next(r)), next)

def | [TT <: T, RR >: R, B >: A](next: => Directive[TT, RR, B]): Directive[TT, RR, B] =
orElse(next)
Expand All @@ -83,7 +83,7 @@ extends (HttpRequest[T] => Result[R, A]) {
// use to extract joined successes. This is a no-op filter; an improved
// response joining system would make `toResponseFunction` available
// to use here with an empty Seq.
new FilteringDirective[TT, JoiningResponseFunction[E,RF], (A,B)](
new FilterDirective[TT, JoiningResponseFunction[E,RF], (A,B)](
runner,
runner
)
Expand All @@ -100,12 +100,12 @@ extends (HttpRequest[T] => Result[R, A]) {

/** Supports filtering by requiring a handler for when success is filtered away.
The onEmtpy handler may produce a success or failure; typically the latter. */
class FilteringDirective[-T, +R, +A](
class FilterDirective[-T, +R, +A](
run: HttpRequest[T] => Result[R, A],
onEmpty: HttpRequest[T] => Result[R, A]
) extends Directive[T,R,A](run) {
def withFilter(filt: A => Boolean): Directive[T, R, A] =
new FilteringDirective({ req =>
new FilterDirective({ req =>
run(req).flatMap { a =>
if (filt(a)) Result.Success(a)
else onEmpty(req)
Expand Down
6 changes: 5 additions & 1 deletion directives/src/main/scala/directives/Directives.scala
Expand Up @@ -45,7 +45,11 @@ trait Directives {

/* HttpRequest has to be of type Any because of type-inference (SLS 8.5) */
case class when[A](f:PartialFunction[HttpRequest[Any], A]){
def orElse[R](fail:ResponseFunction[R]) = Directive[Any, ResponseFunction[R], A](r => if(f.isDefinedAt(r)) Success(f(r)) else Failure(fail))
def orElse[R](fail:ResponseFunction[R]) =
new FilterDirective[Any, ResponseFunction[R], A](r =>
if(f.isDefinedAt(r)) Success(f(r)) else Failure(fail),
_ => Failure(fail)
)
}

def request[T] = Directive[T, Nothing, HttpRequest[T]](Success(_))
Expand Down
37 changes: 35 additions & 2 deletions directives/src/test/scala/DirectivesSpec.scala
Expand Up @@ -22,9 +22,13 @@ trait DirectivesSpec extends Specification with unfiltered.specs2.Hosted {

import dispatch._, Defaults._

// it's simple to define your own directives
// create a directive for a particular content type
def contentType(tpe:String) =
when{ case RequestContentType(`tpe`) => } orElse UnsupportedMediaType
when { case RequestContentType(`tpe`) => } orElse UnsupportedMediaType

// create a directive for any content type
def someContentType =
when { case RequestContentType(t) => t } orElse UnsupportedMediaType

// enables `===` in awesome_json case
implicit val contentTypeAwesome =
Expand Down Expand Up @@ -76,6 +80,13 @@ trait DirectivesSpec extends Specification with unfiltered.specs2.Hosted {
_ <- Accepts.Json
r <- request[Any]
} yield Ok ~> JsonContent ~> ResponseBytes(Body.bytes(r))
case Seg(List("if_json", id)) =>
for {
_ <- POST
contentType <- someContentType if contentType == "application/json"
_ <- Accepts.Json
r <- request[Any]
} yield Ok ~> JsonContent ~> ResponseBytes(Body.bytes(r))
case Seg(List("awesome_json", id)) =>
for {
_ <- POST
Expand Down Expand Up @@ -184,6 +195,28 @@ trait DirectivesSpec extends Specification with unfiltered.specs2.Hosted {
resp().getStatusCode must_== 415
}
}
"Directives if filtering" should {
"respond with json if accepted" in {
val resp = Http(localhost / "if_json" / "123"
<:< Map("Accept" -> "application/json")
<:< Map("Content-Type" -> "application/json")
<< someJson OK as.String)
resp() must_== someJson
}
"respond with unsupported media if content-type wrong" in {
val resp = Http(localhost / "if_json" / "123"
<:< Map("Accept" -> "application/json")
<:< Map("Content-Type" -> "text/plain")
<< someJson)
resp().getStatusCode must_== 415
}
"respond with unsupported media if content-type missing" in {
val resp = Http(localhost / "if_json" / "123"
<:< Map("Accept" -> "application/json")
<< someJson)
resp().getStatusCode must_== 415
}
}
"Directive parameters" should {
"respond with parameter if accepted" in {
val resp = Http(localhost / "valid_parameters"
Expand Down

0 comments on commit 9ed6a7f

Please sign in to comment.