如何检查 Clojure 规范的可解析性?
How to check the resolvability of Clojure specs?
clojure.spec.alpha
允许在定义新规格时使用不可解析的规格:
(s/def :foo/bar (s/or :nope :foo/foo))
这里,:foo/foo
无法解析,所以使用:foo/bar
会引发异常:
(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
source code 我发现我可以用 (keys (s/registry))
获得所有规格所以我的测试看起来像这样:
(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; that’s the function I want
遗憾的是clojure.spec.alpha
中没有s/resolvable?
这样的东西。到目前为止我找到的唯一解决方案是调用 (s/valid? spec 42)
并假设它没有引发异常意味着它是可解析的但它不检查所有分支:
(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
之类的测试值,但不能'找不到。
有没有办法检查,对于给定的规范,它在所有分支中引用的所有规范都存在?
我能够做到以下几点:
(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/and
和 s/or
,这是我的最小用例:
(u/resolvable? :int/int) ;; => true
(u/resolvable? :my/spec) ;; => false
(u/unresolvable? :my/spec) ;; => (:idont/exist)
但它有一些缺陷:
- 它重新发明了轮子;我认为那些 spec-walking 函数已经存在于
clojure.spec.alpha
中的某处
- 它依赖于捕获异常然后解析其消息,因为 (1)
clojure.spec.alpha
没有不引发异常的函数,并且 (2) 引发异常的函数没有使用比 Exception
更具体的内容
如果有人有更可靠的答案,我很乐意接受任何其他答案。
clojure.spec.alpha
允许在定义新规格时使用不可解析的规格:
(s/def :foo/bar (s/or :nope :foo/foo))
这里,:foo/foo
无法解析,所以使用:foo/bar
会引发异常:
(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
source code 我发现我可以用 (keys (s/registry))
获得所有规格所以我的测试看起来像这样:
(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; that’s the function I want
遗憾的是clojure.spec.alpha
中没有s/resolvable?
这样的东西。到目前为止我找到的唯一解决方案是调用 (s/valid? spec 42)
并假设它没有引发异常意味着它是可解析的但它不检查所有分支:
(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
之类的测试值,但不能'找不到。
有没有办法检查,对于给定的规范,它在所有分支中引用的所有规范都存在?
我能够做到以下几点:
(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/and
和 s/or
,这是我的最小用例:
(u/resolvable? :int/int) ;; => true
(u/resolvable? :my/spec) ;; => false
(u/unresolvable? :my/spec) ;; => (:idont/exist)
但它有一些缺陷:
- 它重新发明了轮子;我认为那些 spec-walking 函数已经存在于
clojure.spec.alpha
中的某处
- 它依赖于捕获异常然后解析其消息,因为 (1)
clojure.spec.alpha
没有不引发异常的函数,并且 (2) 引发异常的函数没有使用比Exception
更具体的内容
如果有人有更可靠的答案,我很乐意接受任何其他答案。