简体   繁体   中英

Play2/Scala Custom JSON field validation

Let's say I have this model, in which I defined a custom case class IPAddress address with specific JSON formats. This class holds a String representation of an IPv4 and havs a validation in the Constructor that raises an IllegalArgumentException if the input string is not valid.

case class Node(id: UUID, name: String, ip: IPAddress)

case class IPAddress(s: String) {
  val rx = """^([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])$""".r
  require(rx.pattern.matcher(s).matches())

  val ip = s
  override def toString = ip
}


object JsonNodeFormat {
  import play.api.libs.json.Json

  implicit val midWrite: Writes[FMMid] = Writes {
    (mid: FMMid) => JsString(mid.toString)
  }
  implicit val midRead: Reads[FMMid] = JsPath.read[String].map(FMMid(_))

  implicit val NodeFormat = Json.format[Node]
}

Then I have my controller with an action that creates a new Node and write it in the database (in my case I'm using ReactiveMongo, but this is irrelevant)

class Nodes extends Controller with MongoController {
  def collection: JSONCollection = db.collection[JSONCollection]("nodes")

  import models._
  import models.JsonNodeFormat._

  def createTest = Action.async(parse.json) {
    request =>
      request.body.validate[Node].map {
        node =>
          collection.insert(node).map {
            lastError =>
              Created(s"Node Created")
          }
      }.getOrElse(Future.successful(BadRequest("invalid json")))
  }
}

If make a request with valid json

{
    "id": "0879d4be-78bb-4cc0-810b965b",
    "ip": "192.168.0.10",
    "name": "node1"
}

everything works fine. The object is correctly added to the db. I would like the controller return a BadRequest response when an invalid IP address is passed. On the contrary, if I pass a Json with an invalid IP address

{
    "id": "0879d4be-78bb-4cc0-810b965b",
    "ip": "192.168.0.foo",
    "name": "node1"
}

it causes an internal server error with all the stack printed on the console (because nobody catches the constructor exception). I would like that when an invalid IPAddress is passed, the request.body.validate function fails and the execution falls in the getOrElse statement.

Also note that passing an invalid UUID doesn't generate an error but a BadRequest reply.

What's missing in my IPAddress class?

Eventually I found a solution. I discovered that between other Reads validation helpers there is one called pattern that matches the a String with a Regex. So I changed my Json Reads as

  implicit val midRead: Reads[FMMid] = JsPath.read[String](pattern("""^5\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])$""".r)).map(FMMid(_))

and everything started working as I expected.

PS still can't understand how UUID works without any custom validator.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM