h3. 问题
在运行时边界的验证中,使用 {{clojure.spec}} 支持多种交换格式是困难的。
h3. 详情
目前在 clojure.spec (alpha-14) 中,合规器在创建 Spec 实例时附加,并且每次合规时都会调用它们。这对于系统边界验证来说不是很有用,在这种验证中,合规/强制转换函数应根据运行时数据选择,例如交换格式。
示例
* 一个 {{keyword?}} spec
** 使用 EDN,不应进行强制转换(它可以显示 Keywords)
** 使用 JSON,应该应用 String->Keyword 强制转换
** 使用基于 String 的格式(CSV、查询参数、...),应应用 String->Keyword 强制转换
* 一个 {{integer?}} spec
** 使用 EDN,不应进行强制转换(它可以显示数字)
** 使用 JSON,不应进行强制转换(它可以显示数字)
** 使用基于 String 的格式(CSV、查询参数、...),应应用 String->Long 强制转换
下面是一个更完整的例子
(s/def ::id integer?)
(s/def ::name string?)
(s/def ::title keyword?)
(s/def ::person (s/keys :opt [::id], :req-un [::name ::title]))
;; 这是我们如何看到不同交换格式中的数据
(def edn-person {::id 1, :name "Tiina", :title :boss})
(def json-person {::id 1, :name "Tiina", :title "boss"})
(def string-person {::id "1", :name "Tiina", :title "boss"})
;; 这是我们想要的
(def conformed-person edn-person)
为了今天使用它,需要手动为所有不同的交换格式创建具有不同合规器的新的边界规范。非限定符关键词可以映射到 {{s/keys}} 来工作(例如,{{::title}} => {{::title$JSON}}),但这不适用于完全限定符键暴露在边界上(例如示例中的 {{::id}}) - 无法以相同名称注册多个、不同合规的规范版本。
h3. 建议
通过新的 3-arity {{conform*}} 和 {{clojure.spec/conform}} 支持在 Spec 协议中进行选择性合规,这两个函数都接受一个额外的用户提供的回调/访问者函数。如果提供了回调,它将在 Spec 的 {{conform*}} 调用中调用,以当前规范为参数,并将返回 {{nil}} 或一个 2-arity 合规器函数,该函数应用于实际合规。
实际的合规匹配器实现可以维护在第三方库中,例如 spec-tools[1]。
使用方法如下
;; edn
(assert (= conformed-person (s/conform ::person edn-person)))
(assert (= conformed-person (s/conform ::person edn-person nil)))
;; json
(assert (= conformed-person (s/conform ::person json-person json-conforming-matcher)))
;; string
(assert (= conformed-person (s/conform ::person string-person string-conforming-matcher)))
h3. 选择方案
支持这一点的另一种方法允许Spec通过协议进行扩展。第三方库可以有一个新的{{Conforming}}协议,具有3-arity的{{conform}},并在所有当前规范中添加对其的实现。目前这是不可能的。
[1]
https://github.com/metosin/spec-tools