h3. 问题
在运行时边界的验证中,使用{{clojure.spec}}支持多个交换格式是非常困难的。
h3. 详细信息
当前在clojure.spec(alpha-14)中,conformer是在创建Spec实例时附加的,并且在每次conform时都会调用它们。这在系统边界验证中并不很有用,在这种验证中,应根据运行时数据选择conform/coercion函数,例如交换格式。
示例
** 一种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)
要使用这些,现在需要手动为所有不同的交换格式创建新的边界规范,并为每个格式创建具有不同conformer的新规范。非限定关键字可以映射到{{s/keys}}中工作(例如,{{::title}} => {{::title$JSON}}),但如果完全限定的键在边界上公开(如示例中的{{::id}}),将不起作用 - 不能为相同的规范名注册多个具有不同转换功能的规范版本。
h3. 建议
在Spec协议中支持选择性conforming,使用新的3参数{{conform*}}和{{clojure.spec/conform}},两者都接受一个额外的用户提供的回调/访问器函数。如果提供了回调,则从Spec的{{conform*}}中调用它,并将当前规范作为参数传递。它将返回{{nil}}或一个2参数conformer函数,该函数应用于实际conform。
实际的conforming-matcher实现可以在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)))
h3. 代替方案
支持这一功能的另一种方法是允许Spec通过协议进行扩展。第三方库可以有一个新的{{Conforming}}协议,其中包含3参数的{{conform}}函数,并为其添加所有当前spec的实现。目前这不可能。
[1]
https://github.com/metosin/spec-tools