h3. 问题
在使用clojure.spec进行运行时边界验证并支持多种交换格式时,使用{{clojure.spec}}很困难。
h3. 详细说明
目前在clojure.spec(alpha-14)中,合规器在创建Spec实例时附加到Spec上,并且每次合规都会调用它们。这对于系统边界验证来说不是很有用,在边界验证中,应根据运行时数据选择合规/强制转换函数,例如交换格式。
示例
* 一个{{keyword?}}规范
** 使用EDN,不应进行强制转换(它可以表示keywords)
** 使用JSON,应用String->Keyword强制转换
** 使用基于String的格式(CSV、查询参数等),应用String->Keyword强制转换
* 一个{{integer?}}规范
** 使用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)
要使用此工具,您需要手动为所有不同的交换格式创建包含不同合规器的新的边界规范。未限定关键词可以映射到sh/keys中(例如,{{::title}}=>{{::title$JSON}}),但如果在边界暴露了完全限定键(例如示例中的{{::id}}),则此方法不会起作用 - 您不能使用相同名称注册不同合规版本的规范。
h3. 建议
建议在规范协议中支持选型合规,使用新的3参数{{conform*}}和{{clojure.spec/conform}},两者都接受一个额外的用户提供的回调/访问者函数。如果提供了回调,则从Spec的{{conform*}}内部调用它,并将当前规范作为参数传递,它将返回{{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)))
;; 字符串
(assert (= conformed-person (s/conform ::person string-person string-conforming-matcher)))
h3. 替代方案
支持此功能的另一个选择是允许使用协议来扩展Spec。第三方库可以有一个新的{{Conforming}}协议,具有3-arity {{conform}},并在所有当前Spec中添加它的实现。目前这是不可能的。
[1]
https://github.com/metosin/spec-tools