h3. 问题
在运行时边界验证中,使用 clojure.spec 支持多种交换格式是困难的。
h3. 详细信息
目前在 clojure.spec (alpha-14) 中,验证器在创建 Spec 实例时附加,并且在每次验证时调用。这在系统边界验证中不是很有用,在系统边界验证中,应基于运行时数据选择验证/强制转换函数,例如交换格式。
示例
* a {{keyword?}} spec
** 使用 EDN,不应该进行强制转换(它可以呈现 Keywords)
** 使用 JSON,应该应用 String->Keyword 强制转换
** 使用基于字符串的格式(CSV、查询参数、...),应该应用 String->Keyword 强制转换
* a {{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. 建议
在 Spec 协议中支持选择性验证,通过一个新的 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)))
;; string
(assert (= conformed-person (s/conform ::person string-person string-conforming-matcher)))
h3. 替代方案
支持这种功能的另一种方法是允许规格通过协议进行扩展。第三方库可以创建一个新的{{Conforming}}协议,具有3-arity的{{conform}}函数,并为所有现有规范添加对此协议的实现。目前这还不可能。
[1]
https://github.com/metosin/spec-tools