Home

Awesome

ex

Exception handling library for clojure

Api docs

Installation

Clojars Project

Clojars Project

Clojars Project

Clojars Project

Rationale

How

This is an exception library, drop in replacement for try/catch/finally, that adds support for ex-info/ex-data with a custom clojure hierarchy that allows to express exceptions relations. It also comes with manifold support.

So we have exoscale.ex/try+, which supports vanilla catch/finally clauses.

If you specify a catch clause with a keyword as first argument things get interesting. We assume you always put an :exoscale.ex/type or :type key in the ex-infos you generate, and will match its value to the value of the key in the catch clause.

The basics

Essentially catch takes this form:

(catch :something m
   ;; where m is a binding to the ex-data (you can destructure at that level as well)
   )

So you can do things like that.


(require '[exoscale.ex :as ex])

(ex/try+

  (throw (ex-info "Argh" {::ex/type ::bar :foo "a foo"}))

  (catch ::foo data
    (prn :got-ex-data data))

  (catch ::bar {:as data :keys [foo]}
    ;; in that case it would hit this one
    (prn :got-ex-data-again foo))

  (catch ExceptionInfo e
   ;; this would match an ex-info that didn't get a hit with catch-ex-info)

  (catch Exception e (prn :boring))

  (finally (prn :boring-too)))

Exception hierarchies

We leverage a clojure hierarchy so you can essentially create exceptions relations/extending without having to mess with Java classes directly.

;; so bar is a foo

(ex/derive ::bar ::foo)

(ex/try+
  (throw (ex-info "I am a bar" {::ex/type ::bar})
  (catch ::foo d
    (prn "got a foo with data" d)
    (prn "Original exception instance is " (-> d meta ::ex/exception))))

Manifold support

We have exoscale.ex.manifold/catch that matches the semantics of a catch block in try+ but with a more manifold like feel.

(require '[exoscale.ex.manifold :as mx])
(require '[manifold.deferred :as d])

(-> (d/error-deferred (ex-info "boom" {::ex/type :bar}))
    (mx/catch :bar (fn [data] (prn "ex-data is: " data)))
    (d/catch (fn [ex] "... regular manifold handling here")))

How to get to the original exception

You can also get the full exception instance via the metadata on the ex-data we extract, it's under the :exoscale.ex/exception key. If you are within a try+ block you can also access it directly via &ex

Our default internal error type table

We suggest you also either use one of these as ::ex/type or derive your own with these.

Within the namespace :exoscale.ex:

categoryretryfix
:unavailableyesmake sure callee healthy
:interruptedyesstop interrupting
:incorrectnofix caller bug
:forbiddennofix caller creds
:unsupportednofix caller verb
:not-foundnofix caller noun
:conflictnocoordinate with callee
:faultnofix callee bug
:busyyesbackoff and retry

This is very much inspired by cognitect-labs/anomalies.

We have simple wrappers to create ex-info based on this: ex/ex-unavailable, ex/ex-interrupted and so on. Their signature is identical to ex-info otherwise. An ex-http/response->ex-info utility function that maps HTTP status codes onto this taxonomy is available in the ex.http namespace.

How to generate/use good ex-infos

Helpers

We have a few helpers

=> (clojure.core.protocols/datafy (ex/ex-incorrect "boom" {:a 1} (ex/ex-incorrect "the-cause")))
#:exoscale.ex{:type :exoscale.ex/incorrect
              :message "boom"
              :data {:a 1}
              :deriving #{:exoscale.ex/foo :exoscale.ex/bar}
              :cause #:exoscale.ex{:type :exoscale.ex/incorrect
                                   :message "the-cause"
                                   :data {...}
                                   :deriving #{:exoscale.ex/foo :exoscale.ex/bar}}

=> (type (ex/map->ex-info *1))
clojure.lang.ExceptionInfo

Usage examples

Some real life examples of usage for this: