h3. 问题
在运行时边界验证中支持多种交换格式时使用{{clojure.spec}}很困难。
h3. 详情
目前在 clojure.spec (alpha-14) 中,符合器在创建 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 参数 {{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)))
;; 字符串
(assert (= conformed-person (s/conform ::person string-person string-conforming-matcher)))
h3. 替代方案
支持这种方式的另一种选择是允许用协议扩展Spec。第三方库可以有一个新的{{Conforming}}协议,它包含3个参数的{{conform}},并在所有当前规范中添加对此协议的实现。目前这还不可能。
[1]
https://github.com/metosin/spec-tools