h3. 问题
在运行时边界验证中,使用 {{clojure.spec}} 支持多个交换格式很困难。
h3. 详细信息
目前,在clojure.spec (alpha-14) 中,验证器在创建Spec实例时附加到Spec实例,并且在每个验证中调用。这在系统边界验证中不太有用,在系统边界验证中,应根据运行时数据选择验证/强制转换函数,例如交换格式。
示例
* 一个 {{keyword?}} spec
** 对于EDN,不应执行任何强制转换(它可以表示关键字)
** 对于JSON,应应用String->Keyword强制转换
** 对于基于字符串的格式(CSV、查询参数等),应应用String->Keyword强制转换
* 一个 {{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}}) - 您不能使用同名的多个不同验证的规格版本。
h3. 建议
通过新的3-arity {{conform*}} 和 {{clojure.spec/conform}} 支持Spec协议中的选择性验证,两者都接受一个额外的用户提供的回调/访问者函数。如果提供了回调,则在Spec的{{conform*}}内调用它,并以当前规格作为参数,它将返回一个或一个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)))
;; 字符串
(断言 (= conformed-person (s/conform ::person string-person string-conforming-matcher)))
h3. 替代方案
支持此功能的另一个选项是允许规格通过协议进行扩展。第三方库可以拥有一个新的 {{Conforming}} 协议,其有三个参数的 {{conform}},并在所有当前规范中为其添加实现。目前,这不可行。
[1]
https://github.com/metosin/spec-tools