pedestal/pedestal — pull request #302 by ohpauleez (2014-12-18 12:45:54+00:00)
Title: New error handling; AOT for new interceptor API

This is another large change, worthy of a pull-request so that others in the community can have better visibility into the incremental changes.

### New error handling
#### The Problem
In any sufficiently comprehensive Pedestal service, one must handle `:error`s at various points within the interceptor chain. A challenge presents itself when two interceptors produce the same error - for example, you have an endpoint handler that can produce a JSON exception, and potentially the json-body interceptor can produce the same JSON exception. In one case, you'd like to return a `:status 400` (the handler couldn't get the resource correctly), in the other case, you'd like to return a `:status 500` - something went wrong within your service and you generated a nonsense body.
The common pattern for handling this situation is to pack your handler exceptions within `ex-info`, and smuggle some "signal" or identifying keyword within its `ex-data`, that you later unpack and inspect within a custom error interceptor.
#### The Solution
We can do better. There's a trade-off with the error path in any system - give up speed to get more robust capabilities. Pedestal is moving further in this direction, since in the `:error` case you're already paying for exception handling anyway. **All exceptions are packed in `ex-info` by Pedestal**. When an exception happens int he interceptor chain, it's packed in ex-info, and the entire internal context for the operation is captured in `ex-data`. The internal context consists of:

* `:execution-id` - (you can mostly ignore this unless you're paying attention to thread affinity)
* `:stage` - The interceptor chain stage (one of :enter :leave :error) of the error'ing interceptor
* `:interceptor` - The keyword name of the interceptor where the error occurred
* `:exception-type` - The keyword name of the exception that was thrown
* `:exception` - The actual throwable object that was thrown

If the exception that was thrown was already ex-info and has ex-data, the above ex-data is merged in, favoring the existing ex-data.

Given this new packaging, pattern-matching for all exceptions/errors in a single interceptor is now possible, and encouraged. There's a new error-handling interceptor you can use called `error-dispatch`,. Consider the snippet below, taken from the Pedestal test suite:

```clojure
(ns io.pedestal.interceptor.error-test
(:require [clojure.test :refer :all]
[io.pedestal.test :refer :all]
[io.pedestal.http :as service]
[io.pedestal.http.route.definition :refer [defroutes]]
[ring.util.response :as ring-resp]
[io.pedestal.interceptor.error :as error-int]))

(def service-error-handler
"Create a new error handler that pattern matches parts of the internal context"
(error-int/error-dispatch [ctx ex]
;; If we have an ArithmeticException from within the `another-bad-one` interceptor/handler...
[{:exception-type :java.lang.ArithmeticException
:interceptor ::another-bad-one}] (assoc ctx :response {:status 400 :body "Another bad one"})
;; If anything throws an ArithmeticException that we haven't caught/matched yet...
[{:exception-type :java.lang.ArithmeticException}] (assoc ctx :response {:status 400 :body "A bad one"})
;; If we don't match anything, forward it on
:else (assoc ctx :io.pedestal.impl.interceptor/error ex)))

(defn bad-page
[request]
(ring-resp/response (str "Bad division: " (/ 3 0))))

(defn drop-through
[request]
(throw (Exception. "Just testing the error-handler, this is not a real exception")))

(defroutes request-handling-routes
[[:request-handling "error-dispatch.pedestal"
["/" ^:interceptors [service-error-handler]
["/div" {:any bad-page}]
["/div2" {:any [::another-bad-one bad-page]}]
["/drop" {:any drop-through}]]]])
```

The new error dispatch (macro) take a two-arg vector (the context and the exception), and then a `core.match/match` body. You can match on **anything** from the internal context: a certain exception, happening at a certain interceptor/handler, at a certain stage (entering, leaving, error). You can even match it when it only happens on a certain thread (if you really wanted to - but please don't do that). **You must always return a context from the exception handling action**

This approach provides a very rich way to capture all your service's error handling in one spot, while giving the developer complete control, and providing the internal error context, which was previously not exposed.

- - - -

This also removed the use of interceptor macros from Pedestal's compile path. This address AOT compilation for the new Interceptors API.