Подтвердить что ты не робот

Пользовательские ограничения проверки 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?

4b9b3361

Ответ 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 _)