Decomplect error logic. Error handling, pre/post conditions and general hooks for Clojure functions.
Ships with two flavors:
- The drop-in style, using functions ending in '!'
- Erlang-style inspired by the work of Joe Armstrong using a supervisor
Installation
Available on Clojars:
[dire "0.5.4"]
API
Check out the Codox API docs here.
Relevant Blog Posts
Evaluation Order
- Eager Pre-hooks
- Preconditions
- Pre-hooks
- The target function
- Exception handlers
- Postconditions
- Post-hooks
- Finally clause
Usage: Drop-in Flavor
Simple Example
(ns mytask (:require [dire.core :refer [with-handler!]])) ;;; Define a task to run. It's just a function. (defn divider [a b] (/ a b)) ;;; For a task, specify an exception that can be raised and a function to deal with it. (with-handler! #'divider "Here's an optional docstring about the handler." java.lang.ArithmeticException ;;; 'e' is the exception object, 'args' are the original arguments to the task. (fn [e & args] (println "Cannot divide by 0."))) (divider 10 0) ; => "Cannot divide by 0."
Multiple Exception Classes
Sometimes it is desirable to check for any one of a number of exceptions:
(with-handler! #'divider "Here's an optional docstring about the handler." [java.lang.ArithmeticException, java.lang.NullPointerException] ;;; 'e' is the exception object, 'args' are the original arguments to the task. (fn [e & args] (println "Cannot divide by 0 or operate on nil values."))) (divider 10 nil) ; => "Cannot divide by 0 or operate on nil values." (divider 10 0) ; => "Cannot divide by 0 or operate on nil values."
Try/Catch/Finally Semantics
(ns mytask (:require [dire.core :refer [with-handler! with-finally!]])) ;;; Define a task to run. It's just a function. (defn divider [a b] (/ a b)) (with-handler! #'divider java.lang.ArithmeticException (fn [e & args] (println "Catching the exception."))) (with-finally! #'divider "An optional docstring about the finally function." (fn [& args] (println "Executing a finally clause."))) (divider 10 0) ; => "Catching the exception.\nExecuting a finally clause.\n"
Slingshot Integration
Map Dispatch
(ns my.ns (:require [dire.core :refer :all] [slingshot.slingshot :refer [throw+]])) (defn f [] (throw+ {:type :db-disconnection :id 42})) (with-handler! #'f [:type :db-disconnection] (fn [e & args] "Safe and sound")) (f) ;; => "Safe and sound"
Predicate Dispatch
(defn f [] (throw+ 42)) (with-handler! #'f even? (fn [e & args] "Caught it")) (f) ;; => "Caught it"
Preconditions
(ns mytask (:require [dire.core :refer [with-precondition! with-handler!]])) (defn add-one [n] (inc n)) (with-precondition! #'add-one "An optional docstring." ;;; Name of the precondition :not-two (fn [n & args] (not= n 2))) (with-handler! #'add-one {:precondition :not-two} (fn [e & args] (apply str "Precondition failure for argument list: " (vector args)))) (add-one 2) ; => "Precondition failure for argument list: (2)"
Postconditions
(ns mytask (:require [dire.core :refer [with-postcondition! with-handler!]])) (defn add-one [n] (inc n)) (with-postcondition! #'add-one "An optional docstring." ;;; Name of the postcondition :not-two (fn [n & args] (not= n 2))) (with-handler! #'add-one {:postcondition :not-two} (fn [e result] (str "Postcondition failed for result: " result))) (add-one 1) ; => "Postcondition failed for result: (2)"
Pre-hooks
(ns mydire.prehook (:require [dire.core :refer [with-pre-hook!]])) (defn times [a b] (* a b)) (with-pre-hook! #'times "An optional docstring." (fn [a b] (println "Logging something interesting."))) (times 21 2) ; => "Logging something interesting."
Eager Pre-hooks
(ns mydire.prehook (:require [dire.core :refer [with-eager-pre-hook!]])) (defn times [a b] (* a b)) (with-eager-pre-hook! #'times "An optional docstring." (fn [a b] (println "Logging something before preconditions are evaluated."))) (times 21 2) ; => "Logging something before preconditions are evaluated."
Post-hooks
(ns mydire.posthook (:require [dire.core :refer [with-post-hook!]])) (defn times [a b] (* a b)) (with-post-hook! #'times "An optional docstring." (fn [result] (println "Result was" result))) (times 21 2) ; => "Result was 42"
Wrap-hooks
(defn fake-db-query "A fake database query that sometimes fails" [query] (rand-nth [nil "valid-data"])) (with-wrap-hook! #'fake-db-query "An optional docstring." (fn [result [query]] (if (not-empty result) result (println "Got the result" result "for input" query)))) (fake-db-query "select * from data") ; => "valid-data" (fake-db-query "select * from data") ; => Got the result nil for input select * from data ; => nil
Usage: Erlang Style with supervise
Simple Example
(ns mytask (:require [dire.core :refer [with-handler supervise]])) ;;; Define a task to run. It's just a function. (defn divider [a b] (/ a b)) ;;; For a task, specify an exception that can be raised and a function to deal with it. (with-handler #'divider "An optional docstring." java.lang.ArithmeticException ;;; 'e' is the exception object, 'args' are the original arguments to the task. (fn [e & args] (println "Cannot divide by 0."))) (with-handler #'divider java.lang.NullPointerException (fn [e & args] (println "Ah! A Null Pointer Exception! Do something here!"))) ;;; Invoke with the task name and it's arguments. (supervise #'divider 10 0) ; => "Cannot divide by 0." (supervise #'divider 10 nil) ; => "Ah! A Null Pointer Exception! Do something here!"
Self-Correcting Error Handling
(ns mytask (:require [dire.core :refer [with-handler supervise]] [fs.core :refer [touch]])) (defn read-file [file-name] (slurp file-name)) (with-handler #'read-file java.io.FileNotFoundException (fn [exception file-name & _] (touch file-name) (supervise #'read-file file-name))) (supervise #'read-file "my-file")
Try/Catch/Finally Semantics
(defn add-one [n] (inc n)) (with-handler #'add-one java.lang.NullPointerException (fn [e & args] (println "Catching the exception."))) (with-finally #'add-one (fn [& args] (println "Executing a finally clause."))) (with-out-str (supervise #'add-one nil)) ; => "Catching the exception.\nExecuting a finally clause.\n"
Preconditions
(defn add-one [n] (inc n)) (with-precondition #'add-one ;;; Name of the precondition :not-two (fn [n & args] (not= n 2))) (with-handler #'add-one ;;; Pair of exception-type (:precondition) to the actual precondition (:not-two) {:precondition :not-two} (fn [e & args] (apply str "Precondition failure for argument list: " (vector args)))) (supervise #'add-one 2) ; => "Precondition failure for argument list: (2)"
Postconditions
(defn add-one [n] (inc n)) (with-postcondition #'add-one :not-two (fn [n & args] (not= n 2))) (with-handler #'add-one {:postcondition :not-two} (fn [e result] (str "Postcondition failed for result: " result))) (supervise #'add-one 1) ; => "Postcondition failed for result: 2"
Pre-hooks
(defn times [a b] (* a b)) (with-pre-hook #'times (fn [a b] (println "Logging something interesting.")) (supervise #'times 1 2) ; => "Logging something interesting.", 2
Eager Pre-hooks
(defn times [a b] (* a b)) (with-eager-pre-hook #'times "An optional docstring." (fn [a b] (println "Logging something before preconditions are evaluated."))) (supervise #'times 21 2) ; => "Logging something before preconditions are evaluated."
Post-hooks
(defn times [a b] (* a b)) (with-post-hook #'times "An optional docstring." (fn [result] (println "Result was" result))) (supervise #'times 21 2) ; => "Result was 42"
Wrap-hooks
(defn fake-db-query "A fake database query that sometimes fails" [query] (rand-nth [nil "valid-data"])) (defn check-result [result [query]] (if (not-empty result) result (println "Got the result" result "for input" query))) (supervise #'fake-db-query "select * from data") ; => "valid-data" (supervise #'fake-db-query "select * from data") ; => Got the result nil for input select * from data ; => nil
Etc
- If an exception is raised that has no handler, it will be raised up the stack like normal.
- Multiple pre-hooks evaluate in arbitrary order.
Contributors
License
Copyright © 2012 Michael Drogalis
Distributed under the Eclipse Public License, the same as Clojure.