catch exceptions hook, line, and sinker
Clojure 91.3%
Other 8.7%
15 1 1

Clone this repository

https://tangled.org/noahbogart.com/sinker https://tangled.org/did:plc:qbidoe2gpkfelnurqod37ikr/sinker
git@knot.tangled.wizardry.systems:noahbogart.com/sinker git@knot.tangled.wizardry.systems:did:plc:qbidoe2gpkfelnurqod37ikr/sinker

For self-hosted knots, clone URLs may differ based on your setup.

Download tar.gz
README.md

noahtheduke/sinker#

catch exceptions hook, line, and sinker.

installation#

:deps {io.github.noahtheduke/sinker {:mvn/version "0.1.0"}}

features#

it's try but a little nicer:

(require '[noahtheduke.sinker :as sinker :refer [try+]])

;; works like normal try
(try+)
;; => nil

(try+ 1 2 3)
;; => 3

(try+ (throw (Exception. "hello world!"))
  (catch Exception ex
    (ex-message ex)))
;; => "hello world!"

;; ex-infos with `:noahtheduke.sinker/type` (or `:type`) ex-data can be caught with a keyword.
;; the keywords are compared with isa? to respect heirarchies.
;; the bound variable is the ex-data, not the exception itself.
(try+ (throw (ex-info "Wrong parameter" {::sinker/type :invalid-parameter
                                         :expected :abc
                                         :given :foobar}))
  (catch :invalid-parameter data
    (:given data)))
;; => :foobar

;; the exception is on the metadata of the bind under the key `:noahtheduke.sinker/exception`.
(try+ (throw (ex-info "Wrong parameter" {::sinker/type :invalid-parameter
                                         :expected 'abc
                                         :given 'foobar}))
  (catch :invalid-parameter data
    (ex-message (:noahtheduke.sinker/exception (meta data)))))
;; => "Wrong parameter"

;; because the ex-data is a map, it can be destructured
(try+ (throw (ex-info "Wrong parameter" {::sinker/type :invalid-parameter
                                         :expected :abc
                                         :given :foobar}))
  (catch :invalid-parameter {:keys [expected given]}
    [expected given (= expected given)]))
;; => [:abc :foobar false]

;; ex-infos can also be caught with predicate functions or vars.
;; the predicate must be a 1-arg function that takes the `ex-data`.
;; like catching a keyword, the bound variable is the ex-data, not the exception itself.
(defn pred [data]
  (= :value (:key data)))

(try+ (throw (ex-info "KV pair" {::sinker/type :incorrect-argument
                                 :key :value}))
  (catch pred {k :key}
    k))
;; => :value

;; like normal try, each catch is checked in definition order,
;; and finally clauses gotta come last
(defn pred2 [data]
  (= :value2 (:key2 data)))

(def finally-ran? (atom nil))

(try+ (assert (= 1 2) "This will work")
  (catch :invalid-argument _
    :invalid-argument)
  (catch pred2 ex
    (ex-message ex))
  (catch clojure.lang.ExceptionInfo ex
    (ex-data ex))
  (catch Exception _
    "Got us an exception!")
  (catch Throwable t
    (str "Received a " (.getName (class t))))
  (finally
    (reset! finally-ran? "hoodee hoodee hoo")))
;; => "Received a java.lang.AssertionError"

@finally-ran?
;; => "hoodee hoodee hoo"

others in the space#

this library is quite similar to exoscale/ex, but ex is solely focused on exception infos and does a lot more, with a stronger emphasis on a specific pattern of error handling. i wrote this to fill a gap in lazytest and to satisfy my curiosity. i don't expect this to receive widespread adoption nor do i really want it. sometimes it's just nice to make something and let others check it out, you know?

license#

Copyright © Noah Bogart

Distributed under the Mozilla Public License version 2.0.