2017-03-02 59 views
0

如果这是一个XY问题,我很抱歉。如何在编译时以编程方式创建验证合同?

TL;博士:

我想有[Request.type, Response.type]类型的编译时间图,所以我可以有效地说,如果我发送消息Request,一个CLI应,在编译时,知道如何反序列化其预期的Response,而不管它直到运行时才知道发送了什么类型的请求。

太长;仍然阅读:

我有一个CLI与HTTP服务器通信,并根据发送到HTTP服务器的消息类型,我想验证JSON响应对案例。

举例来说,如果我发送HTTP服务器的AddFoo消息,我可能要验证JSON响应可以反序列化到AddedFoo

我目前的解决方案是相当哈克。使用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])。

当然,我的方法是不正确的......这是传统上的做法吗?如果方法正确(请不要),是否可以优化?

+2

你的意思是,在*运行时*过去了,发出HTTP请求时,意思? –

+1

我只会尝试在类型都扩展一些密封特征或抽象类的时候这样做,因为只有类匹配才能帮助您在此之后进行排序。 –

+0

@MichaelZajac所有的请求都会扩展'MyBaseRequest',所有的响应都会扩展'MyBaseResponse'。我发现这是一个相当顽皮的问题......我认为这将是一个众所周知的模式。 :) – erip

回答

0

我设法通过将合约直接编码到子类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]) 
}