Pedestal

Error Handling

Error Handling

Pedestal must do something useful when an interceptor throws an exception. It would not be useful to bubble up the runtime call stack, because that call stack only includes the Pedestal machinery itself. Interceptors are data structures that conceptually represent frames on a call stack, but they are not present in the usual nested fashion on the Java call stack.

Pedestal addresses this by catching all exceptions thrown from an interceptor. It binds the exception to the :io.pedestal.interceptor.chain/error key in the context map.

As long as there is an error attached to that key, Pedestal will not invoke the usual :enter and :leave functions. Instead, it looks for the next interceptor in the chain that has a :error function attached to it.

This error handling proceeds the same way regardless of whether interceptors in the chain returned context maps or async channels. (See Interceptor Return Values for details on async return.) As a result, Pedestal unifies error handling for synchronous interceptors and asynchronous interceptors.

For HTTP, the servlet interceptor provides the "last ditch" error handlers that log the error and, in development mode, emit a stack trace as the response body.

You can supply your own :error interceptor anywhere in the interceptor queue.

Error Function

Pedestal will call the function bound to :error on an interceptor with two arguments:

Pedestal wraps all exceptions in ex-info. The folowing keys are defined:

Key Type Description

:execution-id

long

Unique value that identifies one "run" through the interceptors.

:stage

Keyword

Either :enter or :leave depending on which direction the execution was going.

:interceptor

Keyword

The :name from the interceptor that threw the exception

:exception-type

Keyword

Keywordized form of the exception’s Java class name

The context map not contain the error. The :error function can do one of the following things:

  1. Return the context map. This is "catching" the error. Because the context map has no error bound to it, Pedestal will return to normal execution after this interceptor.

  2. Return the context map with the exception re-attached at :io.pedestal.interceptor.chain/error. This indicates that the handler doesn’t recognize that exception and declines to handle it. Pedestal will continue looking for a handler.

  3. Re-throw the exception in the second argument. This means the interceptor can’t handle the exception and Pedestal should continue looking for a handler.

  4. Throw a new exception. This indicates that the interceptor should have been able to handle the error, but something went wrong. Pedestal will start looking for a handler for this new exception.

In that last case, Pedestal keeps track of the exceptions that were overridden. These are in a seq bound to :io.pedestal.interceptor.chain/suppressed in the context map.

Error Dispatch Interceptor

As a convenience, Pedestal offers a macro to build error-handling interceptors that use pattern-matching to dispatch.

error-dispatch builds interceptors that use core.match to select a clause.

Here is an example from the test suite:

(1)
(def service-error-handler
  (error-int/error-dispatch [ctx ex]
    (2)
    [{:exception-type :java.lang.ArithmeticException :interceptor ::another-bad-one}]
    (assoc ctx :response {:status 400 :body "Another bad one"})

    (3)
    [{:exception-type :java.lang.ArithmeticException}]
    (assoc ctx :response {:status 400 :body "A bad one"})

    (4)
    :else
    (assoc ctx :io.pedestal.interceptor.chain/error ex)))

,,,

(def routes
  (5)
  (route/expand-routes
    #{["/v1/api"  :any  [service-error-handler ,,,]]}))
1 This macro returns code that evaluates to an interceptor. Binding it to a var is the normal use.
2 This is a pattern match that looks for an ArithmeticException, but only thrown by a particular interceptor. If the pattern matches, this expression gets evaluated. The error is handled and a response attached.
3 Another pattern match expression, matching any `ArithmeticException`s that weren’t matched before. We return a different response body in these cases.
4 :else always matches because it is a truthy value. Reattaching the error tells Pedestal to keep looking for a handler.
5 We use the resulting interceptor like any other.

Pattern matching is a concise way to express conditions for handling an error.