h3. 问题
在运行时边界验证中支持多个交换格式时使用 {{clojure.spec}} 很困难。
h3. 详细信息
目前在进行 clojure.spec (alpha-14) 时,符合者在创建 Spec 实例时附加到 Spec 实例上,并且每次符合都会触发它们。这在系统边界验证中不太有用,在这个验证中,符合/强制转换函数应根据运行时数据选择,例如交换格式。
示例
* a {{keyword?}} spec
** 使用 EDN,不应进行强制转换(它可以表示关键字)
** 使用 JSON,应应用 String->Keyword 强制转换
** 使用基于字符串的格式(CSV、查询参数等),应应用 String->Keyword 强制转换
* a {{integer?}} spec
** 使用 EDN,不应进行强制转换(它可以表示数字)
** 使用 JSON,不应进行强制转换(它可以表示数字)
** 使用基于字符串的格式(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}}),则不会工作 - 您不能将相同名称的不同符合 spec 的多个版本注册为不同名称。
h3. 建议
在 Spec 协议中支持选择性符合,使用新的 3 个参数 {{conform*}} 和 {{clojure.spec/conform}},它们都包含一个额外的由用户提供的回调/访问者函数。如果提供了回调,它将在 Specs 的 {{conform*}} 中由当前 spec 作为参数调用,并返回要么是 {{nil}},要么是用于实际符合的 2 个参数的符合器函数。
实际的符合器实现可以维护在第三方库中,如 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参数的{{conform}}实现,并在所有当前Spec上添加对此协议的实现。目前,这还不可能。
[1]
https://github.com/metosin/spec-tools