h3. 问题
在运行时边界验证中,支持多个交换格式时使用 clojure.spec 很困难。
h3. 详细信息
在 clojure.spec (alpha-14) 中,合规器在创建 Spec 实例时附加到 Spec 实例,并在每次合规时调用。这在系统边界验证中并不很有用,在这种验证中,合规/转换函数应根据运行时数据选择,例如交换格式。
示例
* a {{keyword?}} spec
** 使用EDN,不应执行转换(它可以表示关键字)
** 使用JSON,应该应用String->关键字转换
** 使用基于字符串的格式(CSV、查询参数、...),应该应用String->关键字转换
* 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}} &=gt; {{::title$JSON}}),但如果完全限定的键在边界上公开(例如例子中的 {{::id}}),则不行 - 不能用相同的名称注册多个,合规方式不同的规范版本。
h3. 建议
在规范协议中支持选择合规性,使用新的 3 参数 {{conform*}} 和 {{clojure.spec/conform}},都包含一个额外的用户提供的回调/访问器函数。如果提供了回调,它将从规范 {{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)))
;; 字符串
(assert (= conformed-person (s/conform ::person string-person string-conforming-matcher)))
h3. 替代方案
支持这一功能的另一个选项是允许使用协议扩展Spec。第三方库可以有一个新的{{Conforming}}协议,具有3元数{{conform}}的实现,并在所有当前Spec上添加对其的实现。目前这是不可能的。
[1]
https://github.com/metosin/spec-tools