h3. 问题
在运行时边界验证支持多种交换格式时使用{{clojure.spec}}很困难。
h3. 详细信息
当前在clojure.spec(alpha-14)中,验证器在创建Spec实例时附加到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. 建议
建议在Spec协议中支持选型符合,并提供一个新的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。第三方库可以有新的{{Conforming}}协议,其中包含3参数的{{conform}},并在所有当前规格上实现它。目前这是不可能的。
[1]
https://github.com/metosin/spec-tools