Quantcast

New query monad for Unfiltered...

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

New query monad for Unfiltered...

corruptmemory
For those interested I've been working on an alternate implementation of a query monad for Unfiltered here: https://github.com/corruptmemory/Unfiltered/tree/master/library/src/main/scala/monad

It's a Reader+Writer(Logging) monad the operates over Validations.  It's based on Scalaz but specialized for Unfiltered.  Example usage:


val storiesByState = unfiltered.netty.cycle.Planify {
  case req@GET(Path(pm("states"::State(state)::"stories"::Nil))) => {
    def body:RequestMonad[A,Result] = for {
      params <- getParams
      maxAge <- params.required[Days]("max_age") map (_.count) ifMissing 5.success check(is("max_age not in range:1-30"){x => x>=1 && x<=30})
      keyword <- params.optionalSeq[Int]("keyword").trim.filterEmpty
      query <- queryBuilder(state,keyword,maxAge)
      stories <- storyFinder(query)
    } yield stories
    body(req).over.fold(failure = {
      case x@AmbiguousLocation(_,_) => ResponseString(x.toString) ~> Ok
      case x@Missing(_) => ResponseString(x.toString) ~> BadRequest
      case x@Invalid(_,_) => ResponseString(x.toString) ~> BadRequest
      case x@Uncaught(_) => ResponseString(x.toString) ~> InternalServerError
      case x@_ => ResponseString(x.toString) ~> InternalServerError
    },
                        success = s => ResponseString(s.toString) ~> Ok)
  }
}

