h3. 问题
在运行时边界的验证中,多个交换格式支持使用 {{clojure.spec}} 难以实现。
h3. 详细信息
目前,在 clojure.spec(alpha-14)中,符合器在创建 Spec 实例时附加到 Spec,并且每次都调用它们进行符合。这在系统边界验证中不是很有用,在系统中,符合/强制函数应基于运行时数据选择,例如交换格式。
例子
* 一个 {{keyword?}} 规范
** 使用 EDN,不应进行强制操作(它可以展示关键字)
** 使用 JSON,应用 String->Keyword 的强制转换
** 使用基于 String 的格式(CSV、查询参数等),应用 String->Keyword 的强制转换
* 一个 {{integer?}} 规范
** 使用 EDN,不应进行强制操作(它可以展示数字)
** 使用 JSON,不应进行强制操作(它可以展示数字)
** 使用基于 String 的格式(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. 建议
通过一个名为 {{conform*}} 的新 3-arity 接口和 {{clojure.spec/conform}} 支持 Spec 协议中的选择性符合,它们都接受一个额外的用户提供的回调/访问器函数。如果提供了回调,则在 Spec 的 {{conform*}} 中以当前规范为参数调用它,并将返回一个 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)))
;; 字符串
(assert (= conformed-person (s/conform ::person string-person string-conforming-matcher)))
h3. 替代方案
支持这一功能的另一种选择是将协议扩展到规范。第三方库可以拥有一个符合新 {{Conforming}} 协议的 3-arity {{conform}},并为其在所有现有规范中添加实现。目前这还不可能。
[1]
https://github.com/metosin/spec-tools