h3. 问题
使用{{clojure.spec}}进行运行时边界验证支持多种交换格式是很困难的。
h3. 详情
目前在clojure.spec (alpha-14) 中,合并器在创建Spec实例时附加到Spec实例,并在每次合并时调用。这在系统边界验证中不是很实用,而在系统边界验证中,合并/强制函数应根据运行时数据选择,例如交换格式。
示例
* a {{keyword?}} spec
** 使用EDN,不应进行强制转换(它可以表示关键字)
** 使用JSON,应应用String->Keyword强制转换
** 使用基于字符串的格式(CSV,查询参数等),应应用String->Keyword强制转换
* 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 > ::title$JSON),但如果完全限定的关键字暴露在边界之上(例如示例中的::id)则不会起作用 - 无法使用相同名称注册多个不同格式的规格版本。
h3. 建议
通过新的3参数函数conform*和clojure.spec/conform在Spec协议中支持选择性合并,这两个函数都接受额外的用户提供的回调/访问者函数。如果提供了回调,则在其内部从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
(断言 (= conformed-person (s/conform ::person string-person string-conforming-matcher)))
3. 代替方案
另一种支持这种功能的方法是允许Spec通过协议扩展。第三方库可以有一个新的{{Conforming}}协议,它有一个3-arity的{{conform}},并在所有当前Spec上添加实现。目前这是不可能的。
[1]
https://github.com/metosin/spec-tools