• This repository has been archived on 14/Nov/2017
  • Stars
    star
    483
  • Rank 87,785 (Top 2 %)
  • Language
    Clojure
  • Created over 11 years ago
  • Updated over 8 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Erlang-style supervisor error handling for Clojure

dire

Decomplect error logic. Error handling, pre/post conditions and general hooks for Clojure functions.

Ships with two flavors:

  1. The drop-in style, using functions ending in '!'
  2. 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

  1. Eager Pre-hooks
  2. Preconditions
  3. Pre-hooks
  4. The target function
  5. Exception handlers
  6. Postconditions
  7. Post-hooks
  8. 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.

More Repositories

1

voluble

Intelligent data generator for Apache Kafka. Generates streams of realistic data with support for cross-topic relationships, tombstoning, configurable rates, and more.
Clojure
153
star
2

night-vision

See through the darkness of a running program.
Clojure
52
star
3

dibble

A Clojure library for seeding databases.
Clojure
36
star
4

rush-hour

Docs for the Rush Hour platform.
32
star
5

zombie

Clojure declarative semantic data transformations made easy.
Clojure
20
star
6

pipelining-example

An example of pipelining in Clojure using core.async
Clojure
6
star
7

clojure-poker

Derping around with Clojure and calculations for poker.
Clojure
5
star
8

hiccup-foundation

Zurb Foundation in Hiccup
Clojure
4
star
9

stream-processing-animations

JavaScript
4
star
10

traffic-sim

Simulation component for the Rush Hour platform
Clojure
3
star
11

oxide

Knowledge platform over Onyx
Clojure
3
star
12

what-is-ksqldb

JavaScript
2
star
13

clojure-units

A small Clojure utility library for converting measurement units.
Clojure
2
star
14

number-matcher

Number recognition game. Much harder than it sounds.
JavaScript
2
star
15

asphalt

Heat map for Rush Hour streaming API.
Clojure
2
star
16

dire-with-component

An example of using Dire with Component.
Clojure
2
star
17

elevator

An experiment of thought in solving the simple problem of elevators.
1
star
18

hdfs-block-reader-experiment

Clojure
1
star
19

onyx-repl

An experimental repl, just an idea -- not ready for usage.
Clojure
1
star
20

try_git

1
star
21

lein-cssgenbuild

Leiningen plugin to generate stylesheets from cssgen
Clojure
1
star
22

onyx-log-subscriber-demo

Demo of subscribing to the Onyx log.
Clojure
1
star
23

http-perf-test

Nothing to see here
Clojure
1
star