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}},同时调用一个额外的用户提供的回调/访问者函数。如果提供了回调,它将根据当前规范作为参数从 Specs {{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. 替代方案
另一种支持此功能的方法是允许通过协议扩展规范。第三方库可以有一个新的 {{Conforming}} 协议,带有 3-参数 {{conform}} 并添加对该协议的所有当前规范的实现。目前这不可能。
[1]
https://github.com/metosin/spec-tools