scalaplayframeworkplayframework-2.3reactivemongoplay-reactivemongo

The best way to convert Success into Failure depending on the value of Success


I use Play! Framework with ReactiveMongo plugin (versions 2.3 and 0.11 accordingly). And I have the next code which removes items from MongoDB collection and returns a number of affected items:

/**
  * Removes all subscribers by subscription id
  * 
  * @param subscriptionId id of the subscription
  * @return count of removed subscribers
  */  
def deleteAll(subscriptionId: String): Future[Int] = {
      logger.debug(s"Removing all subscribers with subscription id '$subscriptionId'...")
      val query = BSONDocument("subscriptionId" -> BSONObjectID(subscriptionId))
      subscribersCollection.remove(query) map { writeResult: WriteResult =>
        writeResult.n match {
          case subscribersRemoved if subscribersRemoved > 0 =>
            logger.debug(s"Successfully removed $subscribersRemoved subscribers with subscribtion id '$subscriptionId'")
          case _ => logger.debug(s"No subscribers with subscribtion id '$subscriptionId' were removed")
        }
        writeResult.n
      }
  }

Due to ReactiveMongo documentation, WriteResult class returned by collection's remove() method has fields such as hasError and writeErrors to indicate errors that occurred during the execution of the database query.

So what is the best and cleanest way to enhance my method in order to return a Failure from it depending on WriteResult's hasError field?

Here is rough example:

subscribersCollection.remove(query).??? match {
  case Success(writeResult) if writeResult.hasErrors => Failure(new DatabaseException(writeResult.errMsg, writeResult.code)
  case any => any
}

I.e. my method should return a Failure even if a database query returns a Success with error fields

Thanks in advance! I really would appreciate any help in order to make my code better

P.S. I have considered wrapping all of my code in Try and just throwing an exception on hasError flag set to true, but I believe that it can be done in a better way, maybe with Future's transform() method

P.P.S. For some reason code samples and demos from ReactiveMongo documentation do not handle WriteError's error fields and flags. In fact, the documentation says

If the write result actually indicates an error, the Future will be in a failed state

However, I've already seen such handling in production code in a couple of applications, so it is slightly confusing. Does it mean that this kind of handling is excessive?


Solution

  • Use flatMap and Future.failed

    do flatMap on the Future and then based on the value return Future.failed(new Exception("unexpected value"))

    Lets say we have a function which returns a future of some int

    def httpStatus: Future[Int] = Future { 404 }
    
    httpStatus.flatMap { 
     case 404 => //return failed future
      Future.failed(new Exception("Bad status"))
     case value => value
    }
    

    Now your code becomes

    def deleteAll(subscriptionId: String): Future[Int] = {
          logger.debug(s"Removing all subscribers with subscription id '$subscriptionId'...")
          val query = BSONDocument("subscriptionId" -> BSONObjectID(subscriptionId))
          subscribersCollection.remove(query) flatMap { writeResult: WriteResult =>
            writeResult.n match {
              case subscribersRemoved if subscribersRemoved > 0 =>
                logger.debug(s"Successfully removed $subscribersRemoved subscribers with subscribtion id '$subscriptionId'")
                Future.successful(writeResult.n)
              case status => 
                 logger.debug(s"No subscribers with subscribtion id '$subscriptionId' were removed")
                 Future.failed(new Exception(s"bad status exception. status: $status"))
            }
    
          }
      }