Fork me on GitHub
#clojure-spec
<
2022-08-23
>
vlaaad20:08:26

Does something like that make sense?

(defmulti dispatch identity)
(defmethod dispatch :default [v]
  (:spec v))

(s/valid? 
  (s/multi-spec dispatch :spec)
  {:spec (s/keys :req-un [::a ::b]) :a 1 :b 2})
=> true
The real code in :default branch would be more complex, but the idea is that I have a complex rule to infer spec from the value, and I don't want to encode it in a single predicate that executes the rule and calls s/valid? inside because it will lose the error reporting/explanation. I.e. I don't want to do this:
(s/valid?
  (s/spec #(s/valid? (:spec %) %))
  {:spec (s/keys :req-un [::a ::b]) :a 1})
=> false
because (s/explain-data ...) will just say that (s/valid? (:spec %) %) is false and not that the :b is missing

vlaaad20:08:36

Explanation comparison:

(s/explain
  (s/spec #(s/valid? (:spec %) %))
  {:spec (s/keys :req-un [::a ::b]) :a 1})
=> {:spec ... :a 1} - failed: (valid? (:spec %) %)

(s/explain
  (s/multi-spec dispatch :spec)
  {:spec (s/keys :req-un [::a ::b]) :a 1})
=> {:spec ... :a 1} - failed: (contains? % :b)

vlaaad18:08:07

check this out:

(defmacro def-dynamic-spec [name & fn-tail]
  (let [multi-name (symbol (str name "-multi-fn"))]
    `(do
       (defmulti ~multi-name (constantly nil))
       (defmethod ~multi-name nil ~@fn-tail)
       (def ~name (s/multi-spec ~multi-name nil)))))
a macro that creates a spec that dynamically infers a value spec using a function of the value, while preserving error reporting/explanation data