2
votes

I'm writing a web application in Spray and am using the case class validation method described at the bottom of this article:

http://spray.io/documentation/1.2.3/spray-routing/advanced-topics/case-class-extraction/

It works great, but I'm unhappy with the message returned to the client. Aside from the fact that it's ugly, it exposes some details about the back end:

The request content was malformed: Instantiation of [simple type, class com.mycompany.models.users.User$Login] value failed: requirement failed: Invalid email address (through reference chain: com.mycompany.models.users.Login["password"])

I'd like it to say, simply

Invalid email address

Any idea on how to accomplish this?

Update

I've implemented what @soong suggested, but it's good not great (so far).

My require code is require(!zipcode.isEmpty, "Invalid zipcode")

My rejection handler code:

  implicit def validationRejectionHandler = RejectionHandler {
    case MalformedRequestContentRejection(msg, cause) :: _ =>
      complete(BadRequest, s"$msg")
  }

The problem is that the message I want ("Invalid zipcode") is wrapped in some other text:

cause.detailMessage = "requirement failed: Invalid zipcode"

Any way to get my exact error message without having to do string manipulation?

1
Are you familiar with making a custom exception handler? spray.io/documentation/1.2.2/spray-routing/key-concepts/… - childofsoong
Nope. This is excellent. Thank you! - threejeez
I'll make it an answer, then. - childofsoong
So I just learned that these types of exceptions are rejects and go the RejectionHandler: spray.io/documentation/1.2.2/spray-routing/key-concepts/… - threejeez
When you say 'string manipulation' do you mean you don't want to compare the error string to "Invalid zipcode"? - childofsoong

1 Answers

3
votes

With Spray, you can do this by making a custom exception handler. Here is the example from their page describing it:

import spray.util.LoggingContext
import spray.http.StatusCodes._
import spray.routing._

implicit def myExceptionHandler(implicit log: LoggingContext) =
  ExceptionHandler {
    case e: ArithmeticException =>
      requestUri { uri =>
        log.warning("Request to {} could not be handled normally", uri)
        complete(InternalServerError, "Bad numbers, bad result!!!")
      }
  }

class MyService extends HttpServiceActor {
  def receive = runRoute {
    `<my-route-definition>`
  }
}

Note that it is an implicit definition, so when you are defining your service, all you need to do is have it in scope, and it will be picked up. Any errors not handled by it will default to their original values. As with most exception handling situations, I would encourage you to be very specific in deciding which exceptions will be handled.

Since you say it is actually a rejection, not an exception, I'll add the link for rejection handling, and its example:

import spray.routing._
import spray.http._
import StatusCodes._
import Directives._

implicit val myRejectionHandler = RejectionHandler {
  case MissingCookieRejection(cookieName) :: _ =>
    complete(BadRequest, "No cookies, no service!!!")
}

class MyService extends HttpServiceActor {
  def receive = runRoute {
    `<my-route-definition>`
  }
}

Again, we see the object is an implicit definition, and so you'll need to have it in scope to be picked up.