*描述*
假设我们想要遍历一个引发了一些规范错误的规范,从嵌入在 explain-data 中的根规范到最后导致错误的那个谓词。
我们通常可以使用 {{:path}} 信息在 explain-data 中达到这个目的
user=> (s/explain-data (s/tuple integer? string?) [1 :a])
#:clojure.spec.alpha{:problems
({:path [1], ;; <- indicates the 1st subspec, ie. integer?, was the cause of the error
:pred clojure.core/string?,
:val :a,
:via [],
:in [1]}),
:spec ...,
:value [1 :a]}
user=>
如果我们沿着 {{:path}} 遍历规范树,最终可以到达引发规范错误的叶子谓词。
然而,在某些情况下,这可能不起作用,因为一些规范,例如 {{s/merge}}、{{s/and}} 和 {{s/&}},没有将任何线索放入 {{:path}} 中,告诉实际引发错误的子规范。
user=> (s/explain-data (s/merge (s/map-of integer? string?)
(s/coll-of (fn [[k v]] (= (str k) v))))
{1 "2"})
#:clojure.spec.alpha{:problems
({:path [], ;; <- doesn't tell us anything at all
:pred (fn [[k v]] (= (str k) v)), ;; <- we don't know which subspec this pred occurs in
:val [1 "2"],
:via [],
:in [0]}),
:spec ...,
:value {1 "2"}}
user=>
为了在这些情况下达到我们的目的,我们必须做出一个非确定性选择:也就是说,任意选择一个子规范并尝试向下遍历它,如果在途中出现问题,就回溯到另一个子规范,依此类推。
根据我在执行库([仓库|
**建议**
为了更容易地实现规范遍历,此票据建议向 {{:path}} 添加索引,以指示哪个子规范为 {{s/merge}}、{{s/and}} 和 {{s/&}} 抛出规范错误,如下所示
user=> (s/explain-data (s/merge (s/map-of integer? string?)
(s/coll-of (fn [[k v]] (= (str k) v))))
{1 "2"})
#:clojure.spec.alpha{:problems
({:path [1], ;; <- 表示第1个子规范,即 (s/coll-of (fn [[k v]] (= (str k) v))) 包含了错误的实际原因
:pred (fn [[k v]] (= (str k) v)),
:val [1 "2"],
:via [],
:in [0]}),
:spec ...,
:value {1 "2"}}
user=>
虽然这一增强确实是一个破坏性变更,但应极大地减少编写沿 {{:path}} 规范遍历代码所需的努力。