error.clj
Branches > main > 2bdfa58 > 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 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)))))
(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 "Assert Failed!" ~@(interpose \space msg) \newline (pr-str '~x))
~m))
x)))
;; 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#))))