h3. 问题
在运行时边界验证中,使用 {{clojure.spec}} 支持多种交换格式是困难的。
h3. 详情
当前在 clojure.spec (alpha-14) 中,符合器在创建 Spec 实例时附加,并在每次符合时调用。这在系统边界验证中并不很有用,在系统边界验证中,符合/强制转换函数应根据运行时数据选择,例如交换格式。
示例
* 一个 {{keyword?}} 规范
** 使用 EDN,不应执行强制转换(它可以显示 Keyword)
** 使用 JSON,应应用 String->Keyword 强制转换
** 使用基于字符串的格式(CSV、查询参数等),应应用 String->Keyword 强制转换
* 一个 {{integer?}} 规范
** 使用 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}}) - 一个不能以相同的名称注册多个、不同符合器的规范版本。
h3. 建议
建议在规范协议中使用新的 3-arity {{conform*}} 和 {{clojure.spec/conform}} 支持选择性符合,两者都接受额外的用户提供的回调/访问函数。如果提供了回调,它将从规范的内侧的 {{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. 替代方案
另一种支持此方法的方法是允许使用协议扩展规范。第三方库可以通过一个新的 {{Conforming}} 协议以及为其添加对所有当前规范的实施情况进行此操作。目前这是不可能的。
【1】
https://github.com/metosin/spec-tools