error.clj
Branches > refactor/use-spec-1 > ab82c4f > files / src / dev / grahp / error.clj
(ns dev.grahp.error
"Some error handling macros."
(:require [clojure.spec.alpha :as s]
[clojure.string :as str]))
(defmacro guard
"Calls form, if it throws an exception, returns 'else', default nil."
([form] `(guard ~form nil))
([form else]
`(try ~form
(catch Exception ~'_ ~else))))
(defmacro raise
"Throws an error with msg and an optional map and cause."
{:arglists '([msg]
[msg m]
[msg m cause]
[exception-class msg]
[exception-class msg cause])}
([msg] `(throw (RuntimeException. ~msg)))
([msg m]
(if (symbol? msg)
`(throw (~msg ~m))
`(throw (ex-info ~msg ~m))))
([msg m cause]
(if (symbol? msg)
`(throw (~msg ~m ~cause))
`(throw (ex-info ~msg ~m ~cause)))))
; TODO of the same type?
(defmacro adorn
"Catches any throwables 'body' throws and throws an exception (not of the same type as the throwable) caused by the
throwable with an additional message and data map."
[body msg m]
`(try ~body
(catch Throwable t#
(raise ~msg ~m t#))))
(defmacro assert!
"Throws an exception with an optional map and message when 'x' is falsey and *assert* is truthy,
otherwise, returns 'x'"
([x] `(assert! ~x {}))
([x m & msg]
(if *assert*
`(or ~x
(raise (str "Assertion failed: " ~@(interpose \space msg) \newline (pr-str '~x))
~m))
x)))
#_(defmacro when! [])
;; TODO these have the arguments in a different order than assert!
;; And keywords are assumed to be specs even if you want to ensure a key is present in a map...
(defmacro ensure!
"Yields x if it conforms to spec, else throws and ex-info with optional message and map"
([spec x] `(ensure! ~spec ~x nil))
([spec x msg] `(ensure! ~spec ~x ~msg {}))
([spec x msg m]
`(let [x# ~x]
(if (s/valid? ~spec x#)
x#
(raise (str "Assertion failed: " (pr-str '~x) "\n"
(when-let [msg# ~msg]
(str msg# "\n"))
(str/trim-newline (s/explain-str ~spec x#)))
(assoc ~m '~x x#))))))
(defmacro ensure-not!
"Yields x if it does not conform to spec, else throws and ex-info with optional message and map"
([spec x] `(ensure-not! ~spec ~x nil))
([spec x msg] `(ensure-not! ~spec ~x ~msg {}))
([spec x msg m]
`(let [x# ~x]
(if (s/valid? ~spec x#)
(raise (str "Assertion failed: " (pr-str '~x) "\n"
(when-let [msg# ~msg]
(str msg# "\n"))
(str/trim-newline (s/explain-str ~spec x#)))
(assoc ~m '~x x#))
x#))))
(defmacro defn!
{:clj-kondo/lint-as 'clojure.core/defn}
[fn-name docstring args & body]
(assert (and (vector? args) (every? simple-symbol? args)))
`(defn ~fn-name ~docstring ~args
(adorn ~@body
(str "Error while in function '" (ns-name *ns*) "/" '~fn-name "'")
{:args ~(zipmap (map keyword args) args)})))