h3. 问题
在运行时边界的验证中,支持多种交换格式时使用 {{clojure.spec}} 难度较大。
h3. 详细信息
目前,在 clojure.spec(alpha-14)中,验证器在创建 Spec 实例时附加到 Spec 实例上,并在每次验证时调用。这在系统边界验证中并不是非常有用,在系统边界验证中,验证/强制转换函数应根据运行时数据选择,例如交换格式。
示例
* 一个 {{keyword?}} 规范
** 使用 EDN,不应进行强制转换(它可以呈现关键词)
** 使用 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. 建议
在 Spec 协议中支持选择性验证,通过一个新的三参数 {{conform*}} 和 {{clojure.spec/conform}},两者都接受一个额外的用户提供的回调/访问者函数。如果提供了回调,则从 Specs 的 {{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)))
;; string
(断言 (= conformed-person (s/conform ::person string-person string-conforming-matcher)))
h3. 替代方案
支持这种方法的另一种选项是允许Spec通过协议进行扩展。第三方库可以创建一个带有3元 {{Conforming}} 协议的 {{conform}},并在所有当前规范中为其添加实现。目前这还不可能。
[1]
https://github.com/metosin/spec-tools