-
Notifications
You must be signed in to change notification settings - Fork 0
/
handlers.clj
179 lines (158 loc) · 6.29 KB
/
handlers.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
(ns conditions.handlers
(:require [conditions.core :refer [condition*]])
(:import conditions.core.Restarts))
(defn custom
"Mark a function as a custom handler.
A custom handler is a function `(f handlers depth condition normally)` that returns a function `(f' value)`."
[f]
(with-meta f {:custom true}))
(defn error
"Handle a condition by throwing an ex-info"
([message]
^{:custom true :message message}
(fn [handlers depth condition normally]
(fn [value]
(throw (ex-info message {:condition condition :value value})))))
([message ex-data]
^{:custom true :message message :ex-data ex-data}
(fn [handlers depth condition normally]
(fn [value]
(throw (ex-info message (merge {:condition condition :value value}
ex-data)))))))
(defn error*
"Handle a condition by throwing an ex-info"
([message]
^{:custom true :message message}
(fn [handlers depth condition normally]
(fn [value]
(throw (ex-info message {})))))
([message ex-data]
^{:custom true :message message :ex-data ex-data}
(fn [handlers depth condition normally]
(fn [value]
(throw (ex-info message ex-data))))))
(defn exception
"Handle a condition by instantiating and throwing an exception of the given class with the given message and cause."
([class message]
^{:custom true :message message :class class}
(fn [handlers depth condition normally]
(fn [value]
(throw (clojure.lang.Reflector/invokeConstructor class (into-array Object [message]))))))
([class message cause]
^{:custom true :message message :class class :cause cause}
(fn [handlers depth condition normally]
(fn [value]
(throw (clojure.lang.Reflector/invokeConstructor class (into-array Object [message cause])))))))
(def trace
"Just print that something happened and return the value"
^:custom
(fn
([message]
^{:custom true :message message}
(fn [handlers depth condition normally]
(fn [value]
(print (str message " "))
(prn condition value)
value)))
([handlers depth condition normally]
(fn [value]
(prn condition value)
value))))
(defn trace-value
"Print a message and return the given value. Ignores any value provided by the restart."
[message value]
^{:custom true :message message :value value}
(fn [handlers depth condition normally]
(fn [_]
(print (str message " "))
(prn condition value)
value)))
(def optional
"Use to indicate that handling a condition is optional. If nothing handles the condition, return the value unmodified."
(custom (constantly identity)))
(def required
"Use to indicate that handling a condition is required. If nothing handles the condition, throw an ex-info."
^:custom
(fn [handlers depth condition normally]
(fn [value]
(throw (ex-info "No handler specified for condition" {:condition condition :value value})))))
(defn default
"Handle the condition with a constant value or a simple function of the value."
[value]
(custom (if (fn? value)
(constantly value)
(constantly (constantly value)))))
(defn handle
"Handle the condition with a simple function of the value.
If the function returns :continue, continue searching handlers from the parent scope."
[f]
^:custom
(fn [handlers depth condition normally]
(fn [value]
(let [result (f value)]
(if (= :continue result)
(condition* (with-meta handlers {:depth (dec depth)})
condition
value
normally)
result)))))
(defn remap
"Restart the condition handler search from the beginning with a new condition key.
If next-handler is a function, it will be called with the value and the returned value will be the new condition key.
If f is provided, uses the value it returns as the new value for the new condition.
The default handler can also be overridden by providing override-normally."
([next-handler]
(remap next-handler identity nil))
([next-handler f]
(assert (not (nil? next-handler)))
(remap next-handler f nil))
([next-handler f override-normally]
(let [f (fn [value]
(if (and (instance? Restarts value)
(not (:restart (meta f))))
(with-meta (update value :data f)
(meta value))
(f value)))]
(cond
(nil? next-handler)
^:custom
(fn [handlers depth condition normally]
(fn [value]
;; Special case to support fall-through which trims the handler stack.
;; Using alone will cause a stack overflow.
(condition* handlers condition (f value) (or override-normally normally))))
(fn? next-handler)
^:custom
(fn [handlers depth condition normally]
(fn [value]
(condition* handlers (next-handler value) (f value) (or override-normally normally))))
:else
^:custom
(fn [handlers depth condition normally]
(fn [value]
(condition* handlers next-handler (f value) (or override-normally normally))))))))
(defn fall-through
"Continue searching for handlers from the parent scope. Similar to `handle` if it were to always return :continue.
f alters the value (because if you don't need to do anything at this scope you don't need a handler at all)
next-handler acts like `remap` except that the search still starts at the parent scope
override-normally changes the default handler."
([f]
(fall-through nil f nil))
([next-handler f]
(fall-through next-handler f nil))
([next-handler f override-normally]
(let [remapped (remap next-handler f override-normally)]
^:custom
(fn [handlers depth condition normally]
(remapped (with-meta handlers {:depth (dec depth)}) depth condition (or override-normally normally))))))
(defn sibling
"Identical to `remap` except that the search resumes at the current scope."
([next-handler]
(sibling next-handler identity nil))
([next-handler f]
(sibling next-handler f nil))
([next-handler f override-normally]
(let [remapped (remap next-handler f override-normally)]
^:custom
(fn [handlers depth condition normally]
(remapped (with-meta handlers {:depth depth}) depth condition (or override-normally normally))))))