Async Action and Future - scala

I'm using Play 2.5 and Scala 2.11. I'm trying to use async Action with Future to return json string but can't get it to work.
Here is my action :
def getData = Action.async { implicit request =>
val futureData = Future { daoObj.fetchData }
futureData onComplete {
case Success(data) =>
Ok(Json.prettyPrint(Json.obj("data" -> data.groupBy(_.name))))
case Failure(t) =>
BadRequest(Json.prettyPrint(Json.obj(
"status" -> "400",
"message" -> s"$t"
)))
}
}
The daoObj.fetchData returns scala.concurrent.Future[List[models.Data]]
Here is the models.Data :
case class Data(name: String, details: String)
I can have data like this
Data (name = "data1", details = "details1")
Data (name = "data1", details = "details1a")
Data (name = "data2", details = "details2")
that I can join on name to return a structure of the form
Map(data1 -> List("details1", "details11"),
data2 -> List("details2"))
I have a compilation error on groupBy :
value groupBy is not a member of scala.concurrent.Future[List[models.Data]]
1) How to get the value (List[models.Data]) from the Future in my action ?
2) It's my first Play Scala app so if you have any comment to improve my code, it's welcome.

You should not use Future.onComplete, which is a callback (with side-effect), but .map and .recover to turn your Future[T] into a Future[Result] which is what's expected by Action.async.
def getData = Action.async { implicit request =>
Future { daoObj.fetchData }.map { data =>
Json.obj("data" -> data.groupBy(_.name))
}.recover {
case error => BadRequest(Json.obj(
"status" -> "400",
"message" -> error.getMessage
))
}
}
There is no need to pretty-print the JsObject which can be directly written as result (except for debugging purposes maybe).

Related

Nested futures in Play for Scala does not compile

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.

Scala - identifier expected but '=>' found

