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. 建议
使用新的 3-arity {{conform*}} 和 {{clojure.spec/conform}} 支持在 Spec 协议中选择性合规,它们都接受额外的用户提供的回调/访问者函数。如果提供了回调,它将在 Specs {{conform*}} 内部调用,以当前规范作为参数,它将返回 {{nil}} 或应用于实际 confrom 的 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)))
;; string
(assert (= conformed-person (s/conform ::person string-person string-conforming-matcher)))
h3. 替代方案
支持该功能的另一种方法是允许使用协议来扩展Spec。第三方库可以拥有一个新的{{Conforming}}协议,具有3个参数的{{conform}}函数,并为所有当前的规格添加该协议的实现。目前,这是不可能的。
[1]
https://github.com/metosin/spec-tools