0

A bit new to Scala (latest), Reactivemongo (latest) and Play framework (2.3.x).

Validating sessions, and on success, loading the user object for it returning it as json. Problem is specific to mapping and futures I guess. The following code won't compile:

def userCheck(token: String) = Action.async {
  val futureSession = sessionCollection.find(Json.obj("token" -> token)).one[BSONDocument]

  futureSession.map {
    case Some(session) => {
      userCollection.find(Json.obj("_id" -> session.getAs[String]("userId").get )).one[JsObject].map {
        case Some(user) => Ok(JsObject(Seq("user" -> user)))
        case None => NotFound( JsObject(Seq("message" -> JsString("not found"))) )
      }
    }
    // NotFound( JsObject(Seq("message" -> JsString("not found"))) )
    case None => NotFound( JsObject(Seq("message" -> JsString("not found"))) )
  }
}

So, the error message is:

[error] AuthController.scala:186: overloaded method value async with alternatives:
[error]   [A](bodyParser: play.api.mvc.BodyParser[A])(block: play.api.mvc.Request[A] => scala.concurrent.Future[play.api.mvc.Result])play.api.mvc.Action[A] <and>
[error]   (block: play.api.mvc.Request[play.api.mvc.AnyContent] => scala.concurrent.Future[play.api.mvc.Result])play.api.mvc.Action[play.api.mvc.AnyContent] <and>
[error]   (block: => scala.concurrent.Future[play.api.mvc.Result])play.api.mvc.Action[play.api.mvc.AnyContent]
[error]  cannot be applied to (scala.concurrent.Future[Object])
[error]   def userCheck(token: String) = Action.async {

If you look at the source code you see a commented line, if I uncomment it, the code compiles. Am I missing to cover some cases?

UPDATE 1:

So, using the tips from the accepted answer, I came up with this:

def userCheck(token: String) = Action.async {
  getSession(token).flatMap {
    session => {
      session match {
        case Some(s) => {
          getUser( s.getAs[String]("userId").get ).flatMap {
            user => {
              user match {
                case Some(u) => Future.successful( Ok( u ) )
                case None => Future.successful( NotFound( JsObject(Seq( "m" -> JsString("User not found")))))
              }
            }
          }
        }
        case None => Future.successful( NotFound( JsObject(Seq( "m" -> JsString("Session not found")))))
      }
    }
  }
}

def getSession(token: String): Future[Option[BSONDocument]] = {
  sessionCollection.find( Json.obj("token" -> token) ).one[BSONDocument]
}

def getUser(id: String): Future[Option[JsObject]] = {
  userCollection.find( BSONDocument( "_id" -> BSONObjectID(id) ) ).one[JsObject]
}

...and it does the trick. Feels like refactoring is needed though.

Wrench
  • 4,070
  • 4
  • 34
  • 46

1 Answers1

2

Try adding explicit types to all the branches; in particular, make sure the block after case Some(Session) returns a Result or whatever you think it should be. I suspect in the Some branch you're ending up with a Future[Future[Result]] rather than a Future[Result] - maybe you need to do futureSession.flatMap, and in the None case return Future.successful(...).

lmm
  • 17,386
  • 3
  • 26
  • 37
  • I didn't get the double `Future` embedded problem you mentioned, but your post got me in the right direction of solving the problem. I have updated my post with my concrete solution. One of these days I'm going to google "scala map vs flatmap with futures". :) – Wrench Jan 07 '15 at 14:27