Permalink
Browse files

[reactivemongo] Typeclasses WriteResponseMaker, WriteResponse and ass…

…ociated handler types.
  • Loading branch information...
cchantep
cchantep committed Sep 13, 2014
1 parent 95e6646 commit 04355e9b1ae25c53960c49b53e842c40d8028d7a
View
@@ -108,17 +108,17 @@ request match {
}
```
### Result creation
### Result creation for queries
Mongo result to be returned by query handler, can be created as following:
```scala
import reactivemongo.bson.BSONDocument
import reactivemongo.core.protocol.Resonse
import reactivemongo.core.protocol.Response
import acolyte.reactivemongo.QueryResponse
val error1: Option[Try[Response]] = QueryResponse.failed("Error #1")
val error2 = QueryResponse("Error #2") // equivalent
val error2 = QueryResponse("Error #1") // equivalent
val success1 = QueryResponse(BSONDocument("name" -> "singleResult"))
val success2 = QueryResponse.successful(BSONDocument("name" -> "singleResult"))
@@ -137,6 +137,29 @@ val undefined1 = QueryResponse(None)
val undefined2 = QueryResponse.empty
```
### Result creation for write operation
Mongo result to be returned by write handler, can be created as following:
```scala
import reactivemongo.core.protocol.Response
import acolyte.reactivemongo.WriteResponse
val error1: Option[Try[Response]] = WriteResponse.failed("Error #1")
val error2 = WriteResponse("Error #1") // equivalent
val error3 = WriteResponse("Error #2" -> Some(1)/* code */)
val success1 = WriteResponse(true/* updatedExisting */)
val success2 = WriteResponse.successful(true) // equivalent
```
When a handler supports some write cases, but not other, it can return an undefined response, to let the chance other handlers would manage it.
```scala
val undefined1 = WriteResponse(None)
val undefined2 = WriteResponse.empty
```
## Build
This module can be built from these sources using SBT (0.12.2+),
@@ -48,12 +48,27 @@ private[reactivemongo] class Actor(
r @ CheckedWriteRequest(op, doc, GetLastError(_, _, _, _)))
val req = Request(op.fullCollectionName, doc.merged)
val chanId = r()._1.channelIdHint getOrElse 1
println(s"oper = ${MongoDB.WriteOp(op)}, chan = $chanId, ${req.body.elements.toList}")
// op = Insert(0,test-db.a-col)
val exp = new ExpectingResponse(msg)
val cid = r()._1.channelIdHint getOrElse 1
println(s"chan = $cid, ${req.body.elements.toList}")
val resp = MongoDB.WriteOp(op).fold({
MongoDB.WriteError(cid, s"No write operator: $msg") match {
case Success(err) err
case _ MongoDB.MkWriteError(cid)
}
}) { wop
handler.writeHandler(cid, wop, req).
fold(NoWriteResponse(cid, msg.toString))(_ match {
case Success(r) r
case Failure(e) MongoDB.WriteError(cid, Option(e.getMessage).
getOrElse(e.getClass.getName)) match {
case Success(err) err
case _ MongoDB.MkWriteError(cid)
}
})
}
/*
val xxp = scala.concurrent.Promise[Response]() completeWith {
@@ -80,7 +95,8 @@ private[reactivemongo] class Actor(
// Error:
//exp.promise.success(MongoDB.WriteError(chan, "Err_1").get)
exp.promise.success(NoWriteResponse(chanId, msg.toString))
//exp.promise.success(NoWriteResponse(chanId, msg.toString))
exp.promise.success(resp)
case msg @ RequestMakerExpectingResponse(RequestMaker(
op @ RQuery(_ /*flags*/ , coln, off, len), doc, _ /*pref*/ , chanId))
@@ -10,43 +10,57 @@ sealed trait ConnectionHandler { self ⇒
/** Query handler */
def queryHandler: QueryHandler
/** Write handler */
def writeHandler: WriteHandler
/**
* Creates a copy of this connection handler,
* with given query `handler` appended.
*
* @param handler Query handler
*/
def withQueryHandler[T](handler: T)(implicit f: T QueryHandler) = {
new ConnectionHandler {
override val queryHandler = new QueryHandler {
override def apply(cid: Int, q: Request) =
self.queryHandler(cid, q).orElse(f(handler)(cid, q))
}
}
}
def withQueryHandler[T](handler: T)(implicit f: T QueryHandler) =
ConnectionHandler(new QueryHandler {
def apply(cid: Int, q: Request) =
self.queryHandler(cid, q).orElse(f(handler)(cid, q))
}, writeHandler)
/**
* Creates a copy of this connection handler,
* with given write `handler` appended.
*
* @param handler Write handler
*/
def withWriteHandler[T](handler: T)(implicit f: T WriteHandler) =
ConnectionHandler(queryHandler, new WriteHandler {
def apply(cid: Int, op: WriteOp, w: Request) =
self.writeHandler(cid, op, w).orElse(f(handler)(cid, op, w))
})
}
/** Companion object for connection handler. */
object ConnectionHandler {
/**
* @param handler Query handler
* Creates connection handler using given query and write handlers.
*
* @param q Query handler
* @param w Write handler
*
* {{{
* import acolyte.reactivemongo.ConnectionHandler
*
* ConnectionHandler(myQueryHandler)
* ConnectionHandler(myQueryHandler, myWriteHandler)
* }}}
*/
def apply[A](handler: A)(implicit f: A QueryHandler): ConnectionHandler =
new ConnectionHandler {
override val queryHandler = f(handler)
}
def apply[A, B](q: A = QueryHandler.empty, h: B = WriteHandler.empty)(implicit f: A QueryHandler, g: B WriteHandler): ConnectionHandler = new ConnectionHandler {
val queryHandler = f(q)
val writeHandler = g(h)
}
/**
* Empty connection handler, not handling any query or write request.
*/
lazy val empty: ConnectionHandler = apply(QueryHandler.empty)
lazy val empty: ConnectionHandler = apply()
}
/** Query handler. */
@@ -72,7 +86,7 @@ object QueryHandler {
* import acolyte.reactivemongo.{ Request, QueryHandler }
*
* val handler1: QueryHandler = // Returns a successful empty response
* (q: Request) => Some(Seq.empty[BSONDocument])
* (q: Request) => QueryResponse(Seq.empty[BSONDocument])
*
* }}}
*/
@@ -98,3 +112,30 @@ sealed trait WriteHandler
override def apply(channelId: Int, op: WriteOp, req: Request): Option[Try[Response]]
}
/** Write handler companion. */
object WriteHandler {
import scala.language.implicitConversions
/**
* Creates a write handler from given function `f`.
*
* @param f Handling function, with arguments channel ID and write request.
*
* {{{
* import acolyte.reactivemongo.{ Request, WriteHandler, WriteOp }
*
* val handler1: WriteHandler = // Returns a successful empty response
* (w: (WriteOp, Request)) => WriteResponse(false)
*
* }}}
*/
implicit def SimpleWriteHandler(f: (WriteOp, Request) PreparedResponse): WriteHandler = new WriteHandler {
def apply(chanId: Int, op: WriteOp, w: Request): Option[Try[Response]] = f(op, w)(chanId)
}
/**
* Empty query handler, not handling any request.
*/
lazy val empty = SimpleWriteHandler((_, _) WriteResponse(None))
}
@@ -5,7 +5,7 @@ import scala.util.Try
import _root_.reactivemongo.bson.BSONDocument
import _root_.reactivemongo.core.protocol.Response
/** Query response companion. */
/** Query response factory. */
object QueryResponse {
/** Creates a response for given `body`. */
def apply[T](body: T)(implicit mkResponse: QueryResponseMaker[T]): PreparedResponse = new PreparedResponse {
@@ -19,8 +19,8 @@ object QueryResponse {
def successful(result: BSONDocument*) = apply(result)
/**
* Empty/undefined response, returned by handler no supporting a specific
* query that may be handled by others.
* Empty/undefined response, returned by handler no supporting
* a specific query that may be handled by others.
*/
lazy val empty = apply(None)
}
@@ -0,0 +1,35 @@
package acolyte.reactivemongo
import scala.util.Try
import _root_.reactivemongo.bson.BSONDocument
import _root_.reactivemongo.core.protocol.Response
/** Write response factory. */
object WriteResponse {
/** Creates a response for given `body`. */
def apply[T](body: T)(implicit mkResponse: WriteResponseMaker[T]): PreparedResponse = new PreparedResponse {
def apply(chanId: Int) = mkResponse(chanId, body)
}
/**
* Named factory for error response.
*
* @param message Error message
* @param code Optional error code
*/
def failed(message: String, code: Option[Int] = None) = apply(message -> code)
/**
* Factory for successful response.
*
* @param updatedExisting Some existing document has been updated
*/
def successful(updatedExisting: Boolean = false) = apply(updatedExisting)
/**
* Empty/undefined response, returned by handler no supporting
* a specific write operation that may be handled by others.
*/
lazy val empty = apply(None)
}
@@ -0,0 +1,61 @@
package acolyte.reactivemongo
import scala.util.Try
import reactivemongo.bson.BSONDocument
import reactivemongo.core.protocol.Response
/**
* Creates a write response for given channel ID and result.
* @tparam T Result type
*/
trait WriteResponseMaker[T] extends ((Int, T) Option[Try[Response]]) {
/**
* @param channelId ID of Mongo channel
* @param result Optional result to be wrapped into response
*/
override def apply(channelId: Int, result: T): Option[Try[Response]]
}
/** Response maker companion. */
object WriteResponseMaker {
/**
* {{{
* import acolyte.reactivemongo.WriteResponseMaker
*
* val maker = implicitly[WriteResponseMaker[Boolean]]
* }}}
*/
implicit def SuccessWriteResponseMaker = new WriteResponseMaker[Boolean] {
override def apply(channelId: Int, updatedExisting: Boolean): Option[Try[Response]] = Some(MongoDB.WriteSuccess(channelId, updatedExisting))
}
/**
* Provides response maker for an error.
*
* {{{
* import acolyte.reactivemongo.WriteResponseMaker
*
* val maker = implicitly[WriteResponseMaker[(String, Option[Int])]]
* }}}
*/
implicit def ErrorWriteResponseMaker = new WriteResponseMaker[(String, Option[Int])] {
override def apply(channelId: Int, error: (String, Option[Int])): Option[Try[Response]] = Some(MongoDB.WriteError(channelId, error._1, error._2))
}
/**
* Provides response maker for handler not supporting
* specific write operation.
*
* {{{
* import acolyte.reactivemongo.WriteResponseMaker
*
* val maker = implicitly[WriteResponseMaker[None.type]]
* val response = maker(1, None)
* }}}
*/
implicit def UndefinedWriteResponseMaker = new WriteResponseMaker[None.type] {
/** @return None */
override def apply(channelId: Int, undefined: None.type): Option[Try[Response]] = None
}
}
@@ -34,6 +34,45 @@ object ResponseMakerSpec
shapeless.test.illTyped("implicitly[QueryResponseMaker[Any]]")
}
"Write response maker" should {
"be working for boolean (updatedExisting)" in {
val makr = implicitly[WriteResponseMaker[Boolean]]
makr(4, true) aka "response" must beSome.which { prepared
zip(prepared, MongoDB.WriteSuccess(4, true)).
aka("maker") must beSuccessfulTry.like {
case (a, b) a.documents aka "response" must_== b.documents
}
}
}
"be working for an error (String, None)" in {
val makr = implicitly[WriteResponseMaker[(String, Option[Int])]]
makr(5, "Custom error #1" -> None) aka "response" must beSome.
which { prepared
zip(prepared, MongoDB.WriteError(5, "Custom error #1", None)).
aka("maker") must beSuccessfulTry.like {
case (a, b) a.documents aka "response" must_== b.documents
}
}
}
"be working for an error (String, Some(Int))" in {
val makr = implicitly[WriteResponseMaker[(String, Option[Int])]]
makr(5, "Custom error #2" -> Some(7)) aka "response" must beSome.
which { prepared
zip(prepared, MongoDB.WriteError(5, "Custom error #2", Some(7))).
aka("maker") must beSuccessfulTry.like {
case (a, b) a.documents aka "response" must_== b.documents
}
}
}
shapeless.test.illTyped("implicitly[WriteResponseMaker[Any]]")
}
@inline def zip[A, B](a: Try[A], b: Try[B]): Try[(A, B)] =
for { x a; y b } yield (x -> y)
}

0 comments on commit 04355e9

Please sign in to comment.