5

I would like to use the Akka Http routing system, along with its rejection system but need to nest the response Json for a rejection within a generic Json message block.

I have this working in a very non-generic manner creating a RejectionHandler then adding cases for all possible rejections and handling them all with the specific response code and message.

example:

    // Wraps string into control block format
def WrappingBlock(msg: String) = ???

val myRejectionHandler = RejectionHandler
.newBuilder()
.handle{case MalformedRequestContentRejection(msg, detail) =>
          complete(BadRequest, WrappingBlock(msg)) }
...     // Further lines for all other possible rejections
...     // along with their response codes and messages.
...     // It would be nice if this was just generic code 
...     // rather than specific to every rejection type.
.result()


val routes = handleRejections(myRejectionHandler){ 
    ...
}

However, what I would like is the response code that Akka HTTP provides by default and also the pretty print message that is provided, just nested within a Json control wrapper without a line for every possible rejection type. This seems like it should be possible but I have not been able to complete it.

AlexC
  • 10,676
  • 4
  • 37
  • 55

1 Answers1

5

I think it's possible to do what you want using a combination of handleRejections explicitly with mapResponse. First, consider this simple route definition:

(get & path("foo")){
  complete((StatusCodes.OK, HttpEntity(ContentTypes.`application/json`, """{"foo": "bar"}""" )))
}

If I get a matching request I will respond using json and my caller is happy because they can parse the response as json. But if you try and call this endpoint with a POST request, you will get a response as follows:

HTTP 405 Method Not Allowed

Date: Wed, 06 Jan 2016 13:19:27 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 47
Allow: GET
Server: akka-http/2.3.12

HTTP method not allowed, supported methods: GET

So here we get a plain text response which is not desirable. We can solve that problem universally by adding a couple of directives to the very top of my routing tree like so:

mapResponse(wrapToJson){
  handleRejections(RejectionHandler.default){
    (get & path("foo")){
      complete((StatusCodes.OK, HttpEntity(ContentTypes.`application/json`, """{"foo": "bar"}""" )))
    }
  }
}

With wrapToJson being defined as:

def wrapToJson(resp:HttpResponse):HttpResponse = {

  //If we get a text/plain response entity, remap it otherwise do nothing
  val newResp = resp.entity match{
    case HttpEntity.Strict(ContentTypes.`text/plain(UTF-8)` , content ) => 
      val jsonResp = s"""{"error": "${content.utf8String}"}"""
      resp.copy(entity = HttpEntity(ContentTypes.`application/json`, jsonResp))
    case other =>
      resp
  }

  newResp
}

This is a very basic example, and you'd probable have a better way to generating the json, but this just serves to show how you can fix the plan text responses from the default rejection handler. Now, you must nest the default rejection handler under the mapResponse explicitly because the automatic handling gets added outside the top level of whatever tree you define and thus mapResponse would not see the rejection cases. You still get the default handling though via RejectionHandler.default.

Hopefully this is close to what you were after.

cmbaxter
  • 35,283
  • 4
  • 86
  • 95
  • That is very interesting I had not thought of using a combination with map response, I will look at that option. – AlexC Jan 06 '16 at 14:39
  • Is it possible to write as custom RejectionHandler ? I couldn't come with such solution because it requires `Route` on exit where as in your case we have `Directive`. – expert Jan 21 '16 at 02:04
  • @ruslan, I'm not sure exactly what you are asking. You certainly can create a custom rejection handler as described here: http://doc.akka.io/docs/akka-stream-and-http-experimental/2.0.2/scala/http/routing-dsl/rejections.html#Customizing_Rejection_Handling. Not sure why you mean on the `Route` vs `Directive` on exit. Perhaps create a new question based on this that better describes your situation. – cmbaxter Jan 21 '16 at 12:51
  • @cmbaxter I'd like custom rejection handler to produce json response without wrapping my routes into `mapResponse`+`handleRejections`. Here is what I want: https://gist.github.com/jrudolph/9387700 – expert Jan 21 '16 at 12:53
  • @ruslan, ok, this is exactly what we do. If you want to have your own custom handler then define an implicit one that is in scope for the construction of your routing tree and then have that one complete the route with json for each rejection type that you might encounter. – cmbaxter Jan 21 '16 at 13:06
  • @cmbaxter What you have here is very different. In custom handler you'd need to provide completion route. In your example you have inner route provided by user. Could you please demonstrate generic rejectionHandler ? I'm curious what you'd use as inner route. – expert Jan 21 '16 at 13:09
  • @ruslan, that's because this is an interesting use case in that they still want the default rejection handler logic but they want to wrap those messages in json. The more standard use case is to define your own implicit rejection handler in scope if you want json handling. I don't want to get into an extended conversation in the comments here. If you have a new question, ask it as a question and I will do my best to provide you with an answer. – cmbaxter Jan 21 '16 at 13:13