json - 如何在 Play 2.7 for Scala 中编写一个通用的 JSON 解析器来验证入站请求?
问题描述
我在 Scala 中有一个 Play 2.7 控制器,它根据案例类模式验证入站 JSON 请求,并报告入站请求有效负载错误(请注意,我从更大的代码库中提取了这个示例,试图保持其正确的可编译性和功能,尽管可能存在小错误):
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))
})
}
}
向“handleRequest”端点发送 POST 请求时,验证的工作方式如下:
- 使用有效负载
{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 自己的关于 Action composition 的文档中提取,但我没有成功地把事情做好,所以我留下了所有的碎片,希望有人能帮助我把它们一起工作成有用的东西。
如何更改第二个代码示例以编译并在功能上与第一个代码示例相同?
解决方案
我通过使用隐式类归档了类似的目标ActionBuilder
:
trait ActionBuilderImplicits {
implicit class ExActionBuilder[P](actionBuilder: ActionBuilder[Request, P])(implicit cc: ControllerComponents) {
def validateJson[A](implicit executionContext: ExecutionContext, reads: Reads[A]): ActionBuilder[Request, A] = {
actionBuilder(cc.parsers.tolerantJson.validate(jsValue => {
jsValue.validate.asEither.left
.map(errors => BadRequest(JsError.toJson(errors)))
}))
}
}
}
object ActionBuilderImplicits extends ActionBuilderImplicits
然后在控制器中,您可以将其导入ActionBuilderImplicits
并用作
Action.validateJson[A].async { request =>
processingService.process(request.body)
}
这里request.body
已经是类型A
推荐阅读
- java - Java 文件从另一个分区中的路径创建
- python - 在 matplotlib 中使用 3 个变量绘制条件
- mysql - 如何将mysql数据库流量镜像到另一台服务器
- angular - 角度 6 中的角度数据表、嵌套行对齐问题和 dtOptions 未加载
- c# - C# WinForms 帮助实现 BackgroundWorker 和加载表单
- excel - 覆盖单元格excel VBA的重新格式化
- python - 如何表示用于神经网络的音频文件?
- excel - 将数据从一张纸转移到另一张纸
- python - 使用“tarfile”时跳过损坏的档案 (.tar.gz)
- python - 如何解决串行通信中的绘图问题?