(defn ^:skip-wiki fspec-impl
"Do not call this directly, use 'fspec'"
[argspec aform retspec rform fnspec fform gfn]
(let [specs {:args argspec :ret retspec :fn fnspec}]
(reify
clojure.lang.ILookup
(valAt [this k] (get specs k))
(valAt [_ k not-found] (get specs k not-found))
Specize
(specize* [s] s)
(specize* [s _] s)
Spec
(conform* [this f] (if argspec
(if (ifn? f)
(if (identical? f (validate-fn f specs *fspec-iterations*)) f ::invalid)
::invalid)
(throw (Exception. (str "Can't conform fspec without args spec: " (pr-str (describe this)))))))
(unform* [_ f] f)
(explain* [_ path via in f]
(if (ifn? f)
(let [args (validate-fn f specs 100)]
(if (identical? f args) ;;hrm, we might not be able to reproduce
nil
(let [ret (try (apply f args) (catch Throwable t t))]
(if (instance? Throwable ret)
;;TODO add exception data
[{:path path :pred '(apply fn) :val args :reason (.getMessage ^Throwable ret) :via via :in in}]
(let [cret (dt retspec ret rform)]
(if (invalid? cret)
(explain-1 rform retspec (conj path :ret) via in ret)
(when fnspec
(let [cargs (conform argspec args)]
(explain-1 fform fnspec (conj path :fn) via in {:args cargs :ret cret})))))))))
[{:path path :pred 'ifn? :val f :via via :in in}]))
(gen* [_ overrides _ _] (if gfn
(gfn)
(gen/return
(fn [& args]
(c/assert (pvalid? argspec args) (with-out-str (explain argspec args)))
(gen/generate (gen retspec overrides))))))
(with-gen* [_ gfn] (fspec-impl argspec aform retspec rform fnspec fform gfn))
(describe* [_] `(fspec :args ~aform :ret ~rform :fn ~fform)))))