Как проверить разрешимость спецификаций Clojure?

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 исходный код Я обнаружил, что могу получить все характеристики с помощью (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

К сожалению, нет такой вещи, как s/resolvable? в clojure.spec.alpha. Единственное решение, которое я нашел до сих пор, — это вызвать (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 выше, но не смог найти ни одной.

Есть ли способ проверить, что для данной спецификации все спецификации, на которые она ссылается во всех ее ветвях, действительно существуют?


person bfontaine    schedule 22.08.2017    source источник


Ответы (1)


Я смог сделать следующее:

(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)

Но у него есть некоторые недостатки:

  • Он заново изобретает колесо; Я думаю, что эти функции хождения по спецификации уже существуют где-то в clojure.spec.alpha
  • Он основан на перехвате исключения, а затем анализе его сообщения, потому что (1) clojure.spec.alpha не имеет функции, которая не вызывает исключение, и (2) функции, которые вызывают исключение, не используют ничего более конкретного, чем Exception

Я был бы рад принять любой другой ответ, если у кого-то есть что-то более надежное.

person bfontaine    schedule 22.08.2017