I have a block of code as shown below to handle some exceptions, i use if-else statement but i don't like them nested within each other, wondering if it is possible to use pattern match to make it nicer?
try {
if (response.code < 200 || response.code > 299) {
throw new SearchClientFailure(s"API request failed with code ${response.code}, body ${response.body}")
} else {
if (isExceeded(response.body)) {
throw new SearchClientFailure("Exceed limit")
} else {
response.body
}
}
} catch {
case e: SearchClientFailure =>
if (queries.isEmpty) {
throw new SearchClientFailure
} else {
logger.warn(s"Failed to update the queries: ${e.message}")
queries
}
case _ =>
throw new SearchClientFailure
}
You could do :
response match {
case r if (r.code < 200 || r.code > 299) => ...
case r if (isExceeded(r.body)) => ...
case r => r.body
}
Is it nicer ? I'm not 100% sure honestly, I don't really prefer this style to your one.
Btw, depending on what you used you often have access to response.isSuccess() or response.code.isSuccess() instead of testing code values
Rather than take on the overhead of those short throws and catches, I'd be tempted to use Either[String,Response].
Right(response).flatMap{r =>
if (r.code > 199 && r.code < 300) Right(r)
else Left(s"API request failed with code ${r.code}, body ${r.body}")
}.flatMap{r =>
if (isExceeded(r.body)) Left("Exceed limit")
else Right(r)
}.fold(msg => {
if (queries.isEmpty) throw new SearchClientFailure
logger.warn(s"Failed to update the queries: $msg")
queries
}, _.body)
The only throw required is the one tossed out of this context. Everything else is handled in the code flow.
Here is a version that uses Either
val apiResult: Either[String, String] =
if (response.code < 200 || response.code > 299)
Left(s"API request failed with code ${response.code}, body ${response.body}")
else if (isExceeded(response.body))
Left("Exceed limit")
else
Right(response.body)
apiResult match {
case Right(result) =>
result
case Left(message) if queries.nonEmpty =>
logger.warn(s"Failed to update the queries: $message")
queries
case _ =>
throw new SearchClientFailure
}
The apiResult value stores either the error string or the correct result of the API call. The subsequent match can then retrieve the original error string if required.
This follows the convention that Right is the normal/successful result and Left is the error case or abnormal result.
Related
I'm using akka http and streams to fulfill API requests.
When the request is invalid I want to return a 400 and if it's valid, i want to proceed with the computation and return the result afterwards.
The Problem I'm facing is, that the Payload I'm receiving from the POST request is a Source and I cannot convert it into 2 Streams (one for valid and one for invalid input data) and complete the request correct.
path("alarms")(
post(entity(asSourceOf[String]) { message =>
val flow = message.via(Flow[String].map((it) =>
Try(if valid(it) then it else throw Exception("Wrong input"))
))
complete(repository.create(flow).run) // <-- here I only want to pass all events that are valid. For the other events complete(HttpResponse(NotFound, entity = "Invalid input")) should be used
})
)
/// The signature of the repository.create looks like that
def create(message: Source[String, NotUsed]): RunnableGraph[Future[Done]]
You may use the akka-http handleExceptions directive, sth like this:
val exceptionHandler = ExceptionHandler {
case ex: RuntimeException =>
complete(HttpResponse(NotFound, entity = "Invalid input"))
}
path("alarms")(
handleExceptions(exceptionHandler) {
post(entity(asSourceOf[String]) { message =>
val flow = message.via(Flow[String].map((it) =>
Try(if valid(it) then it else throw new RuntimeException("Invalid input"))
))
complete(repository.create(flow).run)
})
}
)
Doc:
https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/execution-directives/handleExceptions.html
https://doc.akka.io/docs/akka-http/current/routing-dsl/exception-handling.html
There is also handleRejections directive for even more control - see https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/execution-directives/handleRejections.html
I am trying to count rejects that were returned by RejectionHandler
I guess the way I doing this now is not the best one, or even incorrect. I am just trying to invoke the incremental method in my database, in each of the handled cases.
implicit def rejectionHandler: RejectionHandler =
RejectionHandler.newBuilder()
.handle {
case MissingCookieRejection(cookieName) =>
requestInfoEntry.incrementRjectedNum
complete(HttpResponse(BadRequest, entity = "No cookies, no service!!!"))
}
.handle {
case AuthorizationFailedRejection =>
requestInfoEntry.incrementRjectedNum
complete((Forbidden, "You're out of your depth!"))
}
.handle {
case ValidationRejection(msg, _) =>
requestInfoEntry.incrementRjectedNum
complete((InternalServerError, "That wasn't valid! " + msg))
}
.handleAll[MethodRejection] { methodRejections =>
requestInfoEntry.incrementRjectedNum//todo sideeffect ??
val names = methodRejections.map(_.supported.name)
complete((MethodNotAllowed, s"Can't do that! Supported: ${names mkString " or "}!"))
}
.handleNotFound {
requestInfoEntry.incrementRjectedNum
complete((NotFound, "Not here bldghad!"))
}
.result()
While I "visit my unfound page", Akka HTTP returns me a right response: "Not here bldghad!" every time I refresh browser on not existed web-page. But when I check my database, I see the only one increment. Can I do this way as I do at all? (I need to count successes too)
PS Maybe I need to work with status codes and do not use side effects. But what is the best place where I can do it? I have a lot of controllers and do not want to intercept this in every controller)
Can I globally intercept responses somewhere?
I've done it this way, hope it would be useful
def rejectionHandlerWithCounter: RejectionHandler = { (rejections: Seq[Rejection]) =>
requestInfoEntry.incrementRjectedNum
Some(complete((StatusCodes.Forbidden)))
}
I've been trying to perform a test that uses a mock Http server to respond and a function that returns a Future[String] or an Exception if the Http server response isn't 200.
I'm trying to achieve a test without using Awaits, but instead AsyncFunSuiteLike.
However the following test seems impossible to resolve without doing it synchronously:
test("Error responses") {
Future.sequence {
NanoHTTPD.Response.Status.values().toList.filter(status => status.getRequestStatus >= 400).map {
status => {
httpService.setStatusCode(status)
val responseBody = s"Request failed with status $status"
httpService.setResponseContent(responseBody)
val errorMessage = s"Error response (${status.getRequestStatus}) from http service: $responseBody"
recoverToExceptionIf[ServiceException] {
myObject.httpCall("123456")
}.map {
ex => assert(ex.getMessage === errorMessage)
}
}
}
}.map(assertions => assert(assertions.forall(_ == Succeeded)))
}
Basically the problem is that when the Futures are tested, the NanoHTTPD is set to the last valued set in the map, so all ex.getMessage are the same. If I run those status codes one by one I do get the desired results, but, is there a way to perform all this in one single Async test?
From the looks of it, NanoHTTPD is stateful, so you have a race between the .set... calls and the .httpCall.
If you can spin up a new httpService within each Future, then you should be able to parallelize the tests (unless the state in question would be shared across instances, in which case you're likely out of luck).
So you'd have something like (replace Status with the type of status in your code and HTTPService with the type of httpService):
// following code composed on the fly and not run through the compiler...
def spinUpHTTPService(status: Status, body: String): Future[HTTPService] = {
// insert the code outside of the test which creates httpService
httpService.setStatusCode(status)
httpService.setResponseContent(body)
httpService
}
test("Error responses") {
Future.sequence(
NanoHTTPD.Response.Status.values().toList.filter(status => status.getRequestStatus >= 400).map { status =>
spinUpHTTPService(status, s"Request failed with status $status")
.flatMap { httpService =>
val errorMessage = s"Error response (${status.getRequestStatus}) from http service: $responseBody"
recoverToExceptionIf[ServiceException] {
myObject.httpCall("123456")
} map {
ex => assert(ex.getMessage === errorMessage)
}
} // Future.flatMap
} // List.map
).map { assertions => assertions.forAll(_ == Succeeded) }
}
I am new to Akka, and am having trouble with the Future.await call in Akka 1.2. I have created some Futures with OnTimeout and OnException handlers, and then I am waiting for them to complete. The code looks something like this:
val futures = ListBuffer.empty[Future[Any]]
val future = (peer ? bMsg) onResult {
case result => result match {
case msg:Ack => handleAck(msg)
case msg:Nack => handleNack(msg)
}
} onTimeout {
case _ => {
// do something
}
} onException {
case _ => {
// do something
}
}
futures += future
futures.foreach(_.await(Duration(8000, "millis")))
log.info("Got here")
When there is an exception, the exception handler gets executed and I get to the "Got here" line. However, when there is a timeout, though the timeout handler is executed, I never get to the "Got here" line. Even with the AtMost value set, await does not return.
What could be causing this?
await throws an exception if it times out, have you've verified that you're not getting an exception in futures.foreach?
I am using scalatra to "export" a MongoDB data to JSon, my actions are very simple, like:
get("/") {
val title = db.get_collection("main", "api", "title")
send_json(title)
}
I want to send a HTTP error and a text if something go wrong, on the other side it will be converted to something meaninful to the user.
So the method becames:
get("/") {
try {
val title = db.get_collection("main", "api", "title")
send_json(title)
} catch {
case e:java.util.NoSuchElementException => send_error("DB malformed", InternalError)
case e:com.mongodb.MongoException => send_error("Can not connect to MongoDB", InternalError)
case e => send_error("Internal Error", InternalError)
}
}
The try catch is bigger that the actual method and I need to do this for every method, the class become at first sight an ugly collection of try catch.
There is any way to avoid or minimize the bad looking and distracting try catch all over the code? I am new to Scala (and Java BTW) so I guess I am missing something.
I dont want the DB object to send JSON, so having the try catch on the db.get_collection method is not an option.
There's a special route handler for this:
error {
case e: Throwable => {
redirect("/")
}
}
By changing the case statement you can switch on the error type.
Well, I don't know Scalatra enough, but the catch block is a partial function, so you could do something like this:
val errorHandling: PartialFunction[Throwable, Unit] = {
case e:java.util.NoSuchElementException => send_error("DB malformed", InternalError)
case e:com.mongodb.MongoException => send_error("Can not connect to MongoDB", InternalError)
case e => send_error("Internal Error", InternalError)
}
get("/") {
try {
val title = db.get_collection("main", "api", "title")
send_json(title)
} catch errorHandling
}