Hi,
Versions
"org.reactivemongo" %% "reactivemongo" % "0.12.5",
"org.reactivemongo" %% "play2-reactivemongo" % "0.12.5-play-25"
When doing a
findAndUpdate() which causes an exception (i.e. unique key exception), a
JsResultException is thrown instead of a
reactivemongo.core.errors.ReactiveMongoException.
This happens when using Play-Json to interact with the driver.
Reproduction1. Insert a single document
{ a: 1 } into a clean Mongo collection.
2. Run the following code:
import play.api.libs.concurrent.Execution.Implicits._
import play.api.libs.json.Json
import reactivemongo.api.MongoDriver
import reactivemongo.play.json.ImplicitBSONHandlers._
import reactivemongo.play.json.collection.JSONCollection
import scala.concurrent.Await
import scala.concurrent.duration.Duration
object Main {
def main(args: Array[String]): Unit = {
val driver = new MongoDriver()
val conn = driver.connection(List("localhost:27017"))
val db = Await.result(conn.database("test"), Duration.Inf)
val col = db.collection[JSONCollection]("col")
val f = col.findAndUpdate(
Json.obj("b" -> 100),
Json.obj("$setOnInsert" -> Json.obj("a" -> 1)),
upsert = true
)
Await.result(f, Duration.Inf)
}
}
3. You'll get a stack trace which says you got a
JsResultException.
Exception in thread "main" play.api.libs.json.JsResultException: JsResultException(errors:List((,List(ValidationError(List(CommandError[code=11000, errmsg=E11000 duplicate key error collection: test.col index: a_1 dup key: { : 1 }, doc: {"ok":0,"errmsg":"E11000 duplicate key error collection: test.col index: a_1 dup key: { : 1 }","code":11000}]),WrappedArray())))))
at reactivemongo.play.json.JSONSerializationPack$.deserialize(package.scala:729)
at reactivemongo.play.json.JSONSerializationPack$.deserialize(package.scala:703)
at reactivemongo.api.SerializationPack$class.readAndDeserialize(serializationpack.scala:30)
at reactivemongo.play.json.JSONSerializationPack$.readAndDeserialize(package.scala:703)
at reactivemongo.api.commands.Command$$anon$2$$anonfun$one$2.apply(commands.scala:165)
at reactivemongo.api.commands.Command$$anon$2$$anonfun$one$2.apply(commands.scala:164)
...
...
This is wrong, since
JsResultException is dedicated to JSON serialization exceptions, and errors such as
11000 should be a
ReactiveMongoException. Moreover, the
JsResultException contains a complicated structure of
ValidationErrors and sequences, and down multiple nested structures, you'll find a string error which contains
"...code=11000" that you'll have to match on. So you're forced to write something like this:
col.findAndUpdate(
Json.obj("b" -> 100),
Json.obj("$setOnInsert" -> Json.obj("a" -> 1)),
upsert = true
) recover {
case JsResultException((_, ValidationError(s :: _) :: _) :: _) if s matches ".*code=11000[^\\w\\d].*" =>
println("Matched case")
}
I investigated the callstack, and found that there's a piece of code which examines the response, and if the response from Mongo contains an
ok field which is not
1, then it wraps the response as
JsError with a complicated string (see
here). The calling code, matches the
JsError in pattern matching and throws a
JsResultException (see
here).
I suspect it's a bug which went unnoticed. I doubt the maintainers ever intended that a JsResultException would be thrown in such cases. If it's intended, please advise how to pattern match on such errors in a clean way, without regex matching.
Thanks