Skip to content

Exception middleware looks at descendants, instead of ancestors #768

@ferdinand-beyer

Description

@ferdinand-beyer

It seems that the keys of the handler map given to create-exception-middleware should have isa? semantics, allowing users to provide handlers for "trees" of exceptions, e.g. for a given exception type or any derived type.

Indeed, this is the example given in the doc-string:

(require '[reitit.ring.middleware.exception :as exception])

;; type hierarchy
(derive ::error ::exception)
(derive ::failure ::exception)
(derive ::horror ::exception)

(defn handler [message exception request]
  {:status 500
   :body {:message message
          :exception (str exception)
          :uri (:uri request)}})

(def exception-middleware ;; def added by me
  (exception/create-exception-middleware
   (merge
    exception/default-handlers
    {;; ex-data with :type ::error
     ::error (partial handler "error")

     ;; ex-data with ::exception or ::failure
     ::exception (partial handler "exception")

     ;; SQLException and all it's child classes
     java.sql.SQLException (partial handler "sql-exception")

     ;; override the default handler
     ::exception/default (partial handler "default")

     ;; print stack-traces for all exceptions
     ::exception/wrap (fn [handler e request]
                        (.printStackTrace e)
                        (handler e request))})))

However, the example does not work as advertised (ex-data with ::exception or ::failure):

(defn ex-response [e]
  (((:wrap exception-middleware) (fn [_] (throw e))) {:uri "/test"}))

(-> (ex-response (ex-info "exception" {:type ::exception}))
    (get-in [:body :message]))
;; => "exception", ok

(-> (ex-response (ex-info "failure" {:type ::failure}))
    (get-in [:body :message]))
;; => "default", incorrect: expected "exception", because: (isa? ::failure ::exception)

The reason is that reitit.ring.middleware.exception/call-error-handler calls (descendants-safe type), instead of (ancestors type): For a given error type, we need to find a handler for one of the ancestors, not descendants.

(derive ::exception ::error-base)

(-> (ex-response (ex-info "exception" {:type ::error-base}))
    (get-in [:body :message]))
;; => "error" (!!!)

For exception classes, it correctly looks at superclasses, but unfortunately ignores any implemented interfaces, but that is a separate issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions