error.clj

Branches > main > 169f063 > 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 adorn
  "Catches any throwables 'body' throws and throws an exception 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 "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#))))