关于clojure.spec:如何检查Clojure规格的可分辨性?

How to check the resolvability of Clojure specs?

clojure.spec.alpha允许在定义新规范时使用无法解析的规范:

1
(s/def :foo/bar (s/or :nope :foo/foo))

在这里,:foo/foo无法解决,因此使用:foo/bar会在用法上引发异常:

1
2
(s/valid? :foo/bar 42)
;; Exception Unable to resolve spec: :foo/foo  clojure.spec.alpha/reg-resolve! (alpha.clj:69)

当我输入:my-ns/my-spec而不是::my-ns/my-spec时,这是我的代码中发生的事情。我想抓住那些进行单元测试的人。

深入研究clojure.spec.alpha源代码,发现我可以使用(keys (s/registry))获得所有规格,因此我的测试如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
(ns my-ns.spec-test
  (:require [clojure.test :refer :all]
            [clojure.spec.alpha :as s]
            ;; :require all the relevant namespaces to populate the
            ;; global spec registry.
            [my-ns.spec1]
            [my-ns.spec2]))

(deftest resolvable-specs
  (doseq [spec (keys (s/registry))]
    (is (resolvable? spec))))
    ;;   ^^^^^^^^^^^ placeholder; thata€?s the function I want

不幸的是,这不是clojure.spec.alpha中的s/resolvable?之类的东西。到目前为止,我找到的唯一解决方案是调用(s/valid? spec 42)并假定它没有引发异常意味着它可以解决,但它不检查所有分支:

1
2
3
4
5
6
7
8
9
10
11
12
(s/def :int/int int?)
(s/def :bool/bool bool?)

(s/def :my/spec (s/or :int :int/int
                      :other (s/or :bool bool/bool
                                   :nope :idont/exist)))

(s/valid? :my/spec 1) ; <- matches the :int branch
;; => true

(s/valid? :my/spec :foo)
;; Exception Unable to resolve spec: :idont/exist  clojure.spec.alpha/reg-resolve! (alpha.clj:69)

我检查了异常堆栈跟踪以及源代码,以查看是否可以找到任何功能来完全解析规格,而无需使用上面的42:foo这样的测试值,但找不到任何功能。铅>

对于给定的规范,是否有办法检查它在其所有分支中引用的所有规范是否存在?


我能够执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
(ns my-ns.utils
  (:require [clojure.spec.alpha :as s]))

(defn- unresolvable-spec
  [spec]
  (try
    (do (s/describe spec) nil)
    (catch Exception e
      (if-let [[_ ns* name*] (re-matches #"Unable to resolve spec: :([^/]+)/(.+)$" (.getMessage e))]
        (keyword ns* name*)
        (throw e)))))

(defn unresolvable?
 "Test if a spec is unresolvable, and if so return a sequence
   of the unresolvable specs it refers to."
  [spec]
  (cond
    (symbol? spec)
      nil

    (keyword? spec)
      (if-let [unresolvable (unresolvable-spec spec)]
        [unresolvable]
        (not-empty (distinct (unresolvable? (s/describe spec)))))

    (seq? spec)
      (case (first spec)
        or (->> spec (take-nth 2) rest (mapcat unresolvable?))
        and (->> spec rest (mapcat unresolvable?))

        ;; undecidable
        nil)

    :default (unresolvable-spec spec)))

(def resolvable? (complement unresolvable?))

它可以在s/ands/or上运行,这是我的最小用例:

1
2
3
(u/resolvable? :int/int) ;; => true
(u/resolvable? :my/spec) ;; => false
(u/unresolvable? :my/spec) ;; => (:idont/exist)

但是它有一些缺陷:

  • 它重新发明了轮子;我认为这些规范遍历功能已经存在于clojure.spec.alpha
  • 它依赖于捕获异常然后解析其消息,因为(1)clojure.spec.alpha不具有不会引发异常的函数,并且(2)引发一个异常的函数不使用任何更具体的函数比Exception

如果有人有更强壮的东西,我很乐意接受其他答案。