h3. 问题
在运行时边界验证中支持多种交换格式时使用{{clojure.spec}}很困难。
h3. 详细信息
目前,在clojure.spec(alpha-14)中,验证器在创建时附加到Spec实例上,并在每次匹配时被调用。这在系统边界验证中不是非常有用,在这种情况下,应根据运行时数据(例如,交换格式)选择匹配/强制转换函数。
示例
* a {{keyword?}} 规范
** 使用EDN,不应进行强制转换(它可以表示关键字)
** 使用JSON,应应用String→Keyword强制转换
** 使用基于字符串的格式(CSV,查询参数...),应应用String→Keyword强制转换
* a {{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函数参数{{conform*}}和{{clojure.spec/conform}},两者都接受额外的用户提供的回调/访问者函数。如果提供回调,它将在Spec的{{conform*}}内部使用当前规范作为参数调用,并返回一个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。第三方库可以拥有一个符合3个参数的新{{Conforming}}协议,并在所有当前Spec中添加其实例。目前这是不可能的。
[1]
https://github.com/metosin/spec-tools