Permalink
Browse files

[reactivemongo] Refactor Request pattern matching

  • Loading branch information...
cchantep
cchantep committed Sep 26, 2014
1 parent 8fe2ef4 commit d213d6df9c7c4f158f9699f1027c363ddbafbfc8
View
@@ -206,58 +206,59 @@ Pattern matching can be used in handler to dispatch result accordingly.
import reactivemongo.bson.{ BSONInteger, BSONString }
import acolyte.reactivemongo.{
CollectionName, QueryHandler, RequestBody, Property, &
CollectionName, QueryHandler, Request, Property, SimpleBody, &
}
val queryHandler = QueryHandler { queryRequest =>
queryRequest match {
case RequestBody("a-mongo-db.a-col-name", _) =>
case Request("a-mongo-db.a-col-name", _) =>
// Any request on collection "a-mongo-db.a-col-name"
resultA
case RequestBody(colNameOfAnyOther, _) => resultB // Any request
case Request(colNameOfAnyOther, _) => resultB // Any request
case RequestBody(colName, (k1, v1) :: (k2, v2) :: Nil) =>
case Request(colName, SimpleBody((k1, v1) :: (k2, v2) :: Nil)) =>
// Any request with exactly 2 BSON properties
resultC
case RequestBody("db.col", ("email", BSONString(v)) :: _) =>
case Request("db.col", SimpleBody(("email", BSONString(v)) :: _)) =>
// Request on db.col starting with email string property
resultD
case RequestBody("db.col", ("name", BSONString("eman")) :: _) =>
case Request("db.col", SimpleBody(("name", BSONString("eman")) :: _)) =>
// Request on db.col starting with an "name" string property,
// whose value is "eman"
resultE
case RequestBody(_, ("age": ValueDocument(
("$gt", BSONInteger(minAge)) :: Nil))) =>
case Request(_, SimpleBody(("age": ValueDocument(
("$gt", BSONInteger(minAge)) :: Nil)))) =>
// Request on any collection, with an "age" document as property,
// itself with exactly one integer "$gt" property
// e.g. `{ 'age': { '$gt', 10 } }`
resultF
case RequestBody("db.col", ~(Property("email"), BSONString(e))) =>
case Request("db.col", SimpleBody(~(Property("email"), BSONString(e)))) =>
// Request on db.col with an "email" string property,
// anywhere in properties (possible with others which are ignored there)
resultG
case RequestBody("db.col", ~(Property("name"), BSONString("eman"))) =>
case Request("db.col", SimpleBody(
~(Property("name"), BSONString("eman")))) =>
// Request on db.col with an "name" string property with "eman" as value,
// anywhere in properties (possibly with others which are ignored there).
resultH
case RequestBody(colName,
case Request(colName, SimpleBody(
~(Property("age"), BSONInteger(age)) &
~(Property("email"), BSONString(v))) =>
~(Property("email"), BSONString(v)))) =>
// Request on any collection, with an "age" integer property
// and an "email" string property, possibly not in this order.
resultI
case RequestBody(colName,
case Request(colName, SimpleBody(
~(Property("age"), ValueDocument(
~(Property("$gt"), BSONInteger(minAge)))) &
~(Property("email"), BSONString(email))) =>
~(Property("email"), BSONString(email)))) =>
// Request on any collection, with an "age" property with itself
// a operator property "$gt" having an integer value, and an "email"
// property (at the same level as age), without order constraint.
@@ -267,7 +268,7 @@ val queryHandler = QueryHandler { queryRequest =>
// Matching on count query
resultK
case RequestBody("col1", ("$in", ValueList(bsonA, bsonB)) :: Nil) =>
case Request("col1", SimpleBody(("$in", ValueList(bsonA, bsonB)) :: Nil)) =>
// Matching BSONArray using with $in operator
resultL
}
@@ -280,7 +281,8 @@ Without this plugin, such parameterized extractor need to be declared as stable
```scala
// With scalac plugin
request match {
case RequestBody("db.col", ~(Property("email"), BSONString(e))) => result
case Request("db.col", SimpleBody(
~(Property("email"), BSONString(e)))) => result
// ...
}
@@ -289,7 +291,7 @@ val EmailXtr = Property("email")
// has declare email extractor before, as stable identifier
request match {
case RequestBody("db.col", ~(EmailXtr, BSONString(e))) => result
case Request("db.col", SimpleBody(~(EmailXtr, BSONString(e)))) => result
// ...
}
```
@@ -301,7 +303,7 @@ import acolyte.reactivemongo.{ WriteHandler, DeleteOp, InsertOp, UpdateOp }
val handler = WriteHandler { (op, wreq) =>
(op, wreq) match {
case (DeleteOp, RequestBody("a-mongo-db.a-col-name", _)) => resultDelete
case (DeleteOp, Request("a-mongo-db.a-col-name", _)) => resultDelete
case (InsertOp, _) => resultInsert
case (UpdateOp, _) => resultUpdate
}
@@ -69,6 +69,8 @@ private[reactivemongo] class Actor(
case msg @ CheckedWriteRequestExResp(
r @ CheckedWriteRequest(op, doc, GetLastError(_, _, _, _)))
println(s"buf = ${new String(doc.merged.array)}")
val req = Request(op.fullCollectionName, doc.merged)
val exp = new ExpectingResponse(msg)
val cid = r()._1.channelIdHint getOrElse 1
@@ -22,7 +22,7 @@ trait Request {
/**
* Request body (BSON statement)
*/
def body: BSONDocument
def body: List[BSONDocument]
override lazy val toString = s"Request($collection, $body)"
}
@@ -37,7 +37,14 @@ object Request {
*/
def apply(name: String, buffer: ChannelBuffer): Request = new Request {
val collection = name
val body = BSONDocument.read(go(buffer))
val body = parse(go(buffer), Nil)
}
/** Parses body documents from prepared buffer. */
@annotation.tailrec
private def parse(buf: ReadableBuffer, body: List[BSONDocument]): List[BSONDocument] = if (buf.readable == 0) return body else {
val doc = BSONDocument.read(buf)
parse(buf, body :+ doc)
}
@annotation.tailrec
@@ -52,13 +59,68 @@ object Request {
go(chan, body.writeBytes(buff))
}
}
/**
* Request extractor.
*
* {{{
* import reactivemongo.bson.BSONString
* import acolyte.reactivemongo.Request
*
* request match {
* case Request("db.col", _) => // Any request on "db.col"
* resultA
*
* case Request(colName, SimpleBody((k1, v1) :: (k2, v2) :: Nil)) =>
* // Any request with exactly 2 BSON properties
* resultB
*
* case Request("db.col", SimpleBody(("email", BSONString(v)) :: _)) =>
* // Request on db.col starting with an "email" string property
* resultC
*
* case Request("db.col", SimpleBody(("name", BSONString("eman")) :: _)) =>
* // Request on db.col starting with an "name" string property,
* // whose value is "eman"
* resultD
*
* case Request(_, SimpleBody(("age": ValueDocument(
* ("\$gt", BSONInteger(minAge)) :: Nil)))) =>
* // Request on any collection, with an "age" document as property,
* // itself with exactly one integer "\$gt" property
* // e.g. `{ 'age': { '\$gt', 10 } }`
* resultE
* }
* }}}
*
* @return Collection name -> request body
* @see SimpleBody
* @see ValueDocument
* @see CountRequest
*/
def unapply(q: Request): Option[(String, List[BDoc])] =
Some(q.collection -> q.body.map(BDoc.apply))
}
/** BSONDocument wrapper for pattern matching */
case class BDoc(underlying: BSONDocument)
/**
* Body extractor for simple request, with only one document.
* If there are more than one document, matching just ignore extra ones.
*/
object SimpleBody {
/** @return BSON properties from the first document of the body. */
def unapply(body: List[BDoc]): Option[List[(String, BSONValue)]] =
body.headOption.map(_.underlying.elements.toList)
}
/**
* Extractor of properties for a document used a BSON value
* (when operator is used, e.g. `{ 'age': { '\$gt': 10 } }`).
*
* @see RequestBody
* @see Request
* @see Property
*/
object ValueDocument {
@@ -77,69 +139,24 @@ object ValueList {
}
/**
* Request body extractor.
*
* {{{
* import reactivemongo.bson.BSONString
* import acolyte.reactivemongo.RequestBody
*
* request match {
* case RequestBody("db.col", _) => // Any request on "db.col"
* resultA
*
* case RequestBody(colName, (k1, v1) :: (k2, v2) :: Nil) =>
* // Any request with exactly 2 BSON properties
* resultB
*
* case RequestBody("db.col", ("email", BSONString(v)) :: _) =>
* // Request on db.col starting with an "email" string property
* resultC
*
* case RequestBody("db.col", ("name", BSONString("eman")) :: _) =>
* // Request on db.col starting with an "name" string property,
* // whose value is "eman"
* resultD
*
* case RequestBody(_, ("age": ValueDocument(
* ("\$gt", BSONInteger(minAge)) :: Nil))) =>
* // Request on any collection, with an "age" document as property,
* // itself with exactly one integer "\$gt" property
* // e.g. `{ 'age': { '\$gt', 10 } }`
* resultE
* }
* }}}
*
* @see [[ValueDocument]]
* @see [[CountRequest]]
*/
object RequestBody {
/**
* @return Collection name -> request body (BSON properties)
*/
def unapply(q: Request): Option[(String, List[(String, BSONValue)])] =
Some(q.collection -> q.body.elements.toList)
}
/**
* Request extractor for Count command.
* @see [[RequestBody]]
* Body extractor for Count request.
* @see SimpleBody
*/
object CountRequest {
/**
* @return Collection name -> query body (count BSON properties)
*/
def unapply(q: Request): Option[(String, List[(String, BSONValue)])] =
q match {
case RequestBody(col, ("count", BSONString(_)) ::
("query", ValueDocument(query)) :: Nil) Some(col -> query)
case Request(col, SimpleBody(("count", BSONString(_)) ::
("query", ValueDocument(query)) :: Nil)) Some(col -> query)
case _ None
}
}
/**
* Meta-extractor, to combine extractor on BSON properties.
* @see RequestBody
* @see SimpleBody
* @see Property
*/
object & {
@@ -154,39 +171,40 @@ object & {
*
* {{{
* import reactivemongo.bson.{ BSONInteger, BSONString }
* import acolyte.reactivemongo.{ RequestBody, Property, & }
* import acolyte.reactivemongo.{ Request, SimpleBody, Property, & }
*
* val EmailXtr = Property("email") // Without scalac plugin
*
* request match {
* case RequestBody("db.col", ~(Property("email"), BSONString(e))) =>
* case Request("db.col", SimpleBody(~(Property("email"), BSONString(e)))) =>
* // Request on db.col with an "email" string property,
* // anywhere in properties (possibly with others which are ignored there),
* // with `e` bound to extracted string value.
* resultA
*
* case RequestBody("db.col", EmailXtr(BSONString(e))) =>
* case Request("db.col", SimpleBody(EmailXtr(BSONString(e)))) =>
* // Request on db.col with an "email" string property,
* // anywhere in properties (possibly with others which are ignored there),
* // with `e` bound to extracted string value.
* resultB // similar to case resultA without scalac plugin
*
* case RequestBody("db.col", ~(Property("name"), BSONString("eman"))) =>
* case Request("db.col", SimpleBody(
* ~(Property("name"), BSONString("eman")))) =>
* // Request on db.col with an "name" string property with "eman" as value,
* // anywhere in properties (possibly with others which are ignored there).
* resultC
*
* case RequestBody(colName,
* case Request(colName, SimpleBody(
* ~(Property("age"), BSONInteger(age)) &
* ~(Property("email"), BSONString(v))) =>
* ~(Property("email"), BSONString(v)))) =>
* // Request on any collection, with an "age" integer property
* // and an "email" string property, possibly not in this order.
* resultD
*
* case RequestBody(colName,
* case Request(colName, SimpleBody(
* ~(Property("age"), ValueDocument(
* ~(Property("\$gt"), BSONInteger(minAge)))) &
* ~(Property("email"), BSONString("demo@applicius.fr"))) =>
* ~(Property("email"), BSONString("demo@applicius.fr")))) =>
* // Request on any collection, with an "age" property with itself
* // a operator property "\$gt" having an integer value, and an "email"
* // property (at the same level as age), without order constraint.
@@ -114,9 +114,9 @@ trait ConnectionHandlerFixtures {
fixtures: QueryHandlerFixtures with WriteHandlerFixtures
lazy val chandler1 = ConnectionHandler(QueryHandler {
case RequestBody(col, _) if col.endsWith("test1")
case Request(col, _) if col.endsWith("test1")
QueryResponse(BSONDocument("b" -> 3))
case RequestBody(col, _) if col.endsWith("test2") QueryResponse(
case Request(col, _) if col.endsWith("test2") QueryResponse(
Seq(BSONDocument("d" -> 4.56d), BSONDocument("ef" -> "ghi")))
case q QueryResponse(None)
}, WriteHandler {
@@ -66,13 +66,13 @@ object QueryHandlerSpec extends org.specs2.mutable.Specification
"Mixed handler" should {
val handler = QueryHandler { q
q match {
case RequestBody("test1", _) QueryResponse.undefined
case RequestBody("test2", _) QueryResponse("Error #2")
case Request("test1", _) QueryResponse.undefined
case Request("test2", _) QueryResponse("Error #2")
case RequestBody("test3", _) QueryResponse(
case Request("test3", _) QueryResponse(
Seq(BSONDocument("prop" -> "A"), BSONDocument("a" -> 1)))
case RequestBody("test4", _)
case Request("test4", _)
QueryResponse.successful(BSONDocument("prop" -> "B"))
}
}
@@ -105,16 +105,16 @@ object QueryHandlerSpec extends org.specs2.mutable.Specification
trait QueryHandlerFixtures {
val query1 = new Request {
val collection = "test1"
val body = BSONDocument("filter" -> "valA")
val body = List(BSONDocument("filter" -> "valA"))
}
val query2 = new Request {
val collection = "test2"
val body = BSONDocument("filter" -> "valB")
val body = List(BSONDocument("filter" -> "valB"))
}
val query3 = new Request {
val collection = "test3"
val body = BSONDocument("filter" -> "valC")
val body = List(BSONDocument("filter" -> "valC"))
}
}
Oops, something went wrong.

0 comments on commit d213d6d

Please sign in to comment.