I met a problem that I cannot solve these days. I'm doing multiple http requests and in each response, it should have a Array[DTAnnotation]. I want to accumulate all the resulting list into one (this is not the problem here). My problem is that I cannot return the results from a WSResponse. What I try :
import mymodel.{DTFeatures, DTResponse, DTRequest, DTAnnotations}
def checkForSpike
(
awsKey : String,
collection : JSONCollection,
record : Record,
features : Array[DTFeatures]
) : Unit = {
val url = config.getString("url").getOrElse
val holder = WS.url(url)
val complexHolder =
holder.withHeaders(("Content-Type","application/json"))
// excepting result is List[Array[DTAnnotations]]
val test : List[Array[DTAnnotations]] =
for(feature <- features) yield {
val dtFeature = Json.stringify(Json.toJson(DTRequest(feature)))
val futureResponse = complexHolder.post(dtFeature)
Logger.info("Make the HTTP POST Request in " + (t1 - t0) + " msecs")
futureResponse.map { response =>
Logger.info("Get response in " + (System.currentTimeMillis - t1))
if(response.status == 200) {
response.json.validate[DTResponse].map { dtResponse =>
// I want to return this
dtResponse.annotations
}.recoverTotal { _ =>
Logger.debug("invalid json")
}
} else {
Logger.debug(Json.prettyPrint(Json.obj("status" -> response.status, "body" -> response.body)))
}
}
Await.result(futureResponse, 10.seconds)
}
}
Because the response is a Future, I try to add a Await to get the annotations but I have one error at the typing phase :
[error] found : Array[play.api.libs.ws.WSResponse]
[error] required: List[Array[DTAnnotation]]
How I can fix this ? Thank you !
There are a number of errors that avoid this to work. I add a version that compiles with your expected type and if you have questions I will answer in the comments.
def checkForSpike
(
awsKey: String,
collection: JSONCollection,
record: Record,
features: Array[DTFeatures]
): Unit = {
val url = config.getString("url").getOrElse
val holder = WS.url(url)
val complexHolder =
holder.withHeaders(("Content-Type", "application/json"))
// excepting result is List[Array[DTAnnotations]]
val test: List[Array[DTAnnotations]] =
for (feature <- features.toList) yield {
val dtFeature = Json.stringify(Json.toJson(DTRequest(feature)))
val futureResponse = complexHolder.post(dtFeature)
val futureAnnotations: Future[Array[DTAnnotations]] = futureResponse.map { response =>
if (response.status == 200) {
response.json.validate[DTResponse].map { dtResponse =>
// I want to return this
dtResponse.annotations
}.recoverTotal { _ =>
Logger.debug("invalid json")
??? // An Array response should be expected, maybe empty
}
} else {
Logger.debug(Json.prettyPrint(Json.obj("status" -> response.status, "body" -> response.body)))
??? // An Array response should be expected, maybe empty
}
}
Await.result(futureAnnotations, 10.seconds)
}
}
Issues:
Features must be converted to List if you expect a list to be
returned by the for comprehension
The map on future response returns
another future and this value should be used in the Await
To make sure the type of futureAnnotations is correct in all the branches the type should be valid
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
I have this ugly massive piece of code. It does many things, mostly around querying databases. Each query depends on the result of the previous query or on the parameters of the incoming request body. Queries are done asynchronously using Future. The code works but is not readable. How can I restructure it to make it concise? I have thought of using for instead of map and flatMap but due to dependencies on previous queries, I am not able to figure out how to use result of previous Future in new ones and how to handle error paths.
I am using play 2.6 and scala 2.12.2
def oldSignupUser:Action[AnyContent] = silhouette.UserAwareAction.async {
implicit request => {
val body: AnyContent = request.body
val jsonBody: Option[JsValue] = body.asJson
jsonBody match {
case Some(json) => {
val userProfile: Option[UserProfile] = json.asOpt[UserProfile] userProfile match {
case Some(profile) => {
val loginInfo = LoginInfo(CredentialsProvider.ID, profile.externalProfileDetails.email)
val userKeys = UserKeys(utilities.bucketIDFromEmail(profile.externalProfileDetails.email),profile.externalProfileDetails.email,loginInfo,profile.externalProfileDetails.firstName,profile.externalProfileDetails.lastName)
val findUserFuture: Future[Option[User]] = userRepo.findOne(userKeys) // userFuture will eventually contain the result of database query i.e Some(user) or None
findUserFuture.flatMap { (userOption: Option[User]) =>
userOption match {
case Some(user) => {
if(user.profile.internalProfileDetails.get.confirmed) {
Future {
Ok(Json.toJson(JsonResultError(messagesApi("error.duplicateUser")(langs.availables(0)))))
}
} else {
val userKeys = UserKeys(utilities.bucketIDFromEmail(user.profile.externalProfileDetails.email),user.profile.externalProfileDetails.email,loginInfo,user.profile.externalProfileDetails.firstName,user.profile.externalProfileDetails.lastName)
val userToken:UserToken = utilities.createUserToken(user.id,userKeys,UserTokenType.RegistrationConfirmation)
val userTokenSaveFuture:Future[Option[UserToken]] = userTokenRepo.save(userToken)
logger.trace(s"user token save future ${userTokenSaveFuture}")
userTokenSaveFuture.map( (userTokenOption:Option[UserToken])=>{
userTokenOption match {
case Some(userToken) => {
val signupEmailOption:Option[SignupEmail] = createEmailMessageForUserToken(userToken)
signupEmailOption match {
case Some(signupEmail:SignupEmail) =>{
val _:String = mailerService.sendEmail(signupEmail.subject, signupEmail.from,List(user.profile.externalProfileDetails.email),None,Some(signupEmail.html))
Ok(Json.toJson(JsonResultSuccess(messagesApi("success.userSignupConfirmationEmailSent")(langs.availables(0)))))
}
case None =>{
InternalServerError(Json.toJson(JsonResultError("Internal Server Error")))
}
}
}
case None => {
Ok(Json.toJson(JsonResultError("user not added"))) //Todom - this is misleading as user is added but token isn't
}
}
})
}
}
case None => {
val passwordInfo:PasswordInfo = userRepo.hashPassword(profile.externalProfileDetails.password.get)
val bucketId = utilities.bucketIDFromEmail(profile.externalProfileDetails.email)
val newUser:User = User(
utilities.getUniqueID(),//UUID.randomUUID(),
UserProfile(Some(InternalUserProfile(loginInfo,bucketId,false,Some(passwordInfo))),
profile.externalProfileDetails))
val saveUserFuture:Future[Option[User]] = userRepo.save(newUser)
saveUserFuture.flatMap { (userOption:Option[User]) =>{ userOption match {
case Some(user) => {
val initialPortfolio = user.profile.externalProfileDetails.portfolio
val profileAndPortfolio = profile.externalProfileDetails.copy(portfolio = initialPortfolio)
logger.trace(s"saving external profile and portfolio ${profileAndPortfolio}")
val savedProfileAndPortfolioOptionFuture = userProfileAndPortfolioRepo.save(profileAndPortfolio)
savedProfileAndPortfolioOptionFuture.flatMap(profileAndPortfolioOption =>{
profileAndPortfolioOption match {
case Some(profileAndPortfolio) => {
val userKeys = UserKeys(utilities.bucketIDFromEmail(user.profile.externalProfileDetails.email),user.profile.externalProfileDetails.email,loginInfo,user.profile.externalProfileDetails.firstName,user.profile.externalProfileDetails.lastName)
val userToken:UserToken = utilities.createUserToken(user.id,userKeys,UserTokenType.RegistrationConfirmation)
val userTokenSaveFuture:Future[Option[UserToken]] = userTokenRepo.save(userToken)
userTokenSaveFuture.flatMap( (userTokenOption:Option[UserToken])=>{
userTokenOption match {
case Some(userToken) => {
val signupEmailOption = createEmailMessageForUserToken(userToken)
signupEmailOption match {
case Some(signupEmail) =>{
val _:String = mailerService.sendEmail(signupEmail.subject,signupEmail.from,List(user.profile.externalProfileDetails.email),None,Some(signupEmail.html))
Future{Ok(Json.toJson(JsonResultSuccess((messagesApi("success.userSignupConfirmationEmailSent"))(langs.availables(0)))))}
}
case None =>{
Future{InternalServerError(Json.toJson(JsonResultError("Internal Server Error")))}
}
}
}
case None => {
Future{Ok(Json.toJson(JsonResultError("user not added"))) }
}
}
})
}
case None =>{
Future{InternalServerError(Json.toJson(JsonResultError("Internal Server Error")))}
}
}
})
}
case None => {
Future{Ok(Json.toJson(JsonResultError("unable to add user")))}
}
}
}
}
.recover { case x => {
x match {
case _:EmailException =>InternalServerError(Json.toJson(JsonResultError("The server encountered internal error and couldn't sent email to the email id.")))
case _ => InternalServerError(Json.toJson(JsonResultError("Internal Server Error")))
}
}
}
}
}
}
.recover { case x => {
logger.trace("Future failed in signupUser. In recover. Returning Internal Server Error"+x)
x match {
case _:EmailException =>InternalServerError(Json.toJson(JsonResultError("The server encountered internal error and couldn't sent email to the email id.")))
case _ => InternalServerError(Json.toJson(JsonResultError("Internal Server Error")))
}
}
}
}
case None => Future {
logger.trace("invalid profile structure")
Ok(Json.toJson(JsonResultError(messagesApi("error.incorrectBodyStructure")(langs.availables(0))))) }
}
}
case None => Future {
Ok(Json.toJson(JsonResultError(messagesApi("error.incorrectBodyType")(langs.availables(0))))) }
}
}
}
Update - the question was closed before I could solve the problem. Many thanks to mfirry and tim for their answers. this is the version I could come up with which I believe is modular.
def getUserProfileFromBody(json:JsValue): Option[UserProfile] ={
val userProfile = json.asOpt[UserProfile] //check if json conforms with UserProfile structure
userProfile
}
def getJsonBody(body:AnyContent) = {
val jsonBody: Option[JsValue] = body.asJson
jsonBody
}
def generateUserKeysFromUserProfile(profile:UserProfile):UserKeys = {
val loginInfo = LoginInfo(/*provider id eg "credentials"*/CredentialsProvider.ID, /*provider Key eg email*/profile.externalProfileDetails.email)
val userKeys = UserKeys(utilities.bucketIDFromEmail(profile.externalProfileDetails.email),profile.externalProfileDetails.email,loginInfo,profile.externalProfileDetails.firstName,profile.externalProfileDetails.lastName)
logger.trace(s"generated userkey ${userKeys}")
userKeys
}
def findIfUserIsNewOrExisting(userKey:UserKeys): Future[Boolean] ={
logger.trace(s"looking for user with keys ${userKey}")
val findUserFuture = userRepo.findOne(userKey) // userFuture will eventually contain the result of database query i.e Some(user) or None
logger.trace(s"user future is ${findUserFuture}")
for(userOption <- findUserFuture) yield {
logger.trace(s"user option ${userOption}")
userOption match {
case Some(user) => {
val userConfirmed = isUserConfirmed(user)
if(userConfirmed) {
throw new DuplicateUserException(user,"duplicateuser", new Throwable("duplicateuser"))
} else throw new UnconfirmedUserException(user,"unconfirmeduser", new Throwable("unconfirmeduser"))
}
case None => {
false
}
}
}
}
def returnError(error:String) = {
println(s"returning error ${error}")
Future {
/*
note the distintion between langs and messages. Lang means languages this application supports
eg English, French
Messages are the messages defined per language. Eg app.title is a message defined inn English. It might not be defined in French
*/
/*logger.trace("langs array"+langs.availables) //languages available
logger.trace("app.title: "+messagesApi.isDefinedAt("app.title")(langs.availables(0)))//pick the first language and see taht app.title is defined in it
logger.trace("error: "+messagesApi.isDefinedAt("error.incorrectBodyType")(langs.availables(0)))//see taht error.incorrectBodyType is defined in the 1st language
*/
Ok(Json.toJson(JsonResultError(error)))
}/*TODOM - Standardise error messages. Use as constants*/
}
def isUserConfirmed(user:User):Boolean = user.profile.internalProfileDetails.get.confirmed
def sendConfirmationTokenForUser(user:User) = {
val userKeys = generateUserKeysFromUserProfile(user.profile)
//for this user, create a token which could be sent in the email for verification
val userToken:UserToken = utilities.createUserToken(user.id,userKeys,UserTokenType.RegistrationConfirmation)
logger.trace(s"saving token ${userToken}")
val userTokenSaveFuture:Future[Option[UserToken]] = userTokenRepo.save(userToken)
logger.trace(s"user token save future ${userTokenSaveFuture}")
for(userTokenOption <- userTokenSaveFuture) yield {
logger.trace(s"user token ${userTokenOption}")
userTokenOption match {
case Some(userToken) => {
val signupEmailOption:Option[SignupEmail] = createEmailMessageForUserToken(userToken)
signupEmailOption match {
case Some(signupEmail:SignupEmail) =>{
val email = mailerService.sendEmail(signupEmail.subject, signupEmail.from,List(user.profile.externalProfileDetails.email),None,Some(signupEmail.html))
println(s"sent email message ${email}")
email
//Ok(Json.toJson(JsonResultSuccess(messagesApi("success.userSignupConfirmationEmailSent")(langs.availables(0)))))
}
case None =>{
logger.trace("unable to create html response for email confirmation")
//InternalServerError(Json.toJson(JsonResultError("Internal Server Error")))
throw EmailMessageCreationException("errorInCreatingHTML", new Throwable("errorInCreatingHTML"))
}
}
}
case None => {
logger.trace("error in adding token")
throw EmailTokenSaveException("emailTokenSaveException", new Throwable("emailTokenSaveException"))
//Ok(Json.toJson(JsonResultError("user not added"))) //Todom - this is misleading as user is added but token isn't
}
}
}
}
def addUserToDatabase(user:User) = {
println(s"saving user ${user}")
val saveUserFuture:Future[Option[User]] = userRepo.save(user)
for(userOption <- saveUserFuture) yield {
userOption match {
case Some(user) => {
logger.trace("user added successfully "+user)
user
}
case None => throw new NewUserAdditionException("newUserAdditionError", new Throwable("newUserAdditionError"))
}
}
}
def addNewUser(profile:UserProfile) = {
logger.trace(s"new user sign up request with profile ${profile}")
////NOTE - //salt is empty for BCryptSha256PasswordHasher. The 'hash' method of BCryptSha256PasswordHasher does not return the salt separately because it is embedded in the hashed password.
//should creation of passwordInfo be moved to UserRepo? Not sure.
//A profile associated with the credentials provider stores a Silhouette PasswordInfo object holding the hashed password
//val passwordInfo:PasswordInfo = userRepo.passwordHasher.hash(profile.externalProfileDetails.password.get)
val passwordInfo: PasswordInfo = userRepo.hashPassword(profile.externalProfileDetails.password.get)
//logger.trace("password info is ",passwordInfo)
val bucketId = utilities.bucketIDFromEmail(profile.externalProfileDetails.email)
val loginInfo = LoginInfo(/*provider id eg "credentials"*/ CredentialsProvider.ID, /*provider Key eg email*/ profile.externalProfileDetails.email)
val newUser: User = User(
utilities.getUniqueID(), //UUID.randomUUID(),
UserProfile(Some(InternalUserProfile(loginInfo, bucketId, false, Some(passwordInfo))),
profile.externalProfileDetails))
logger.trace("adding new user" + newUser)
addUserToDatabase(newUser)
}
def saveProfileAndPortfolio(profileAndPortfolio:ExternalUserProfile) = {
logger.trace(s"saving external profile and portfolio ${profileAndPortfolio}")
val savedProfileAndPortfolioOptionFuture = userProfileAndPortfolioRepo.save(profileAndPortfolio)
for(savedProfileAndPortfolio <- savedProfileAndPortfolioOptionFuture) yield {
savedProfileAndPortfolio match {
case Some(profileAndPortfolio) => profileAndPortfolio
case None => throw ProfileAndPortfolioAdditionException("profileAndPortfolioAdditionException",new Throwable("profileAndPortfolioAdditionException"))
}
}
}
def createUserProfileAndPortfolioInformation(user:User) = {
val profile = user.profile
val initialPortfolio = user.profile.externalProfileDetails.portfolio //Some(TagsOfInterestToAUserAPI(Set(),Set(),Set()))
val profileAndPortfolio = profile.externalProfileDetails.copy(portfolio = initialPortfolio)
saveProfileAndPortfolio(profileAndPortfolio)
}
def signupUser = silhouette.UserAwareAction.async {
implicit request => {
logger.trace(s"received request ${request}")
val jsonBody = getJsonBody(request.body)
/*
TODOM - testcase - check what happens if some other body type is sent.
*/
jsonBody match {
case Some(json) => { //got json in message body.
//TODOM - convert to pretty print only if logger level is trace
val readableString: String = Json.prettyPrint(json)
logger.trace(s"received Json ${readableString}")
val userProfile = getUserProfileFromBody(json)
userProfile match {
case Some(profile) => { //json conforms to UserProfile.
logger.trace(s"received correct profile structure ${profile}")
val userKeys = generateUserKeysFromUserProfile(profile)
val res = for{isNewUser <- findIfUserIsNewOrExisting(userKeys) //this will throw error if user is duplicate
newUserDetails <- addNewUser(profile)
profileAndPortfolioInfo <- createUserProfileAndPortfolioInformation(newUserDetails)
confirmationEmail <- sendConfirmationTokenForUser(newUserDetails)} yield {
Ok(Json.toJson(JsonResultSuccess((messagesApi("success.userSignupConfirmationEmailSent"))(langs.availables(0)))))
}
res.recover {
case exception: DuplicateUserException => Ok(Json.toJson(JsonResultError(messagesApi("error.duplicateUser")(langs.availables(0)))))
case exception: UnconfirmedUserException => {
sendConfirmationTokenForUser(exception.user)
Ok(Json.toJson(JsonResultSuccess((messagesApi("success.userSignupConfirmationEmailSent"))(langs.availables(0)))))
}
case _:EmailException =>InternalServerError(Json.toJson(JsonResultError("The server encountered internal error and couldn't sent email to the email id.")))
case exception:ProfileAndPortfolioAdditionException => Ok(Json.toJson(JsonResultError("user not added")))
case _:EmailMessageCreationException => Ok(Json.toJson(JsonResultError("user not added")))
case _: EmailTokenSaveException=> Ok(Json.toJson(JsonResultError("user not added")))
case x => {
logger.error(s"unknown exception ${x}")
InternalServerError(Json.toJson(JsonResultError("Internal Server Error")))
}
}
res
}
//Json doesn't conform to UserProfile
case None => Future {
logger.trace("invalid profile structure")
Ok(Json.toJson(JsonResultError(messagesApi("error.incorrectBodyStructure")(langs.availables(0))))) } /*TODOM - Standardise error messages. Use as constants*/
}
}
//message body is not json. Error.
//TODOM - langs contains array of lang. picking the 1st one but would need too pick based on locale.
//langs.availables(0) maps to array defined in application.conf eg. - play.i18n.langs = [ "en", "en-US", "fr" ]
case None =>{
logger.trace("incorrect body type")
returnError(messagesApi("error.incorrectBodyType")(langs.availables(0)))
}
}
}
}
The most obvious change is to put code in functions. By breaking up the logic into functions with meaningful names you make the code much clearer to read. If you define them as local functions within the method they can access context from the method without it having to be passed in as parameters.
Put all the error returns in static vals outside the method rather than creating them each time.
Consider using Option.fold rather than match:
option.fold(error){ result =>
// Further code
}
Remove the {} after case x =>, they are not required.
The first case in case x => { x match { is redundant.
And don't worry about performance until you can prove that the performance of this section of code has a material effect on the behaviour of the application.
One problem is that you are not using the Failure case of Future to indicate failure so you can't use map/flatMap to chain operations that might fail.
Create your own subclass of Exception and have the methods return Future.failed(MyException(error)) on failure rather than Future(error)
Then do this
val res =
for {
res1 <- futureAction1
res2 <- futureAction2(res1)
res3 <- futureAction3(res2)
} yield {
res3
}
and finally
res.recover{ case MyException(err) => err }
The for will stop at the first Failure return value, and the recover will turn this into the appropriate Success value.
I am quite new with functional programming in scala and play-framework
I have an API request that need to add a chapter to a comic book authored by that particular user, thus I have to verify that the comic exist and belongs to that user. What complicates is the Future, Option etc and I can't make sure that the last command is executed last. Indeed, my problem occurs because the last command is executed before all my db access is completed
Here are the code, hope it is clear
def addchapter(): Action[AnyContent] = Action.async { implicit request =>
var hashMap: HashMap[String, JsValue] = HashMap()
var apiResult: ApiResult = ApiResult(
ReturnCode.COMIC_ADDCHAPTER.id,
ReturnCode.COMIC_ADDCHAPTER.toString(),
ReturnResult.RESULT_ERROR.toString(),
"init"
)
var fHashMap: Future[HashMap[String, JsValue]] = Future {
hashMap
}
try {
val oReq = request.body.asJson
val jsReq = oReq.getOrElse(JsString("null"))
val sessionid = (jsReq \
"sessionid").getOrElse(JsString("0")).as[String]
val comicid = (jsReq \
"comicid").getOrElse(JsString("comicid")).as[String]
val foUser = userRepo.getUserBySessionId(sessionid)
LogManager.DebugLog(this, "add chapter: " + sessionid + " => " +
comicid)
fHashMap = foUser.flatMap( oUser => {
oUser match {
case Some(user) => {
val foComic = comicRepo.getComicByComicId(comicid)
fHashMap = foComic.flatMap( oComic => {
oComic match {
case Some(comic) => {
LogManager.DebugLog(this, "about to add chapter")
val fTup = comicRepo.addChapterToComic(comic)
fHashMap = fTup.map( tuple => {
val wr = tuple._1
val mc = tuple._2
apiResult = ApiResult(
ReturnCode.COMIC_ADDCHAPTER.id,
ReturnCode.COMIC_ADDCHAPTER.toString(),
ReturnResult.RESULT_ERROR.toString(),
"successfully added chapter!"
)
val payloadcomic =
PayloadComicFactory.createWithComic(mc)
hashMap("apiresult") = Json.toJson(apiResult)
hashMap += "comic" -> Json.toJson(payloadcomic)
LogManager.DebugLog(this, "successfully added
chapter!")
hashMap
})
// return
fHashMap
}
case None => {
apiResult = ApiResult(
ReturnCode.COMIC_ADDCHAPTER.id,
ReturnCode.COMIC_ADDCHAPTER.toString(),
ReturnResult.RESULT_ERROR.toString(),
"comic not found"
)
hashMap("apiresult") = Json.toJson(apiResult)
Future { hashMap }
}
}
})
Future { hashMap }
}
case None => {
apiResult = ApiResult(
ReturnCode.COMIC_ADDCHAPTER.id,
ReturnCode.COMIC_ADDCHAPTER.toString(),
ReturnResult.RESULT_ERROR.toString(),
"unauthorized to add chapter to this comic"
)
hashMap("apiresult") = Json.toJson(apiResult)
Future { hashMap }
}
}
})
// I cannot put the return here, it is compile error saying that the return value is a Unit
// fHashMap.map( hashmap => {
// Ok(Json.toJson(hashmap))
// })
} catch {
case t: Throwable => {
LogManager.DebugException(this, "ex: ", t)
// I cannot put it here, it is compile error saying that the return value is Unit
// fHashMap.map( hashmap => {
// Ok(Json.toJson(hashmap))
// })
fHashMap
}
}
// I have to return here, but "return of the request" will be executed first before all my db access is completed, thus the ApiResult is still returning wrong state
LogManager.DebugLog(this, "return of the request")
fHashMap.map( hashmap => {
Ok(Json.toJson(hashmap))
})
}
This Action.async method in Play for Scala should execute the second future only if the first future returns 1, that's why they are nested. If the first future returns a result different than 1 then the second future should never be executed. But I'm getting the following compilation error in f2.map. Why is this error and how to fix it?
type mismatch; found :
scala.concurrent.Future[scala.concurrent.Future[play.api.mvc.Result]]
required: play.api.mvc.Result
def index = Action.async { request =>
val f1 = Future {1}
f1.map {
access => if (access==1) {
val f2 = Future {2}
f2.map { // <-- compilation error
result => {
val json = JsObject(Seq(
"acc" -> JsNumber(access),
"ret" -> JsString("0")
))
Ok(json)
}
}
}
else {
val json = JsObject(Seq(
"acc" -> JsNumber(0),
"ret" -> JsString("0")
))
Future.successful(Ok(json))
}
}
}
You're basically there - just flatMap on f1 instead of map since you're creating another future.
I have a function which will take a Token via ajax request. It will return a response a list of directories while the token is valid.
def all = Action.async(parse.json) {
implicit request => tokenForm.bind(request.body).fold(
formWithErrors => Future.successful(BadRequest(formWithErrors.toString)),
form => checkToken(form.token).map(token => {
val directories = Directories.all.map(directory => {
Json.toJson(directory)
})
Ok(Json.obj("status" -> {if (token.get.id.getOrElse(0) >= 1) true else false}, "message" -> {if (token.get.id.getOrElse(0) >= 1) Json.toJson(directories) else "Invalid token"}))
})
)
}
While I run the above code It says [As ^ sign indicate the position the error found]
No Json serializer found for type scala.concurrent.Future[play.api.libs.json.JsValue]. Try to implement an implicit Writes or Format for this type.
Ok(Json.obj("status" -> {if (token.get.id.getOrElse(0) >= 1) true else false}, "message" -> {if (token.get.id.getOrElse(0) >= 1) Json.toJson(directories) else "Invalid token"}))
^
Here is what I come up with
def all = Action.async(parse.json) {
implicit request => tokenForm.bind(request.body).fold(
formWithErrors => Future.successful(BadRequest(formWithErrors.toString)),
form => for {
(token, directories) <- checkToken(form.token) zip Directories.all
} yield {
val isTokenValid = token.isDefined
val responseBody = Json.obj(
"status" -> isTokenValid,
"message" -> {
if (isTokenValid)
Json.toJson(directories)
else
"Invalid token"
}
)
Ok(responseBody)
}
)
}
asuming checkToken and Directories.all returns Future.
Your function needs to return Future[Result]. When you are folding, the first branch is correct
formWithErrors => Future.successful(BadRequest(formWithErrors.toString))
However in the second branch it seems like you started a new Future by calling Directories.all, and you want to serialize it and return as json. There is no serializer for Future[JsValue] as this makes no sense, it would have to block to get result. You need to somehow get to the values of both checkToken(form.token) and Directories.all.
You can do this as I did above, or for example like this:
form => {
val futureToken = checkToken(form.token)
val futureDirectories = Directories.all
for {
token <- futureToken
directories <- futureDirectories
} yield {
// same code as above
}
}
Notice that if you would inline futureToken and futureDirectories they would be executed serially.
Also note that you are converting your directory list to json twice.
Once here
val directories = Directories.all.map(directory => {
Json.toJson(directory)
})
Asuming Directories.all returns Future[List[Directory]], then when you use map, the function you pass operates on List[Directory] so the variable should be named directories not directory. It will work, play easly converts list of anything convertible to json, no need to do it manually.
Second time you did it here
Json.toJson(directories)
I have 3 futures of type Response. The first future returns a JsValue which defines if future 2 and future 3 shall be executed or only future 3 shall be executed.
Pseudocode:
If Future 1 then {future2 and future 3}
else future 3
Iam trying to do this in a play framwork action which means in order to use the result of the futures later I cant use onSuccess, onFailure and onComplete because all of them return Unit and not the actual JsValue from the last future.
I tried to do this with map() and andThen but I am a Scala noob and I guess i wasn't able to do it because I always just missed a little point. Here is my current approach which does not work!
def addSuggestion(indexName: String, suggestion: String) = Action.async {
val indexExistsQuery: IndexExistsQuery = new IndexExistsQuery(indexName);
val addSuggestionQuery: AddSuggestionQuery = new AddSuggestionQuery(indexName, suggestion)
val indexCreationQuery: CreateCompletionsFieldQuery = new CreateCompletionsFieldQuery(indexName)
val futureResponse: Future[Response] = for {
responseOne <- EsClient.execute(indexExistsQuery)
responseTwo <- EsClient.execute(indexCreationQuery) if (indexExistsQuery.getBooleanResult(indexExistsQuery.getResult(responseOne)))
responseThree <- EsClient.execute(addSuggestionQuery)
} yield responseThree
futureResponse.map { response =>
Ok("Feed title: " + (response.json))
}
I created some pseudocode:
checkIndexExists() map {
case true => Future.successful()
case false => createIndex()
} flatMap { _ =>
query()
}.map { response =>
Ok("Feed title: " + (response.json))
}.recover {
case _ => Ok("bla")
}
First you fire up the query if the index exists.
Then you map that future how to work with that Future[Boolean] if it successful. Since you use map, you kind of extract the Boolean. If the index exists, you just create a future that is already complete. If the index not exists you need to fire up the index creation command. Now you have the situation that you have nested Future's (Future[Future[Response]]). Using flatMap you remove one dimension, so that you only have Future[Response]. That can be mapped to a Play result.
Update (the implementation of MeiSign):
EsClient.execute(indexExistsQuery) map { response =>
if (indexExistsQuery.getBooleanResult(indexExistsQuery.getResult(response))) Future.successful(Response)
else EsClient.execute(indexCreationQuery)
} flatMap { _ =>
EsClient.execute(addSuggestionQuery)
} map { response: Response =>
Ok("Feed title: " + (response.json))
}
I found this solution but I dont think that it is a good solution because Iam using Await.result() which is a blocking operation.
If anyone knows how to refactor this code without blocking operations please let me know.
def addSuggestion(indexName: String, suggestion: String) = Action.async {
val indexExistsQuery: IndexExistsQuery = new IndexExistsQuery(indexName);
val addSuggestionQuery: AddSuggestionQuery = new AddSuggestionQuery(indexName, suggestion)
val indexCreationQuery: CreateCompletionsFieldQuery = new CreateCompletionsFieldQuery(indexName)
val indexExists: Boolean = indexExistsQuery.getBooleanResult(indexExistsQuery.getResult(Await.result(EsClient.execute(indexExistsQuery), 5.second)))
if (indexExists) {
EsClient.execute(addSuggestionQuery).map { response => Ok("Feed title: " + (response.json)) }
} else {
val futureResponse: Future[Response] = for {
responseTwo <- EsClient.execute(indexCreationQuery)
responseThree <- EsClient.execute(addSuggestionQuery)
} yield responseThree
futureResponse.map { response =>
{
Ok("Feed title: " + (response.json))
}
}.recover { case _ => Ok("bla") }
}
}