简体   繁体   English

如何在编译时以编程方式创建验证合同?

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

I apologize in advance if this is an XY problem. 如果这是XY问题,我先向您道歉。

tl;dr: TL;博士:

I'd like to have a compile-time map of type [Request.type, Response.type] so I can effectively say if I send message Request , a CLI should, at compile-time, know how to deserialize its expected Response , irrespective of the fact that it won't know what type of request is sent until runtime. 我想要一个类型为[Request.type, Response.type]的编译时映射,因此我可以有效地说出如果我发送消息Request ,那么CLI应该在编译时知道如何反序列化其预期的Response ,不管它在运行时之前都不知道发送哪种类型的请求。

too long; 太长; still read: 仍然阅读:

I have a CLI which communicates with an HTTP server and depending on the type of message sent to the HTTP server, I'd like to validate the JSON response against a case case. 我有一个与HTTP服务器通信的CLI,并且根据发送到HTTP服务器的消息类型,我想针对一个案例验证JSON响应。

For instance, if I send the HTTP server an AddFoo message, I might want to validate that the JSON response can be deserialized into an AddedFoo , etc. 例如,如果我向HTTP服务器发送AddFoo消息,则可能需要验证JSON响应是否可以反序列化为AddedFoo等。

My current solution is quite hacky. 我当前的解决方案很hacky。 Using play-json, I'm attempting to parse the JSON response using a mapping from config.mode (ie, command issued to the CLI) to the expected responses' implicit Reads . 我使用play-json尝试使用从config.mode (即,发布给CLI的命令)到预期响应的隐式Reads的映射来解析JSON响应。

My code looks something like this: 我的代码如下所示:

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)
  }
}

While this works, it's an incredible kludge that becomes increasingly unmaintainable with an increasing number of messages. 尽管这有效,但随着消息数量的增加,这种不可思议的麻烦变得越来越难以维护。 Further, I've found that this pattern will need to be replicated anywhere some type of validate or conversion will need to happen (eg, Future[Any] being converted to Future[AddedFoo] ). 此外,我发现该模式将需要在需要进行某种类型的验证或转换的任何地方复制(例如, Future[Any]被转换为Future[AddedFoo] )。

Surely my approach isn't the right way... how is this traditionally done? 我的方法肯定不是正确的方法...传统上是如何完成的? If it is the right way (please no), are there optimizations that can be made? 如果这是正确的方法(请否),是否可以进行优化?

I managed to accomplish this by encoding the contract directly into the child Request classes. 我设法通过将合同直接编码到子Request类中来完成此任务。 Namely, the child Request classes would hold a ResponseType type with the base class enforcing the covariant type. 即,子Request类将包含ResponseType类型,而基类将强制协变类型。

So I can do something like this: 所以我可以做这样的事情:

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.

相关问题 在编译期间用 Dlang 迭代 JSON - Iterate JSON during compile-time, in Dlang 如何测试包含java.time.Instant字段的spring-cloud-contract - How can I test a spring-cloud-contract containing a java.time.Instant field 我如何选择退出JSON合同解析程序 - How can I opt out of a JSON contract resolver 为什么Gson会序列化列表中的运行时类型,而不是指定编译时类型? - Why does Gson serializes runtime type in list, not specified compile-time type? MapBox:如何以编程方式创建 featureCollection? - MapBox: How do I create a featureCollection programmatically? 如何将 JS 和 JSON 一起编译? - How can I compile JS and JSON together? 如何使用Spring Cloud Contract从Groovy中的RESTful服务调用获取/打印JSON响应 - How can I get/print the JSON response from a RESTful service call in Groovy with Spring Cloud Contract 如何在MarkLogic中以编程方式在XQuery中创建JSON? - How do I programmatically create JSON in XQuery in MarkLogic? 如何在PlayFramework 2.1 Scala中以编程方式创建JSON对象 - How do I create JSON objects programmatically in playframework 2.1 scala 如何在 VS 代码中编译和运行我的 Pascal 代码? - How can I compile and run my Pascal code in VS code?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM