-
Notifications
You must be signed in to change notification settings - Fork 250
/
exception.clj
180 lines (149 loc) · 5.51 KB
/
exception.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
(ns reitit.ring.middleware.exception
(:require [clojure.spec.alpha :as s]
[clojure.string :as str]
[reitit.coercion :as coercion]
[reitit.ring :as ring])
(:import (java.io Writer PrintWriter)
(java.time Instant)))
(s/def ::handlers (s/map-of any? fn?))
(s/def ::spec (s/keys :opt-un [::handlers]))
;;
;; helpers
;;
(defn- super-classes [^Class k]
(loop [sk (.getSuperclass k), ks []]
(if-not (= sk Object)
(recur (.getSuperclass sk) (conj ks sk))
ks)))
(defn- call-error-handler [handlers error request]
(let [type (:type (ex-data error))
ex-class (class error)
error-handler (or (get handlers type)
(get handlers ex-class)
(some
(partial get handlers)
(descendants type))
(some
(partial get handlers)
(super-classes ex-class))
(get handlers ::default))]
(if-let [wrap (get handlers ::wrap)]
(wrap error-handler error request)
(error-handler error request))))
(defn- on-exception [handlers e request respond raise]
(try
(respond (call-error-handler handlers e request))
(catch Exception e
(raise e))))
(defn- wrap [handlers]
(fn [handler]
(fn
([request]
(try
(handler request)
(catch Throwable e
(on-exception handlers e request identity #(throw %)))))
([request respond raise]
(try
(handler request respond (fn [e] (on-exception handlers e request respond raise)))
(catch Throwable e
(on-exception handlers e request respond raise)))))))
(defn print! [^Writer writer & more]
(.write writer (str (str/join " " more) "\n")))
;;
;; handlers
;;
(defn default-handler
"Default safe handler for any exception."
[^Exception e _]
{:status 500
:body {:type "exception"
:class (.getName (.getClass e))}})
(defn create-coercion-handler
"Creates a coercion exception handler."
[status]
(fn [e _]
{:status status
:body (coercion/encode-error (ex-data e))}))
(defn http-response-handler
"Reads response from Exception ex-data :response"
[e _]
(-> e ex-data :response))
(defn request-parsing-handler [e _]
{:status 400
:headers {"Content-Type" "text/plain"}
:body (str "Malformed " (-> e ex-data :format pr-str) " request.")})
(defn wrap-log-to-console [handler ^Throwable e {:keys [uri request-method] :as req}]
(print! *out* (Instant/now) request-method (pr-str uri) "=>" (.getMessage e))
(.printStackTrace e (PrintWriter. ^Writer *out*))
(handler e req))
;;
;; public api
;;
(def default-handlers
{::default default-handler
::ring/response http-response-handler
:ring.util.http-response/response http-response-handler
:muuntaja/decode request-parsing-handler
::coercion/request-coercion (create-coercion-handler 400)
::coercion/response-coercion (create-coercion-handler 500)})
(defn wrap-exception
([handler]
(handler default-handlers))
([handler options]
(-> options wrap handler)))
(def exception-middleware
"A preconfigured exception handling Middleware. To configure the exceptions handlers, use
`create-exception-handler` instead."
{:name ::exception
:spec ::spec
:wrap (wrap default-handlers)})
(defn create-exception-middleware
"Creates a Middleware that catches all exceptions. Takes a map
of `identifier => exception request => response` that is used to select
the exception handler for the thrown/raised exception identifier. Exception
identifier is either a `Keyword` or a Exception Class.
The following handlers special handlers are available:
| key | description
|------------------------|-------------
| `::exception/default` | a default exception handler if nothing else matched (default [[default-handler]]).
| `::exception/wrap` | a 3-arity handler to wrap the actual handler `handler exception request => response`
The handler is selected from the options map by exception identifier
in the following lookup order:
1) `:type` of exception ex-data
2) Class of exception
3) `:type` ancestors of exception ex-data
4) Super Classes of exception
5) The ::default handler
Example:
(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)}})
(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))}))"
([]
(create-exception-middleware default-handlers))
([handlers]
{:name ::exception
:spec ::spec
:wrap (wrap handlers)}))