У меня есть контроллер Play 2.7 в Scala, который проверяет входящие запросы JSON на соответствие схеме класса case и сообщает об ошибках полезной нагрузки входящего запроса (обратите внимание, что я извлек этот пример из большей кодовой базы, пытаясь сохранить его правильную компилируемость и функциональность, хотя могут быть мелкие ошибки):
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success, Try}
import com.google.inject.Inject
import play.api.libs.json.{JsError, JsPath, JsSuccess, JsValue, Json, JsonValidationError}
import play.api.mvc.{AbstractController, Action, ControllerComponents, Request, Result}
class Controller @Inject() (playEC: ExecutionContext, cc: ControllerComponents) extends AbstractController(cc) {
case class RequestBody(id: String)
implicit val requestBodyFormat = Json.format[RequestBody]
private val tryJsonParser = parse.tolerantText.map(text => Try(Json.parse(text)))(playEC)
private def stringify(path: JsPath, errors: Seq[JsonValidationError]): String = {
s"$path: [${errors.map(x => x.messages.mkString(",") + (if (x.args.size > 0) (": " + x.args.mkString(",")) else "")).mkString(";")}]"
}
private def runWithRequest(rawRequest: Request[Try[JsValue]], method: (RequestBody) => Future[Result]): Future[Result] = {
rawRequest.body match {
case Success(validBody) =>
Json.fromJson[RequestBody](validBody) match {
case JsSuccess(r, _) => method(r)
case JsError(e) => Future.successful(BadRequest(Json.toJson(e.map(x => stringify(x._1, x._2)).head)))
}
case Failure(e) => Future.successful(BadRequest(Json.toJson(e.getMessage.replaceAll("\n", ""))))
}
}
// POST request processor
def handleRequest: Action[Try[JsValue]] = Action(tryJsonParser).async { request: Request[Try[JsValue]] =>
runWithRequest(request, r => {
Future.successful(Ok(r.id))
})
}
}
Проверка работает следующим образом при отправке запроса POST в конечную точку «handleRequest»:
- с полезной нагрузкой
{malformed,,
я получаю ответ 400 сUnexpected character ('m' (code 109)): was expecting double-quote to start field name at [Source: (String)"{malformed,,"; line: 1, column: 3]
. - с полезной нагрузкой
{}
я получаю ответ 400 с/id: [error.path.missing]
Что я хотел бы сделать, так это сделать синтаксический анализ и проверку универсальными, переместив эту логику в служебный класс для наиболее чистого повторного использования, возможного в методе handleRequest
. Например, примерно так:
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success, Try}
import com.google.inject.{Inject, Singleton}
import play.api.{Configuration, Logging}
import play.api.libs.json.{JsError, JsPath, JsSuccess, JsValue, Json, JsonValidationError}
import play.api.mvc.{AbstractController, Action, ActionBuilderImpl, AnyContent, BodyParsers, ControllerComponents, Request, Result}
object ParseAction {
// TODO: how do I work this in?
val tryJsonParser = parse.tolerantText.map(text => Try(Json.parse(text)))(playEC)
}
class ParseAction @Inject()(parser: BodyParsers.Default)(implicit ec: ExecutionContext) extends ActionBuilderImpl(parser) {
private def stringify(path: JsPath, errors: Seq[JsonValidationError]): String = {
s"$path: [${errors.map(x => x.messages.mkString(",") + (if (x.args.size > 0) (": " + x.args.mkString(",")) else "")).mkString(";")}]"
}
// TODO: how do I make this method generic?
override def invokeBlock[A](rawRequest: Request[A], block: (A) => Future[Result]) = {
rawRequest.body match {
case Success(validBody) =>
Json.fromJson[A](validBody) match {
case JsSuccess(r, _) => block(r).getFuture
case JsError(e) => Future.successful(BadRequest(Json.toJson(e.map(x => stringify(x._1, x._2)).head)))
}
case Failure(e) => Future.successful(BadRequest(Json.toJson(e.getMessage.replaceAll("\n", ""))))
}
}
}
class Controller @Inject() (cc: ControllerComponents) extends AbstractController(cc) {
case class RequestBody(id: String)
implicit val requestBodyFormat = Json.format[RequestBody]
// route processor
def handleRequest = ParseAction.async { request: RequestBody =>
Future.successful(Ok(r.id))
}
}
Я знаю, что этот код не компилируется как есть из-за вопиющего неправильного использования Scala и Play API, а не из-за небольших ошибок в коде. Я попытался использовать собственную документацию Play о композиции действий, но у меня не было успеха в том, чтобы сделать все правильно, поэтому я оставил все части в надежде, что кто-нибудь поможет мне объединить их во что-то, что работает.
Как я могу изменить этот второй пример кода, чтобы он компилировался и функционировал так же, как и первый пример кода?