Skip to content

Commit

Permalink
Merge pull request #258 from unfiltered/directive_filter
Browse files Browse the repository at this point in the history
Support ad-hoc filtering of directives
  • Loading branch information
softprops committed Sep 9, 2014
2 parents c7c18e6 + 82f3cc0 commit c82d508
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 15 deletions.
39 changes: 27 additions & 12 deletions directives/src/main/scala/directives/Directive.scala
Expand Up @@ -70,24 +70,24 @@ extends (HttpRequest[T] => Result[R, A]) {
def flatMap[TT <: T, RR >: R, B](f:A => Directive[TT, RR, B]):Directive[TT, RR, B] =
Directive(r => run(r).flatMap(a => f(a)(r)))

/** Doesn't filter. Scala requires something to be defined for pattern matching in for
expressions, and we do use that. */
def withFilter(f:A => Boolean): Directive[T, R, A] = this
/** Doesn't filter. Scala requires something to be defined for pattern matching in for
expressions, and we do use that. */
def filter(f:A => Boolean): Directive[T, R, A] = this

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)

def and[TT <: T, E, B, RF](other: => Directive[TT, JoiningResponseFunction[E,RF], B])
(implicit ev: R <:< JoiningResponseFunction[E,RF]) =
new Directive[TT, JoiningResponseFunction[E,RF], (A,B)] ( req =>
this(req) and other(req)
)
(implicit ev: R <:< JoiningResponseFunction[E,RF]) = {
val runner = (req: HttpRequest[TT]) => this(req) and other(req)
// A `filter` implementation is required for pattern matching, which we
// 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 FilterDirective[TT, JoiningResponseFunction[E,RF], (A,B)](
runner,
runner
)
}

def &[TT <: T, E, B, RF](other: => Directive[TT, JoiningResponseFunction[E,RF], B])
(implicit ev: R <:< JoiningResponseFunction[E,RF]) = this and other
Expand All @@ -98,6 +98,21 @@ extends (HttpRequest[T] => Result[R, A]) {
}
}

/** Supports filtering by requiring a handler for when success is filtered away.
The onEmpty handler may produce a success or failure; typically the latter. */
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 FilterDirective({ req =>
run(req).flatMap { a =>
if (filt(a)) Result.Success(a)
else onEmpty(req)
}
}, onEmpty)
}

class JoiningResponseFunction[E,A]
(val elements: List[E], toResponseFunction: Seq[E] => ResponseFunction[A])
extends ResponseFunction[A] {
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 c82d508

Please sign in to comment.