Пользовательские ограничения проверки JSON в Play Framework 2.3 (Scala)
Мне удалось реализовать проверку формы с пользовательскими ограничениями, но теперь я хочу сделать то же самое с данными JSON.
Как я могу применить специальные правила валидации к парсеру JSON?
Пример: запрос POST клиента содержит имя пользователя (username
) и не только я хочу убедиться, что этот параметр является непустым текстом, но также и то, что этот пользователь действительно существует в базе данных.
// 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 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 _)
}
Это, насколько я понимаю, но это только гарантирует, что username
является строкой. Как применить мое дополнительное правило пользовательской проверки, например. проверить, действительно ли данный пользователь существует? Возможно ли это?
Конечно, я мог бы взять мой obj
в разделе case success
в действии и выполнить дополнительные проверки там, но это не кажется очень изящным, потому что тогда мне придется создать свое собственное сообщение об ошибке и только пользователь JsError.toFlatJson(error)
для некоторых случаев. После поиска и поиска в течение нескольких часов я не мог найти никаких примеров.
Для обычных форм я бы использовал что-то вроде этого:
// 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 action method...
exampleForm.bindFromRequest.fold(
formWithErrors => {
BadRequest("Example error message")
},
formData => {
// do something
Ok("Valid!")
}
)
Но что, если данные передаются как JSON?
Ответы
Ответ 1
Самый простой способ, которым я могу думать, будет использовать метод filter
из Reads
.
Скажем, у нас есть объект User
, который определит, существует ли имя пользователя:
object User {
def findByName(name: String): Option[User] = ...
}
Затем вы можете создать свой Reads
следующим образом:
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 _)
}
Ваша функция контроллера может быть упрощена с помощью json BodyParser
и 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..
}
)
}
Вы также можете создать отдельный Reads[String]
, который будет проверять, существует ли пользователь, и явно использовать Reads[String]
в вашем 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 _)