JsResultException thrown when doing findAndUpdate

53 views
Skip to first unread message

Ori Popowski

unread,
Aug 4, 2017, 5:56:44 PM8/4/17
to ReactiveMongo - http://reactivemongo.org
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.

Reproduction
1. 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

Cédric Chantepie

unread,
Aug 4, 2017, 6:46:48 PM8/4/17
to ReactiveMongo - http://reactivemongo.org
As said previously it's not a bug. Pattern matcher for CommandError should be ok in such case.

Ori Popowski

unread,
Aug 5, 2017, 3:24:52 AM8/5/17
to ReactiveMongo - http://reactivemongo.org
Hi,

Thank you for you support and patience the last few days.

Maybe I wasn't clear enough:

The JsResultException happens only when using a JSONCollection + reactivemongo.play.json.ImplicitBSONHandlers + Play-Json

So the following code will work:

import play.api.libs.concurrent.Execution.Implicits._
import reactivemongo.api.MongoDriver
import reactivemongo.api.collections.bson.BSONCollection
import reactivemongo.bson.BSONDocument
import scala.concurrent.Await
import scala.concurrent.duration.Duration

object Main2 {

 
def main(args: Array[String]): Unit = {

    val db
= getDB()
    val col
= db.collection[BSONCollection]("col")

    val f
= col.findAndUpdate(
     
BSONDocument("b" -> 100),
     
BSONDocument("$setOnInsert" -> BSONDocument("a" -> 1)),

      upsert
= true
   
) recover {

     
case reactivemongo.api.commands.CommandError.Code(11000) => println("good") // will ALWAYS  match
   
}

   
Await.result(f, Duration.Inf)
 
}
}


But the following code won't work:

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 db
= getDB()

    val col
= db.collection[JSONCollection]("col")

    val f
= col.findAndUpdate(
     
Json.obj("b" -> 100),
     
Json.obj("$setOnInsert" -> Json.obj("a" -> 1)),
      upsert
= true

   
) recover {
     
case reactivemongo.api.commands.CommandError.Code(11000) => println("good") // will NEVER match
   
}

   
Await.result(f, Duration.Inf)
 
}
}


As you can see, pattern matcher for CommandError is not okay for the Json case, but only for the BSON case.

How do we solve this issue?

or.wol...@vatbox.com

unread,
Aug 6, 2017, 4:43:07 AM8/6/17
to ReactiveMongo - http://reactivemongo.org
I am currently experiencing the same issue myself,

Reactive Mongo provides a few useful exceptions and they are not used with the plugin.
It seems like that there is a compatibility issue with the plugin and reactive mongo.

The plugin should provide Play-Json<-->Mongo portability and at the same time support reactive mongo interface and follow its documentation rules.

IMHO, this is definitely a bug and the workarounds we need to do are pretty dangerous
  
Thanks.

Cédric Chantepie

unread,
Aug 6, 2017, 7:02:05 PM8/6/17
to ReactiveMongo - http://reactivemongo.org
Improving JSON support for errors already supported by the core driver is understandable. To be constructive, that's a feature request rather than a bug.

Cédric Chantepie

unread,
Aug 7, 2017, 11:11:18 AM8/7/17
to ReactiveMongo - http://reactivemongo.org
You can try CommandError.{ Code, Message } matcher using 0.12.6-{playVariant}-SNAPSHOT (tested at https://github.com/ReactiveMongo/ReactiveMongo-Play-Json/blob/master/src/test/scala/JSONCollectionSpec.scala#L241 ).

Ori Popowski

unread,
Aug 7, 2017, 3:16:37 PM8/7/17
to reacti...@googlegroups.com
This is awesome!
Thank you very much

On Aug 7, 2017 18:11, "Cédric Chantepie" <chantep...@gmail.com> wrote:
You can try CommandError.{ Code, Message } matcher using 0.12.6-{playVariant}-SNAPSHOT (tested at https://github.com/ReactiveMongo/ReactiveMongo-Play-Json/blob/master/src/test/scala/JSONCollectionSpec.scala#L241 ).

--
You received this message because you are subscribed to a topic in the Google Groups "ReactiveMongo - http://reactivemongo.org" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/reactivemongo/0vIVvi-T4jA/unsubscribe.
To unsubscribe from this group and all its topics, send an email to reactivemongo+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Reply all
Reply to author
Forward
0 new messages