I'm writing an endpoint in a play scala application that makes a request to spotify, searching across track, album and artist types. I want to make over them and transform the string's into Future's of the calls.
This is my code:
def index = Action.async { implicit request =>
val futures = List("track", "album", "artist")
.map { type => performSearch("q" -> param(request, "q"), "type" -> type) }
Future.sequence(futures).onComplete {
Ok
}
}
private def performSearch(criteria: (String, String)): Future = {
ws.url("https://api.spotify.com/v1/search")
.withQueryString(criteria)
.get()
}
private def param(request: Request[AnyContent], name: String): String = {
request.queryString.get(name).flatMap(_.headOption).getOrElse("")
}
However I'm getting the error in my map of:
identifier expected but '=>' found
// .map { type => performSearch("q" -> param(request, "q"), "type" -> type) }
type is a keyword. Pick something else or put it inside `:
.map { `type` => performSearch("q" -> param(request, "q"), "type" -> `type`) }

How to get array from a json formatted form in Scala?

I am new to SCALA where I am going to develop an API with PLAY and SLICK.
I am going fetch an array (json formatted all the values are in integer) from a form via a web request like as follows:-
Request data:
{"ids": [1,2,3,4,5,6,7,8,9]}
Now I would like to fetch the form array data. Therefore I have declare a Form in my controller as:-
case class IdsForm(ids: Array[Int])
private val idsForm: Form[IdsForm] = Form(
mapping(
"ids" -> ????
)(IdsForm.apply)(IdsForm.unapply)
)
Now Would like to print all the elements in that array. Hence declare a function as follows:-
def getIds() = Action.async(parse.json) {
implicit request => idsForm.bind(request.body).fold(
formWithErrors => Future.successful(BadRequest(formWithErrors.toString)),
form => {
val ids = form.ids
// Print all the array elements
for ( x <- ids ) {
println( x )
}
val responseBody = Json.obj(
"status" -> 200,
"message" -> Json.toJson("Successfully printed")
)
Ok(responseBody)
}
)
}
Please let me know, what to put instead of "ids" -> ???? on my code. In case of single number I have putted "id" -> number. I don't know what to put instead of ???? in "ids" -> ????
You shouldn't use a form to submit JSON... Here you have example how to do it. Although, you could upload a JSON file if you want?
For example(I didn't test this), your data class:
case class MyData(ids: List[Int])
object MyData { implicit val myDataJsonFormat: Json.format[MyData] }
Your controller route:
def uploadJson = Action(parse.json) { request =>
val dataResult = request.body.validate[MyData]
dataResult.fold(
errors => { BadRequest(........) },
data => {
// do whatever with data....
Ok(....)
}
)
}
I use my code as:-
case class IdsForm(ids: Seq[Int])
private val idsForm: Form[IdsForm] = Form(
mapping(
"ids" -> seq[number]
)(IdsForm.apply)(IdsForm.unapply)
)
And the function is:-
def getIds() = Action.async(parse.json) {
implicit request => idsForm.bind(request.body).fold(
formWithErrors => Future.successful(BadRequest(formWithErrors.toString)),
form => {
val ids = form.ids
// Print all the array elements
for ( x <- 0 to ids.length - 1 ) {
println( ids(x) )
}
val responseBody = Json.obj(
"status" -> 200,
"message" -> Json.toJson("Successfully printed")
)
Ok(responseBody)
}
)
}
It works fine. Is it ok?

Extracting and accumulate result from multiple HTTP request

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

Optional Json Body Parser

I'm trying to write a DRY CRUD restful service using PlayFramework. Here's the code for it.
def crudUsers(operation: String) = Action(parse.json) { request =>
(request.body).asOpt[User].map { jsonUser =>
try {
DBGlobal.db.withTransaction {
val queryResult = operation match {
case "create" =>
UsersTable.forInsert.insertAll(jsonUser)
Json.generate(Map("status" -> "Success", "message" -> "Account inserted"))
case "update" =>
val updateQ = UsersTable.where(_.email === jsonUser.email.bind).map(_.forInsert)
println(updateQ.selectStatement)
updateQ.update(jsonUser)
Json.generate(Map("status" -> "Success", "message" -> "Account updated"))
case "retrieve" =>
val retrieveQ = for(r <- UsersTable) yield r
println(retrieveQ.selectStatement)
Json.generate(retrieveQ.list)
case "delete" =>
val deleteQ = UsersTable.where(_.email === jsonUser.email)
deleteQ.delete
Json.generate(Map("status" -> "Success", "message" -> "Account deleted"))
}
Ok(queryResult)
}
} catch {
case _ =>
val errMsg: String = operation + " error"
BadRequest(Json.generate(Map("status" -> "Error", "message" -> errMsg)))
}
}.getOrElse(BadRequest(Json.generate(Map("status" -> "Error", "message" -> "error"))))
}
}
I've noticed that update, delete and create operations work nicely. However the retrieve operation fails with For request 'GET /1/users' [Invalid Json]. I'm pretty sure this is because the JSON parser is not tolerant of a GET request with no JSON passed in the body.
Is there a way to special case the GET/Retrieve operation without losing losing the DRY approach I've started here?
My guess would be that you split your methods so that you can create a different routes for the ones with and without body.
It seems that the design of the code would not work even if the empty string was parsed as JSON. The map method would not be executed because there would be no user. That would cause the match on operation to never be executed.
Update
Since you mentioned DRY, I would refactor it into something like this:
type Operations = PartialFunction[String, String]
val operations: Operations = {
case "retrieve" =>
println("performing retrieve")
"retrieved"
case "list" =>
println("performing list")
"listed"
}
def userOperations(user: User): Operations = {
case "create" =>
println("actual create operation")
"created"
case "delete" =>
println("actual delete operation")
"updated"
case "update" =>
println("actual update operation")
"updated"
}
def withoutUser(operation: String) = Action {
execute(operation, operations andThen successResponse)
}
def withUser(operation: String) = Action(parse.json) { request =>
request.body.asOpt[User].map { user =>
execute(operation, userOperations(user) andThen successResponse)
}
.getOrElse {
errorResponse("invalid user data")
}
}
def execute(operation: String, actualOperation: PartialFunction[String, Result]) =
if (actualOperation isDefinedAt operation) {
try {
DBGlobal.db.withTransaction {
actualOperation(operation)
}
} catch {
case _ => errorResponse(operation + " error")
}
} else {
errorResponse(operation + " not found")
}
val successResponse = createResponse(Ok, "Success", _: String)
val errorResponse = createResponse(BadRequest, "Error", _: String)
def createResponse(httpStatus: Status, status: String, message: String): Result =
httpStatus(Json.toJson(Map("status" -> status, "message" -> message)))