Search code examples
scalaroutesakkafutureakka-http

Akka HighLevelHttp return Route from recovering Future


Because I have a request body, I need to get it as future and send the response after an action. But, I need to check if the body is valid and if is not, I want to return a BadRequest status.

I know is too much code, so please look inside the requestFuture, in recover function is the problem.

(pathPrefix("api" / "search") & post & extractRequest) { request =>
  val tokenResult = request.headers.find(x => x.name.toLowerCase == "token")
    tokenResult match {
      case None => throw new IllegalArgumentException(ServerMessages.TOKEN_NOT_FOUND)
      case Some(token) => token.value match {
        case this.apiToken => {
          val entity = request.entity
          val strictEntityFuture = entity.toStrict(2 seconds)
          val requestFuture = strictEntityFuture
              .map(_.data.utf8String.parseJson
                .convertTo[SearchFilters])

          requestFuture map { filters =>
             // here - I return an Route with data from controller
             complete(controller.searchData(filters)
               .map(_.toJson.prettyPrint)
               .map(toHttpEntity)))
          } recover (e => {
             // HERE IS THE PROBLEM
             // I need to return an Route, but here will be a Future[Route]
             complete(400 -> ServerMessages.INVALID_REQUEST_BODY)
          }
        }
        case _ => throw new IllegalArgumentException(ServerMessages.INVALID_TOKEN)
      }
}

I need to unpack the response from the future, or to use another way to throw the error.

found a solution using onComplete directive, but I need to know if my json is successfully converted or not, to can throw a custom error.

onComplete(requestFuture) {
    case Success(filters) => searchKpi(filters) ~
       pathEndOrSingleSlash {
           complete(403 -> ServerMessages.INVALID_METHOD_ARGUMENTS)
       }
    case Failure(ex) => failWith(ex) // returns 500 Internal Server Error
}

thanks


Solution

  • You can use an ExceptionHandler.

    Define your exception handler like in the doc and add a case from your exception(which should be the same that is thrown by failWith) to the HTTP Status you need to return (You mention BadRequest)

        case class MyBadException() extends RuntimeException
    
      implicit def myExceptionHandler: ExceptionHandler =
        ExceptionHandler {
          case MyBadException() => complete(HttpResponse(BadRequest)) // You decide the HTTP status to return
          case _ => complete(HttpResponse(InternalServerError, entity = "Bad numbers, bad result!!!"))
        }
    
    
        val route = path("test") {
          onComplete(getSomething()) {
            case Success(v) => complete("Ok")
            case Failure(err) => failWith(MyBadException())
          }
        }
    
    

    Note that you have 2 ways to attach the exception handler

    From the doc:

    Bring it into implicit scope at the top-level.
    Supply it as argument to the handleExceptions directive.