问题
使用 {{clojure.spec}} 进行运行时边界验证支持多个交换格式是很难的。
详细信息
目前,在 clojure.spec (alpha-14) 中,合规器在创建 Spec 实例时附加到 Spec 上,并且每次进行合规化时都会被调用。在系统边界验证中,这不是很有用,在系统边界验证中,应根据运行时数据选择合规化/强制转换函数,例如交换格式。
示例
* 一个 {{keyword?}} spec
** 使用 EDN,不应该进行强制转换(它可以表示关键词)
** 使用 JSON,应该应用 String→Keyword 强制转换
** 使用字符串格式(CSV、查询参数等),应该应用 String→Keyword 强制转换
* 一个 {{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}} => {{::title$JSON}}),但如果边界上公开了完整限定键(如示例中的 {{::id}})则不起作用 - 不能用相同名称注册具有不同合规器的规范。
建议
在 Spec 协议中支持选择性合规化,使用新的 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)))
;; string
(assert (= conformed-person (s/conform ::person string-person string-conforming-matcher)))
替代方案
支持这种方式的另一种选择是允许规范通过协议进行扩展。第三方库可以通过拥有一个新的符合协议的3-参数conform方法和为所有当前规范添加实现来实现它。目前这还不可能。
[1]
https://github.com/metosin/spec-tools