Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala, Akka, Spray: How to validate json data before processing?

I can process this json when all the inputs are valid, i.e with valid keys (including case) and values. The next step is to validate keys and return 400 (Bad Request) if the keys or values are invalid. What is a good way to add this validation?

The API call

POST http://localhost:8080/api/v1/adsession
Content-Type: application/json
body {
  "sessionId": "abcd123123123",
  "serviceGroup": "1234",
  "targetCode": {"zipcodes":"30096,30188","code2":"value2"}
}

Route handler

class AdSessionRoutes(services: Services)(implicit ec: ExecutionContext, log: LoggingContext) extends ApiRoute(services) {

  implicit val timeout = Timeout(10 seconds)

  val postSession = pathPrefix("adsession") & pathEnd & post

  val route: Route = {
    withService("adSession") { service =>

      postSession {
        entity(as[AdSession]) { adSession =>
          log.info(s"Processing POST ${adSession}")
          val future = (service ? CreateAdSession(adSession)).mapTo[AdSession]

          onComplete(future) {
            case Success(result) =>
              complete(StatusCodes.Created, result)

            case Failure(e) =>
              log.error(s"Error: ${e.toString}")
              complete(StatusCodes.InternalServerError, Message(ApiMessages.UnknownException))
          }
        }
      }
    }
  }
}

Model object

case class AdSession(
  sessionId: String,
  serviceGroup: String,
  targetCodes: Map[String,String],
  id: Option[String] = None)

object AdSessionJsonProtocol extends DefaultJsonProtocol {
  implicit val adSessionFormat = jsonFormat4(AdSession)
}

entity(as[AdSession]) does map keys to case class fields, but I'm not sure how to catch those errors. I would like to capture those errors as well as add additional validations and return 400 with valid json error response.

like image 775
Srini K Avatar asked Feb 01 '15 18:02

Srini K


2 Answers

I know this may be a little late but since akka-http-2.4.6, you can put the verification logic inside a case class. Check out this for info on how to do it.

like image 176
zaxme Avatar answered Oct 19 '22 09:10

zaxme


Define your own read and write methods for AdSession like this:

object AdSessionJsonProtocol {
    implicit object adSessionJsonFormat extends RootJsonFormat[AdSession] {
        override def read(json: JsValue): AdSession = ???
        override def write(obj: AdSession): JsValue = ???
    }
}

Inside read function you can perform additional validation.

Another question is how to pass collected errors to Spray route. I would like to suggest you to wrap AdSession into Either[String, AdSession], for example:

postSession {
        entity(as[Either[String, AdSession]]) { adSession =>

So, now your adSessionJsonFormat will looks like:

implicit object adSessionJsonFormat extends RootJsonFormat[Either[String, AdSession]] {

    override def read(json: JsValue): Either[String, AdSession] = {
      // json.asJsObject.fields access fields, perform checks
      // if everything is OK return Right(AdSession(...))
      // otherwise return Lift("Error Message")
    }

    override def write(obj: Either[String, AdSession]): JsValue = ???
}

But, I think it's possible to solve it in more elegant way using some implicit magic.

like image 45
Artsiom Miklushou Avatar answered Oct 19 '22 07:10

Artsiom Miklushou