2014-10-11 3 views
12

Ich habe die Formularüberprüfung mit benutzerdefinierten Einschränkungen implementiert, aber jetzt möchte ich dasselbe mit JSON-Daten machen.Benutzerdefinierte JSON-Validierungsbeschränkungen in Play Framework 2.3 (Scala)

Wie kann ich benutzerdefinierte Validierungsregeln auf einen JSON-Parser anwenden?

Beispiel: Die POST-Anfrage des Clients enthält einen Benutzernamen (username) und ich möchte nicht nur sicherstellen, dass dieser Parameter ein nicht leerer Text ist, sondern auch, dass dieser Benutzer tatsächlich in der Datenbank vorhanden ist.

// In the controller... 

def postNew = Action { implicit request => 
    request.body.asJson.map { json => 
     json.validate[ExampleCaseClass] match { 
      case success: JsSuccess[ExampleCaseClass] => 
       val obj: ExampleCaseClass = success.get 
       // ...do something with obj... 
       Ok("ok") 
      case error: JsError => 
       BadRequest(JsError.toFlatJson(error)) 
     } 
    } getOrElse(BadRequest(Json.obj("msg" -> "JSON request expected"))) 
} 


// In ExampleCaseClass.scala... 

case class ExampleCaseClass(username: String, somethingElse: String) 

object ExampleCaseClass { 
    // That's what I would use for a form: 
    val userCheck: Mapping[String] = nonEmptyText.verifying(userExistsConstraint) 

    implicit val exampleReads: Reads[ExampleCaseClass] = (
     (JsPath \ "username").read[String] and 
     (JsPath \ "somethingElse").read[String] 
    )(ExampleCaseClass.apply _) 
} 

, die so weit ist, wie ich, aber dies stellt nur sicher, dass username ein String ist. Wie kann ich meine zusätzliche benutzerdefinierte Validierungsregel, z. um zu überprüfen, ob der angegebene Benutzer wirklich existiert? Ist das überhaupt möglich?

Sicher, kann ich meinen obj in dem case success Abschnitt in der Aktion nehmen und zusätzliche Kontrollen dort durchführen, aber dies scheint nicht sehr elegant, denn dann würde ich meine eigene Fehlermeldung erstellen und konnte nur Benutzer JsError.toFlatJson(error) für einige Fälle. Nach stundenlangem Suchen und Versuchen konnte ich keine Beispiele finden.

Für regelmäßige Formen würde ich so etwas wie folgt verwenden:

// In the controller object... 

val userValidConstraint: Constraint[String] = Constraint("constraints.uservalid")({ username => 
    if (User.find(username).isDefined) { 
     Valid 
    } else { 
     val errors = Seq(ValidationError("User does not exist")) 
     Invalid(errors) 
    } 
}) 

val userCheck: Mapping[String] = nonEmptyText.verifying(userValidConstraint) 

val exampleForm = Form(
    mapping(
     "username" -> userCheck 
     // ...and maybe some more fields... 
    )(ExampleCaseClass.apply)(ExampleCaseClass.unapply) 
) 


// In the controller's action method... 

exampleForm.bindFromRequest.fold(
    formWithErrors => { 
     BadRequest("Example error message") 
    }, 
    formData => { 
     // do something 
     Ok("Valid!") 
    } 
) 

Aber was, wenn die Daten als JSON eingereicht?

+1

würde ich vorschlagen, die Benutzer nach JSON-Validierung Validierung. Ich denke, die JSON-Validierung wird am besten verwendet, um sicherzustellen, dass Sie in das Scala-Objekt landen können, und dann wird alles, was Sie tun müssen, von da an komfortabler. – acjay

Antwort

16

Die einfachste Methode, die ich mir vorstellen kann, wäre die filter Methode von Reads.

Lassen Sie uns sagen, dass wir etwas User Objekt, das, wenn der Benutzername existiert bestimmen:

object User { 
    def findByName(name: String): Option[User] = ... 
} 

Sie könnten dann konstruieren Reads wie folgt aus:

import play.api.libs.json._ 
import play.api.libs.functional.syntax._ 
import play.api.data.validation._ 

case class ExampleCaseClass(username: String, somethingElse: String) 

object ExampleCaseClass { 
    implicit val exampleReads: Reads[ExampleCaseClass] = (
     (JsPath \ "username").read[String].filter(ValidationError("User does not exist."))(findByName(_).isDefined) and 
     (JsPath \ "somethingElse").read[String] 
    )(ExampleCaseClass.apply _) 
} 

Ihre Controller-Funktion kann mit Hilfe vereinfacht werden a json BodyParser und fold:

def postNew = Action(parse.json) { implicit request => 
    request.body.validate[ExampleCaseClass].fold(
     error => BadRequest(JsError.toFlatJson(error)), 
     obj => { 
      // Do something with the validated object.. 
     } 
    ) 
} 

Sie könnten auch eine separate Reads[String] erstellen, wenn der Benutzer vorhanden überprüfen wird, und verwenden Sie ausdrücklich, dass Reads[String] in Ihrem Reads[ExampleCaseClass]:

val userValidate = Reads.StringReads.filter(ValidationError("User does not exist."))(findByName(_).isDefined) 

implicit val exampleReads: Reads[ExampleCaseClass] = (
    (JsPath \ "username").read[String](userValidate) and 
    (JsPath \ "somethingElse").read[String] 
)(ExampleCaseClass.apply _) 
+0

m-z die Sache ist, dass, wenn Sie viele Validatoren wie folgt verwenden, nachfolgende werden immer noch ausgeführt, wenn der vorherige fehlgeschlagen ist. Siehe zum Beispiel: http://stackoverflow.com/questions/31077937/force-play-scala-json-composite-validators-to-fail-on-first-failed-validator –

+0

@FelipeAlmeida Im Allgemeinen ist es besser zu akkumulieren Fehler, anstatt auf den ersten zu stoppen (deshalb ist die Bibliothek so gebaut). Deshalb würde ich persönlich keinen JSON-Validator verwenden, der einen Datenbankaufruf wie diesen macht und lieber anderswo behandelt. Aber das ist ein Thema zu dieser Frage. –

+1

Ich glaube, das funktioniert nicht, wenn Ihr findByName 'Future' oder DBIO (slick) zurückgibt, was mehr als wahrscheinlich ist. Die Validierungs-API des Play-Frameworks wurde nicht geschrieben, um absichtlich Futures zu unterstützen. – Ciantic

Verwandte Themen