The result of the monadic computation is a ReaderLogger object that has a .over member that is a Scalaz validation and a .log member that contains logging messages from the monadic computation (using Scalaz's FingerTree).  For the .over member the success value can be of an type, the failure values are all derived from a RequestError trait (not sealed, so you can extend the error cases to your own types).  The computation can short-circuit on failure but using combinators such as orElse or ifMissing one can alter the handling of failure cases.

There are a bunch more combinators for logging values and such (basically lifted from Scalaz's Logger implementation).

Since the current implementation is based on Scalaz 6.0.x Scala 2.8.0 is a minimum compiler version to use this code.

I still have to write some test cases for the code, and things are in some flux as well, but input would be welcome on guiding this towards something that folks might find useful with Unfiltered.
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: New query monad for Unfiltered...

corruptmemory
HA!

Yeah, I know that my snippet isn't "real", but it contained a bit of example code I used to show someone at work that the ".trim" pimp was type safe (only applied to string-ish things)

So:

      keyword <- params.optionalSeq[Int]("keyword").trim.filterEmpty

should read:

      keyword <- params.optionalSeq[String]("keyword").trim.filterEmpty

The full example again:

val storiesByState = unfiltered.netty.cycle.Planify {
  case req@GET(Path(pm("states"::State(state)::"stories"::Nil))) => {
    def body:RequestMonad[A,Result] = for {
      params <- getParams
      maxAge <- params.required[Days]("max_age") map (_.count) ifMissing 5.success check(is("max_age not in range:1-30"){x => x>=1 && x<=30})
      keyword <- params.optionalSeq[String]("keyword").trim.filterEmpty
      query <- queryBuilder(state,keyword,maxAge)
      stories <- storyFinder(query)
    } yield stories
    body(req).over.fold(failure = {
      case x@AmbiguousLocation(_,_) => ResponseString(x.toString) ~> Ok
      case x@Missing(_) => ResponseString(x.toString) ~> BadRequest
      case x@Invalid(_,_) => ResponseString(x.toString) ~> BadRequest
      case x@Uncaught(_) => ResponseString(x.toString) ~> InternalServerError
      case x@_ => ResponseString(x.toString) ~> InternalServerError
    },
                        success = s => ResponseString(s.toString) ~> Ok)
  }
}
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: New query monad for Unfiltered...

n8han
Administrator
In reply to this post by corruptmemory
One thing is that 2.8.0+ is not an issue since we are planning on dropping 2.7.7 support in the next major version (unless anyone has an objection).

I'm glad you've contributed this now because it's unlikely that Unfiltered needs two parameter query monads, and we could make the switch if needed before anyone gets too attached to one. Alternatives could live in secondary modules but I think only one should be documented and recommended.

My primary aim with QParams was to not short-circuit on errors, by default. In my experience this is the most common case, the more errors you can report for a given request the better. The tradeoff for that is the extraneous ".get" required on parameter values used in the yield expression. It should always be a safe call, because QueryM#map doesn't evaluate its given function when a parameter error has been recorded. Still, it's a weird thing.

So if the alternate implementation solves this in a more palatable way (?), that's a big plus for me. And I see that it also can take validation functions inline, so that's good.

One other shortcoming of the current approach that was pointed out some time ago is that they can't be chained in the way you might hope:
http://databinder.3617998.n2.nabble.com/Chaining-QueryMs-td5845202.html

It's easy enough to hold on to individual validation expressions, but it's fairly resistant to reusing groups of them. I wonder if the alternate implementation is better in this sense? I'll certainly dig into it asap to see for myself, but the sbt 0.10 migration is sucking up my spare time these past few weeks.

Nathan

On 06/18/2011 11:16 PM, corruptmemory [via Databinder] wrote:
For those interested I've been working on an alternate implementation of a query monad for Unfiltered here: https://github.com/corruptmemory/Unfiltered/tree/master/library/src/main/scala/monad

It's a Reader+Writer(Logging) monad the operates over Validations.  It's based on Scalaz but specialized for Unfiltered.  Example usage:


val storiesByState = unfiltered.netty.cycle.Planify {
  case req@GET(Path(pm("states"::State(state)::"stories"::Nil))) => {
    def body:RequestMonad[A,Result] = for {
      params <- getParams
      maxAge <- params.required[Days]("max_age") map (_.count) ifMissing 5.success check(is("max_age not in range:1-30"){x => x>=1 && x<=30})
      keyword <- params.optionalSeq[Int]("keyword").trim.filterEmpty
      query <- queryBuilder(state,keyword,maxAge)
      stories <- storyFinder(query)
    } yield stories
    body(req).over.fold(failure = {
      case x@AmbiguousLocation(_,_) => ResponseString(x.toString) ~> Ok
      case x@Missing(_) => ResponseString(x.toString) ~> BadRequest
      case x@Invalid(_,_) => ResponseString(x.toString) ~> BadRequest
      case x@Uncaught(_) => ResponseString(x.toString) ~> InternalServerError
      case x@_ => ResponseString(x.toString) ~> InternalServerError
    },
                        success = s => ResponseString(s.toString) ~> Ok)
  }
}

The result of the monadic computation is a ReaderLogger object that has a .over member that is a Scalaz validation and a .log member that contains logging messages from the monadic computation (using Scalaz's FingerTree).  For the .over member the success value can be of an type, the failure values are all derived from a RequestError trait (not sealed, so you can extend the error cases to your own types).  The computation can short-circuit on failure but using combinators such as orElse or ifMissing one can alter the handling of failure cases.

There are a bunch more combinators for logging values and such (basically lifted from Scalaz's Logger implementation).

Since the current implementation is based on Scalaz 6.0.x Scala 2.8.0 is a minimum compiler version to use this code.

I still have to write some test cases for the code, and things are in some flux as well, but input would be welcome on guiding this towards something that folks might find useful with Unfiltered.


If you reply to this email, your message will be added to the discussion below:
http://databinder.3617998.n2.nabble.com/New-query-monad-for-Unfiltered-tp6492154p6492154.html
To start a new topic under Unfiltered, email [hidden email]
To unsubscribe from Unfiltered, click here.

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: New query monad for Unfiltered...

corruptmemory
Thanks for the follow-up, I will put together a more complete response to your points probably tomorrow. As a side note I did a quick-and-dirty port of Unfiltered to SBT 0.10 here:

https://github.com/corruptmemory/Unfiltered/blob/master/project/Build.scala

It's far from complete as it does not even begin to run the test cases and some of the other magic in the current SBT build, but if there is anything in there of use to you please feel free to take what you need.
Loading...