Asynchronous wait for database-value in Playframework (2.4-M3) and Slick (3.0.0-RC3) - scala

I'd like to keep my application as asynchronously as possible. Now I have this repository:
object LanguageRepository extends LanguageRepositoryTrait
{
private val languages = TableQuery[Languages]
private def db:Database = Database.forDataSource(DB.getDataSource())
private def filterQuery(id: Long): Query[Languages, Language, Seq] = languages.filter(_.id === id)
private def filterCode(code: String): Query[Languages, Language, Seq] = languages.filter(_.code === code)
private def all() : Query[Languages, Language, Seq] = languages
override def find(id: Long): Future[Language] =
{
try db.run(filterQuery(id).result.head)
finally db.close()
}
override def find(code: String): Future[Language] =
{
try db.run(filterCode(code).result.head)
finally db.close()
}
override def get(): Future[Seq[Language]] =
{
try db.run(all().result)
finally db.close()
}
}
When I call a url like "domain.tld/{language}" I want to check whether the given language(code) actually exists. Like, if the site isn't available in french (fr) I want to throw an exception or a 404.
Now, my problem is that this whole asynchronously thing is pretty cool and while I do think to understand the theory behind it, I'm rather baffled right now. I mean, I want this to be non-blocking (and asynchronously, which is the reason for me using Future and async ;))
In my controller I want to do something like:
def checkLanguage(language:String) = Action
{
val lang:Future[Language] = languageRepository.find(language)
lang.onComplete
{
case Success(s) = Ok("Yay")
case Failure(f) = 404("Oh no!")
}
}
Of course this can't work, but that's a schema of how I want to have things working. I want to wait or postpone the rendering of the site, until it's confirmed that the given language-code is valid or invalid.
I had a look at the Playframeworks async-documentation for 2.3.6 (https://www.playframework.com/documentation/2.3.6/ScalaAsync) but I couldn't really get this to work as intended.
Any input appreciated!

From your db query don't use .head instead use .headOption. that way ur return type will be a Future [Option [x]]
In ur controller u can do something like this
Lang.map { case Some (x) => Ok (x)
case None => 404 ( "not found ")
}

Try this,
Action.async {
val lang:Future[Option[Language]] = languageRepository.find(language)
lang.map {l => l.map{_ => Ok("Yay") }.getOrElse(NotFound("Oh no!"))
}
First of all, I am assuming that if there is a possibility that a language will not exist then languageRepository.find(language) should return an Option of Language. Change Future[Language] to Future[Result] and use Action.async instead of Action
Now for some explanation, Action takes a block whose result should be Result. However, what you get is Future[Option[Language]]. Play provides async method for Action which needs Future[Result] and it takes cares of completing the request.
So, you need to convert Future[Option[Language]] to Future[Result].
lang.map {l => l.map{_ => Ok("Yay") }.getOrElse(NotFound("Oh no!"))
We map over the lang, if the Option[Language] is not None then we convert it to Ok("yay") else we convert it to NotFound
Even, If you don't get Option[Language], the idea remains the same. Convert Future[Language] to Future[Result] and use Action.async instead of Action

Related

Add exception handling in http4s with rho

I'm using http4s & rho (mainly for Swagger integration)
My services are using this DAO object, that methods that can throw Exceptions (fail the Task)
case class BasicMatchDao() {
def readAll(): Task[List[BasicMatch]] = Task.fail(ActionNotImplemented("readAll"))
def read(id: String): Task[Option[BasicMatch]] = readQuery(id).option.transact(xa)
}
In my RhoService I can handle these like
private def exceptionToJson(t: Throwable):Json = Json.obj("error" -> t.getMessage.asJson)
val rhoService = new RhoService {
GET / path |>> { (request: Request) =>
Ok(dao.readAll.map(_.asJson)).handleWith {
case t:ActionNotImplemented => NotImplemented(exceptionToJson(t))
case t:Throwable => InternalServerError(exceptionToJson(t))
}
}
This way I make sure that whatever I return, it's always a Json
Since I don't want to pollute every RhoRoute with a similar errorhandling I want to do something which is possible with the default http4s.dsl, but I can't seem to get working with rho:
1. Create default error handler
e.g. add
...
Ok(dao.readAll.map(_.asJson)).handleWith(errorHandler)
...
private def errorHandler(): PartialFunction[Throwable, Task[Response]] = {
case t:ActionNotImplemented => NotImplemented(exceptionToJson(t))
case t:Throwable => InternalServerError(exceptionToJson(t))
}
This will fail because NotImplemented is not a Response (I can call .pure on these to make type checking work)
But then the code will compile, but I get this exception:
Cannot convert from fs2.Task[Product with Serializable]
to an Entity, because no EntityEncoder[fs2.Task[Product with
Serializable]] instance could be found.
Ok(dao.readAll.map(_.asJson)).handleWith(errorHandler)
2. Add errorhandler to each RhoRoute
After defining the rhoRoute I'd like to map over it and add the errorhandler to each route, so do something at the r that let's me add the 'handleWith' somewhere (below will not work)
new RhoService(rhoService.getRoutes.map(_.handleWith(errorHandler))
If I can't get this to work, I'll probably move back to the default dsl, but I really liked rho
So Part 1 is fixed for now. Defining the Task as Task[BaseResult] instead of Task[Response] will work
import org.http4s.rho.Result.BaseResult
val errorHandler: PartialFunction[Throwable, Task[BaseResult]] = {
case t:ActionNotImplemented => NotImplemented(exceptionToJson(t))
case t:Throwable => InternalServerError(exceptionToJson(t))
}
I'm looking into part 2 as well. All help is welcome :-)

Idiomatic way to handle side effect and return value in Scala functions

How would you construct a function in which you both want to do a side effect and return a value?
For example I would like the following function:
def futureFromHttpCall: Future[HttpResponse] =
doHttpCall.foreach(publishDomainEvent).returnOriginalFuture
(somehow I have a feeling that monads will come up so if that is the path Im somewhat familiar with cats if there is a solution for this problem there?)
The simplest thing I can think of is instead of "hiding" the side effect inside the Future[T] returning method, expose it as a continuation on the future:
def futureFromHttpCall: Future[HttpResponse] = doHttpCall
And then you could either onComplete on it as a side effect:
futureFromHttpCall.onComplete {
case Success(_) => publishDomainEvent
case Failure(e) => // Stuff
}
Making the effect explicit. Or if you're inside an actor system, you can can pipeTo the Future to your receive method and handle success / failure there.
I think your Future should only complete when all of your domain events are pushed. They should be a Future as well. Then you can use Future.sequence to wait for all of them to complete before returning.
Your question is a little unclear but i assume doHttpCall is a list of some type.
def doHttpCall(): Future[Seq[X]] = ???
def publishDomainEvent(x:X): Future[Unit] = ???
def futureFromHttpCall(): Future[Seq[X]] = {
val firstFuture = ???
firstFuture.flatMap { xs =>
val xxs: Seq[Future[Unit]]= xs.map(publishDomainEvent)
Future.sequence(xxs).map { _ => re }
}
}
All of this waiting can be pretty helpful when testing.

FakeRequest down-casted to RequestHeader

I have a controller that is protected by an Authentication trait. The trait looks like this:
def isAuthenticated(f: => AccountDTO => Request[AnyContent] => Result) =
Security.Authenticated(username, onUnauthorized) { user =>
Action.async {
request => {
Future.successful(f(user)(request))
}
}
}
Everything works well when doing a normal request, but when doing unit tests I encounter problems.
I create the following fake request:
val goodRequest = FakeRequest("POST", "/platform/api/v1/files")
.withBody(Json.toJson(ScalaMockingUtil.fileValidMetadataJson))
.withHeaders((HeaderNames.AUTHORIZATION, "4322tertf2643t34t34"))
Next, I get my controller object and call the method by applying that FakeRequest:
val result: Iteratee[Array[Byte], Result] = filesController.createFileMetadata()(goodRequest)
The problem that I am facing is that somewhere along the line the FakeReuqest gets down-casted to RequestHeader. The problem seems to be the one described here: Unable to test controller using Action.async where the Action has two apply methods instead of one. however, I can't seem to be able to force the one that I need.
Any help is appreciated.
The solution (or a workaround) was to use call() instead of apply():
val result = call(filesController.createFileMetadata(), goodRequest)
Now everything works as intended.

Slick - What if database does not contain result

I am trying to build a simple RESTful service that performs CRUD operations on a database and returns JSON. I have a service adhering to an API like this
GET mydomain.com/predictions/some%20string
I use a DAO which contains the following method that I have created to retrieve the associated prediction:
def getPrediction(rawText: String): Prediction = {
val predictionAction = predictions.filter{_.rawText === rawText}.result
val header = predictionAction.head
val f = db.run(header)
f.onComplete{case pred => pred}
throw new Exception("Oops")
}
However, this can't be right, so I started reading about Option. I changed my code accordingly:
def getPrediction(rawText: String): Option[Prediction] = {
val predictionAction = predictions.filter{_.rawText === rawText}.result
val header = predictionAction.headOption
val f = db.run(header)
f.onSuccess{case pred => pred}
None
}
This still doesn't feel quite right. What is the best way to invoke these filters, return the results, and handle any uncertainty?
I think the best way to rewrite your code is like this:
def getPrediction(rawText: String): Future[Option[Prediction]] = {
db.run(users.filter(_.rawText === rawText).result.headOption)
}
In other words, return a Future instead of the plain result. This way, the database actions will execute asynchronously, which is the preferred way for both Play and Akka.
The client code will then work with the Future. Per instance, a Play action would be like:
def prediction = Action.async {
predictionDao.getPrediction("some string").map { pred =>
Ok(views.html.predictions.show(pred))
}.recover {
case ex =>
logger.error(ex)
BadRequest()
}
}

scala returning a Future[Option[user]]

So, I am totally new to Scala, coming from a Java background and have been given a huge scala code base to learn. And, I am very lost and would appreciate any help.
I have the below function that I want to re-arrange. Currently, the function calls two functions in a row and then returns the result. The first function returns a boolean and the second function returns a User. However, what actually should happen is that the second function should only be called if the first function returns true. So, I need to rearrange it to check the return value of the first function before continuing. Every time I rewrite it to do that, I either get a compile error or exception. It is supposed to return a Future[Option[User]] and the first function doesn't return a User. I just want to return None if FunctionA fails, but because it expects Future[Option[X]]], it is unhappy. So, below is the function:
private def profileForCredentials(userId: String, password: String)(implicit ec: ExecutionContext): Future[Option[User]] =
{
val credentials: Throwable \/ Boolean = {
try {
FunctionA(userId, password).right[Throwable]
}
catch {
case npe: NullPointerException =>
npe.left[Boolean]
}
}
//This function doesn't need to be called unless credentials=true
FunctionB(id, userId).map {
case maybeUser#Some(user) =>
credentials match {
case \/-(x) if x =>
user.some
case _ =>
None
}
case None =>
None
}
}
You guys just got carried away with the scalaz.
I am going to bookmark this page to show people at work as an illustration why we should not be using all this fancy stuff.
Just do this:
Try {
FunctionA(userId, password)
}
.toOption
.collect { case(true) =>
FunctionB(id, userId)
}
.getOrElse(Future.value(None))