簡體   English   中英

如何在編譯時以編程方式創建驗證合同?

[英]How can I programmatically create a validation contract at compile-time?

如果這是XY問題,我先向您道歉。

TL;博士:

我想要一個類型為[Request.type, Response.type]的編譯時映射,因此我可以有效地說出如果我發送消息Request ,那么CLI應該在編譯時知道如何反序列化其預期的Response ,不管它在運行時之前都不知道發送哪種類型的請求。

太長; 仍然閱讀:

我有一個與HTTP服務器通信的CLI,並且根據發送到HTTP服務器的消息類型,我想針對一個案例驗證JSON響應。

例如,如果我向HTTP服務器發送AddFoo消息,則可能需要驗證JSON響應是否可以反序列化為AddedFoo等。

我當前的解決方案很hacky。 我使用play-json嘗試使用從config.mode (即,發布給CLI的命令)到預期響應的隱式Reads的映射來解析JSON響應。

我的代碼如下所示:

val modeToResponseReads: Map[String, Reads[_]] = Map(
  Modes.ADD_FOO -> AddedFoo.addedFooReads,
  Modes.ADD_BOO -> AddedBoo.addedBooReads,
  Modes.GET_WOO -> GetWooResponse.getWooReads,
)

parser.parse(args, MyConfig()) match {

  case Some(config) => try {
    val exec = new MyHttpExecutor(remoteUri, config)
    val res = Await.result(exec.getResponse, 100.seconds)

    // passing `Reads` to `as` because JsValue#as[T] cannot be
    // applied at runtime -- only compile-time.
    val _ = Json.parse(res.json.toString)
                .as(modeToResponseReads(config.mode))

    exec.actorSystem.terminate()
    exec.wsClient.close()
  } catch {
    case t: Throwable => logger.error(t.getMessage)
  }

  case None => {
    logger.error("Bad arguments.")
    sys.exit(1)
  }
}

盡管這有效,但隨着消息數量的增加,這種不可思議的麻煩變得越來越難以維護。 此外,我發現該模式將需要在需要進行某種類型的驗證或轉換的任何地方復制(例如, Future[Any]被轉換為Future[AddedFoo] )。

我的方法肯定不是正確的方法...傳統上是如何完成的? 如果這是正確的方法(請否),是否可以進行優化?

我設法通過將合同直接編碼到子Request類中來完成此任務。 即,子Request類將包含ResponseType類型,而基類將強制協變類型。

所以我可以做這樣的事情:

abstract class Response
abstract class Request[+A <: Response]

case class Foo(id: String)

object Foo {
  implicit val fooReads = Json.reads[Foo]
  implicit val fooFormat = Json.format[Foo]
}

case class FooResponse(foo: Foo) extends Response {
  def greet = println("woo hoo!")
}

object FooResponse {
  implicit val fooRespReads = Json.reads[FooResponse]
  implicit val fooRespFormat = Json.format[FooResponse]
}

case class FooRequest() extends Request[FooResponse] {
  type ResponseType = FooResponse
}

object Main extends App {
  val req: FooRequest = new FooRequest()
  val foo = Foo("12345")
  val resp = new FooResponse(foo)

  val respJsonString = Json.toJson(resp).toString
  println(Json.parse(respJsonString).as[req.ResponseType])
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM