Compilation error for Future method [Option [BasicProfile]]
I am writing a 2.3 app for a game using a secure social library and an updated library with scala. Now I am trying to implement the UserService [T] property, but I am getting compile errors in the updatePasswordInfo method. This is the method:
def updatePasswordInfo(user: LoginUser,info: PasswordInfo): scala.concurrent.Future[Option[BasicProfile]] = {
implicit val passwordInfoFormat = Json.format[PasswordInfo]
//the document query
val query = Json.obj("providerId" -> user.providerId,
"userId" -> user.userId
)
//search if the user exists
val futureUser: Future[Option[LoginUser]] = UserServiceLogin.find(query).one
futureUser map {
case Some(x) => val newPassword = Json.obj("passswordInfo" -> info)// the new password
UserServiceLogin.update(query, newPassword) //update the document
val newDocument: Future[Option[LoginUser]] = UserServiceLogin.find(query).one
newDocument map {
case Some(x) => x
case None => None
} //return the new LoginUser
case None => None
}
}
And this is a compiler error:
/Users/alberto/git/recommendation-system/app/security/UserService.scala:203: type mismatch;
[error] found : scala.concurrent.Future[Product with Serializable]
[error] required: Option[securesocial.core.BasicProfile]
[error] newDocument map {
What's wrong?
source to share
If you really want the search to complete quickly (although it's not that useful) and then reload the updated user from the database, something like this should do without the need for scalaz:
def updatePasswordInfo(user: LoginUser,info: PasswordInfo): scala.concurrent.Future[Option[BasicProfile]] = {
implicit val passwordInfoFormat = Json.format[PasswordInfo]
//the document query
val query = Json.obj("providerId" -> user.providerId,
"userId" -> user.userId)
val newPassword = Json.obj("passswordInfo" -> info)
//update the document
for {
userO <- UserServiceLogin.find(query).one[BasicProfile] //fail fast (not sure this is really useful)
updatedUser<-UserServiceLogin.update(query, newPassword).map(_=>userO).recover{case _ =>None}
actualUser <- UserServiceLogin.find(query).one[BasicProfile]
} yield actualUser
}
source to share
If you type Future[A]
, you get Future[B]
where T
is the return type from the lambda you are passing to map
.
Since this lambda returns Future[B]
, in this case you will get Future[Future[B]]
one that does not match the expected type.
An easy fix is to use flatMap
that accepts a lambda going from A
to Future[B]
.
Also, you are returning Option[LoginUser]
, but the method is expected to return Option[BasicProfile]
. The compiler has inferred a common supertype, which is in this case Product with Serializable
, since they are both case classes.
Summarizing
scala.concurrent.Future[Product with Serializable]
^_____________________^^_________________________^
1 2
- use
flatMap
insteadmap
- return
BasicProfile
instead ofLoginUser
or change the return type of the method toFuture[Option[LoginUser]]
By the way, there is a lot of room for improvement, as you could use OptionT
scalaz's monad transformer to make things look nicer.
Here's an example
import scalaz._; import Scalaz._
val newPassword = Json.obj("passswordInfo" -> info)
(for {
// this is done only for failing fast in case the user doesn't exist
_ <- optionT(UserServiceLogin.find(query).one)
_ <- optionT(Future.successful(Some(UserServiceLogin.update(query, newPassword))))
updatedUser <- optionT(UserServiceLogin.find(query).one)
} yield updatedUser).run
By the way, this works on the assumption that it update
is a synchronous call, which may (and hopefully) not. If it returns Future[T]
, just change the code to
_ <- optionT(UserServiceLogin.update(query, newPassword).map(Some(_)))
or if it already returns Future[Option[T]]
_ <- optionT(UserServiceLogin.update(query, newPassword))
source to share
There are several ways to improve your code.
For example, you don't need to search for a user before running a query.
Also, it would be nice to check if your request was indeed successful (if the API allows it).
Third, I'm not sure how LoginUser corresponds to BasicProfile. Your code doesn't seem to be doing any conversion. If LoginUser is a subclass of BasicProfile, or can be passed to BasicProfile, you can try something like this:
def updatePasswordInfo(user: LoginUser,info: PasswordInfo): scala.concurrent.Future[Option[BasicProfile]] = {
implicit val passwordInfoFormat = Json.format[PasswordInfo]
//the document query
val query = Json.obj("providerId" -> user.providerId,
"userId" -> user.userId
)
UserServiceLogin.update(query, newPassword) //update the document
for {
user <- UserServiceLogin.find(query).one
} yield user.map(_.asInstanceOf[BasicProfile]) //return the new LoginUser
}
source to share