Search code examples
scalaakkasprayspray-client

Spray request - Complete request only after nested futures


New to spray and scala. Been struggling to get it right for couple of days now.

I am trying to merge facebook oauth2 login + user login details into the database in case the same user logs in by different ways(user/pass or fb login).

Pasting below spray routing snippet.

  path("facebook") {
    post{
      entity(as[JObject]) { json =>
        val fb: FacebookAuthModel = json.extract[FacebookAuthModel]
        complete {

          //Get user details from fb oauth2
          val fbUser = fbAuth.getIdentity(fb) match {
            case Right(user: User) => user
            case Left(error: Failure) => throw new FailureException(error)
          }

          //Check if user is already present either by fb id or email
          val userFuture = userRepo(FetchUserByFacebook(fbUser.facebook.get,fbUser.email))

          userFuture.map {
            case u: User => {

              //user present but fb id not attached yet
              if (u.facebook.isEmpty) {
                //update fb id for the user - fire to actor and forget, i.e no callback to sender
                userRepo(UpdateFacebookId(u.id.get, fbUser.facebook.get))
              }

              //complete request with a token - request(1)
              AuthToken(token=jwt.createToken(u))
            }
            case None => {

              //first time user using fb login
              userRepo(CreateUser(fbUser)).map {

                //complete request with the token - request(2)
                case createdUser: User => AuthToken(token=jwt.createToken(createdUser))

                case None => throw new FailureException(Failure("Not able to CreateUser", FailureType.Unauthorized))
              }
            }
          }
        }  
      }
    }
  } 

Everything works fine except in case of first time user using fb login (refer request(2)).
Request gets completed with empty response before the nest future could complete.

I tried flatMapping the result from userFuture and then using onComplete on it to give the appropriate response, but it din't work.

Any idea how I could successfully complete the request(request(2)) with the token?


Solution

  • If one of the two branches in your code execution path could result in a Future, then you have to code to this as the lowest common denominator when dealing with userFuture. That means flatMap on userFuture and using Future.successful in the case where you don't have an explicit second Future to deal with. Something along this line:

    def handleUserResult(a:Any):Future[AuthToken] = a match{
      case u:User =>              
        if (u.facebook.isEmpty) {                
          userRepo(UpdateFacebookId(u.id.get, fbUser.facebook.get))
        }              
        Future.successful(AuthToken(token=jwt.createToken(u)))
    
      case None =>              
        userRepo(CreateUser(fbUser)).map {
          case createdUser: User =>
            AuthToken(token=jwt.createToken(createdUser))
    
          case None => 
            throw new FailureException(Failure("Not able to CreateUser", FailureType.Unauthorized))
        }
    }
    

    Once you define that method, you can use it on userResult as follows:

    userResult.flatMap(handleUserResult)
    

    I didn't check this code for compilation issues. I was more trying to show the general approach of flatMap used to handle two cases, one that produces another second Future and one that does not.