I have a telegram bot on Scala and I want to send the image to the user if it exists and the message "Sorry, the image doesn't exist" if it's not. And I have a function getImage(tag), which returns Future.successful(link) or Future.failed(NoImageException(msg)).
onCommand("/img") { implicit msg =>
val tag = msg.text.get.drop("/img ".length)
try {
if (tag.isEmpty) throw new IndexOutOfBoundsException()
service.getImage(tag).transform {
case Success(link) => Success(
try {
replyWithPhoto(InputFile(link))
} catch {
case _ => reply(link) // maybe it isn't a photo...
})
case Failure(e) => Success(reply(e.getMessage))
}.void
} catch {
case _: IndexOutOfBoundsException => reply("Empty argument list. Usage: /img tag").void
}}
And this code sends an image if it's a success, but it doesn't send a message if it's a failure (but it definitely chooses case Failure(e) in this case)
reply family of functions return a Future[Message]. Currently you wrap the result of reply in Success, so the result of your transform is Future[Future[Message]], which doesn't work. Instead you can use transformWith, which expects a Future result from its argument:
onCommand("/img") { implicit msg =>
val tag = msg.text.get.drop("/img ".length)
val message: Future[Message] =
if (tag.isEmpty) reply("Empty argument list. Usage: /img tag")
else {
service.getImage(tag).transformWith {
case Success(link) => replyWithPhoto(InputFile(link)).recoverWith {
case _ => reply(link) // maybe it isn't a photo...
}
case Failure(e) => reply(e.getMessage)
}
}
message.void
}
Note, that I have also removed both try operators. The external is just unnecessary, because you can just use if/else. The internal won't work at all, because replyWithPhoto returns a Future. So it doesn't throw errors, and you need to recover or transform when it fails.
Related
I have some API Rest with CRUD operations.
Each entity is identified by using UUID.
For example for Create it is similar to this:
private val createProduct = post {
path("product" / Segment) { productUUID =>
Try(UUID.fromString(productUUID)) match {
case Failure(_) => // Bad request
case Success(validUuid) =>
entity(as[ProductData]) { productData =>
onComplete(addProduct(validUuid, productData)) {
case Success(_) => complete(StatusCodes.OK)
case Failure(ex) => // some code
}
}
}
}
}
Operations Read(GET), Update(PUT) and Delete(DELETE) are similar to POST:
first step is get the uuid
for PUT get the payload using entity(as[ProductData]) (not needed for GET and DELETE)
invoke some method that returns Future[Something]
What I would like to do is remove boilerplate like:
getting the UUID, validating it, returning Bad Request if it's not valid
create some directive/function for handling the future: if there's an exception just return 500 Internal Server Error, but in case of Success continue for processing the value (I think using provide directive).
I found this example (https://fmsbeekmans.com/blog/akka-http-2-creating-custom-directives.html):
def simplifiedOnComplete[T](future: Future[T])(timeout: FiniteDuration): Directive1[T] = {
Try(Await.result(future, Duration.Inf)) match {
case Success(result) => provide(result)
case Failure(error) => failWith(error)
}
}
I said, ok, there's a try in this example! Maybe I can change it for working with UUID instead of Future:
def getUUID[T]: Directive1[T] = {
path(Segment) { maybeuuid =>
Try(UUID.fromString(maybeuuid)) match {
case Success(result) => provide(result) // compilation error here
case Failure(error) => failWith(error)
}
}
}
The code does not compile with the error:
Type mismatch. Required: Route, found: Directive1[UUID]
I guess the problem is I've added path ...
How can I create a Directive to extract the valid uuid and return Bad Request if it's not valid?
And, is it possible to encapsulate in a custom directive the code that handles de future?
For example the routes defined at the top would be something like:
private val createProduct = post {
path ("product") {
extractUUID { validUUID =>
entity(as[ProductData]) { productData =>
futureSuccess(addProduct(validUUID, productData)) { successValue =>
// code here, for example: complete(OK)
}
}
}
}
}
You were almost there - Your code is :
def getUUID[T]: Directive1[T] = {
path(Segment) { maybeuuid =>
Try(UUID.fromString(maybeuuid)) match {
case Success(result) => provide(result) // compilation error here
case Failure(error) => failWith(error)
}
}
}
You don't need generic T since you can only have a UUID back from UUID.fromString
path(Segment) gives you a Directive. So you want to useflatMap to get a Directive back (provide returns a Directive)
So it would be something like
def getUUID: Directive1[UUID] = {
path(Segment).flatMap { maybeuuid =>
Try(UUID.fromString(maybeuuid)) match {
case Success(result) => provide(result)
case Failure(error) => failWith(error)
}
}
}
And, is it possible to encapsulate in a custom directive the code that
handles de future?
Yes, it is the same as the above. onComplete returns a Directive so you will have to flatMap.
To return BadRequest, look up the rejection handlers in the akka-http doc.
Consider following sample code
try{
.....
}catch{
case e1:Exception1 => { method1(..)}
case e2:Exception2 => { method2(..)}
case e3:Exception3 => { method3(..)}
}
Now if I'd like to run same method i.e. methodGeneral() to all exception (e1, e2, e3)
however how to avoid to apply the method in each block like below, but can achieve in one shot?
try{
.....
}catch{
case e1:Exception1 => { method1(..); methodGeneral();}
case e2:Exception2 => { method2(..); methodGeneral();}
case e3:Exception3 => { method3(..); methodGeneral();}
}
The Scala Try type offers a few more features than the basic try..catch. For example, you could do something like this.
import scala.util.Try
Try {
. . .
}.recover{ excp:Throwable =>
excp match {
case e1:Exception1 => method1(..)
case e2:Exception2 => method2(..)
case e3:Exception3 => method3(..)
}
methodGeneral(excp)
}.get
This requires that the .recover code block returns the same type as the Try code block. If the return value is actually of no interest, such as Unit for example, then the .get can be omitted.
If you're really wedded to the old try..catch you might try something like this.
try {
. . .
} catch {
case excp: Throwable =>
excp match {
case e1:Exception1 => method1(..)
case e2:Exception2 => method2(..)
case e3:Exception3 => method3(..)
}
methodGeneral(excp)
}
It's still a good idea to ensure that the try block and catch block are type-compatible.
in my scala playframework application I want to return the via Post submitted and then with slick stored object back to frontend as json
I tried this:
def createClient = Action.async { implicit request =>
request.body.asJson.map(_.validate[ClientModel] match {
case JsSuccess(client) =>
clientDTO.createClient(client).map { clients =>
Ok(Json.toJson(clients))
}
})
}
but I get this error:
what could be my problem?
NEW ERROR
Try with something along these lines:
def createClient = Action.async { implicit request =>
request.body.asJson match {
case None => // do something that returns a Future[Result] ~ such as NotFound or
case Some(js) =>
js.validate[ClientModel] match {
case client: JsSuccess[ClientModel] =>
clientDTO.createClient(client).map { clients =>
Ok(Json.toJson(clients))
}
case e: JsError => // do something that returns a Future[Result] ~ such as InternalServerError
}
}
}
the .async, as the name suggests require a Future type.
You have 2 options:
Remove the .async, this will make your def synchronous (deprecated)
Leave the .async but return a Future result
def createClient = Action.async { implicit request =>
request.body.asJson.map(_.validate[ClientModel] match {
case JsSuccess(client) =>
clientDTO.createClient(client).map { clients =>
Future(Ok(Json.toJson(clients)))
}
})
}
But you still need to add the case of validation error:
case JsSuccess(client) =>
{
clientDTO.createClient(client).map
{
clients => Future(Ok(Json.toJson(clients)))
}
}
case _ => Future(BadRequest(""))
This should work and, in all the cases apart JsSuccess, the function will return a future BadRequest response.
A better solution is to change the _ with JsError:
case e: JsError =>
{
Println(e)
Future(BadRequest(.....))
}
This will also print the error.
You can read more here: https://www.playframework.com/documentation/2.6.x/ScalaJson
(Using validation chapter)
More about future in scala: https://docs.scala-lang.org/overviews/core/futures.html
I need to write simple web service with akka-http and reactivemongo.
Function to save data looks like this
def saveRoute(route: Route):Future[WriteResult] = {
collection.insert(route)
}
a code that calls this function looks like this
val userRoutes = {
logRequestResult("akka-http-microservice") {
path("routes") {
(post & entity(as[Route])) { route =>
Database.saveRoute(route)
}
}
}
}
I need to return result with inserted ID of Route and do this without making the thread to wait.
if try
Database.saveRoute(route).onComplete{
case Success(r) => complete(r.toString)
case Failure(e) => complete(e.getMessage)
}
It cannot compile, because it doesn't return value.
I know how to make it in dirty way, but really want to make in appropriate manner.
What should be done in this case?
Seems like I've found most efficient way to do this. It's built in onComplete directive
(path("routes" / "add") & post & entity(as[Route])) {
route =>
onComplete(routesController.addRoute(route)) {
case Success(result) => complete(StatusCodes.Created, "OK")
case Failure(ex) => complete(new ErrorResponse(StatusCodes.InternalServerError.intValue, ErrorResponse.ERROR, ex.getMessage))
}
}
Use onSuccess to handle the valid response when the future finishes and handleExceptions to handle when the future does not succeed.
val userRoutes = {
handleExceptions(mongoDbExceptionHandler) {
logRequestResult("akka-http-microservice") {
path("routes") {
(post & entity(as[Route])) { route =>
onSuccess(Database.saveRoute(route)) { result =>
complete(result)
}
}
}
}
}
}
// Something like this for whatever the exceptions you expect are
val mongoDbExceptionHandler = ExceptionHandler {
case ex: MongoDbReadException => complete(HttpResponse(InternalServerError, "No database")))
}
onSuccess:
http://doc.akka.io/docs/akka/2.4.9/scala/http/routing-dsl/directives/future-directives/onSuccess.html
handleExceptions:
http://doc.akka.io/docs/akka/2.4.9/scala/http/routing-dsl/exception-handling.html
You can map over the future and then complete the request like below.
val future = Database.saveRoute(route)
val response = future.map(_.getId).recover(_.getMessage)
complete(response)
On a side note, for handling exceptions, it is a good practice to have a ExceptionHandler and wrap it with your route. You can find example here.
You have few option i will try to put the most commonly used ones for REST API based solutions:
OnSuccess use it when you want your expectations to be bubbled and handled by expectionHandler
concat(
path("success") {
onSuccess(Future { "Ok" }) { extraction =>
complete(extraction)
}
},
path("failure") {
onSuccess(Future.failed[String](TestException)) { extraction =>
complete(extraction)
}
}
)
https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/future-directives/onSuccess.html
onComplete: When you want to manually handle the exception. Try Monad wrapped.
val route =
path("divide" / IntNumber / IntNumber) { (a, b) =>
onComplete(divide(a, b)) {
case Success(value) => complete(s"The result was $value")
case Failure(ex) => complete((InternalServerError, s"An error occurred: ${ex.getMessage}"))
}
}
https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/future-directives/onComplete.html
How about this, replace:
Database.saveRoute(route)
with:
complete(Database.saveRoute(route).map(_.toString).recover(_.getMessage))
When you use RequestContext you should use something like this:
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.RouteResult.{Complete, Rejected}
...
val myRoute: Route = (path("my-path") & get) { req: RequestContext =>
val futureResp: Future[HttpResponse] = ???
futureResp.map(resp => RouteResult.Complete(resp))
}
#volatile var breakRequested: Boolean = false
// ...
def futureFunc(): Option[Iterable[String]] = {
val result = hugeList.map { item =>
if(breakRequested) {
// put exit code here
// return None
// throw AnException
// what else?
}
item.toText() // time-expensive function
}
Some(result)
}
Future { futureFunc() }
Given somebody set the breakRequested flag to true: How may I exit the map?
What I tried:
return None => such a return transformed into a scala.runtime.NonLocalReturnControl: I tried to catch this error, but it seems to be uncatchable (bypasses try/catch).
object Cancelled extends Exception: I tried to throw this but was not able to catch it as well.
All exceptions show up in the command line, when I run the app through SBT.
If somehow possible, I prefer a solution without try/catch.
For the fast solution, you can convert your hugeList to the Iterator, and then use takeWhile:
...
val result = hugeList.toIterator
.takeWhile(_ => !breakRequested)
.map { item =>
item.text
}
...
Edit:
Scala's Future has no cancellation, but twitter's Future has. To cancel this use method raise.
Also you can write your own map, for example:
#annotation.tailrec def map[T,R](
src: List[T],
cancel: => Boolean,
dsc: List[R] = List.empty[R])(f: T => R):List[R] = src match {
case _ if cancel => dsc
case h :: t => map(t, cancel, f(h) :: dsc)(f)
case Nil => dsc
}
Future{map(hugeList, breakRequested)(_.text)}
If you don't need the result, you can create another future,which will be completed after your breakRequested was changed. And use method Future.firstCompletedOf.