Skip to content

Commit 78600eb

Browse files
committed
[mod] Make macros: ex-info, ex-info!, unexpected-arg!
Before this commit: `ex-info`, `ex-info!`, `unexpected-arg!` were functions After this commit: `ex-info`, `ex-info!`, `unexpected-arg!` are macros This is technically a BREAKING change, though it's very unlikely that anyone was actually using any of these in a way that'd be broken by the change. And by changing to macros, it became possible to include callsite info ({:keys [ns coords]}) in resulting ex-data. The callsite info can be handy, so I think it's worth the small theoretical risk of a very small number of breakages. Sincere apologies if anyone is broken by this!! If you are, it hopefully shouldn't be difficult to update the affected code. Feel free to ping me at taoensso.com/truss if I can assist.
1 parent 99a86ac commit 78600eb

3 files changed

Lines changed: 89 additions & 74 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ Truss's [inline assertions](#inline-assertions) (above) provide one solution to
100100

101101
Truss's **contextual exception** API provides another. Any time a [`truss/ex-info`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#ex-info) is thrown, it'll include the dynamic [`*ctx*`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#*ctx*) value in its `ex-data`.
102102

103-
You can use [`set-ctx!`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#set-ctx!), [`with-ctx`, `with-ctx+`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#with-ctx+) to easily establish relevant information about what your code is doing. Then if something unexpectedly throws, this context info will be included in relevant exceptions.
103+
You can use [`set-ctx!`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#set-ctx!), [`with-ctx`, `with-ctx+`](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#with-ctx+) to easily establish relevant information about what your code is doing. Then if something unexpectedly throws, this context (and callsite info) will be included in relevant exceptions.
104104

105105
Example:
106106

src/taoensso/truss.cljc

Lines changed: 73 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111

1212
#?(:cljs
1313
(:require-macros
14-
[taoensso.truss :refer [typed-val try*]]))
14+
[taoensso.truss :refer
15+
[keep-callsite typed-val ex-info ex-info! unexpected-arg!
16+
with-ctx with-ctx+ try* catching throws throws?
17+
have have? have! have!?
18+
with-dynamic-assertion-data with-data with-error-fn]]))
1519

1620
#?(:clj
1721
(:import
@@ -51,6 +55,8 @@
5155

5256
;;;; Misc
5357

58+
#?(:clj (defmacro ^:no-doc typed-val [x] `{:value ~x, :type (type ~x)}))
59+
5460
(defn ^:no-doc submap?
5561
"Returns true iff `sub-map` is a (possibly nested) submap of `super-map`,
5662
i.e. iff every (nested) value in `sub-map` has the same (nested) value in `super-map`.
@@ -85,7 +91,7 @@
8591
true
8692
sub-map))
8793

88-
;;;; Error context
94+
;;;; Truss exceptions
8995

9096
(def ^:dynamic *ctx*
9197
"Context map to assoc to `:truss/ctx` key of `truss/ex-info` data map.
@@ -97,24 +103,72 @@
97103
using futures, agents, etc."
98104
nil)
99105

100-
(defn ex-info
101-
"Like `core/ex-info` but when dynamic `*ctx*` value is present, it will be
102-
assoc'ed to `:truss/ctx` key of returned exception's data.
106+
(defn ^:no-doc ex-info*
107+
"Private, don't use."
108+
[ns coords msg data-map cause]
109+
(let [data-map
110+
(if coords
111+
(conj {:ns ns, :coords coords} data-map)
112+
(conj {:ns ns} data-map))
103113

104-
Useful for dynamically providing arbitrary contextual info to exceptions.
105-
See `*ctx*` for details."
106-
([msg ] (ex-info msg {} nil))
107-
([msg data-map ] (ex-info msg data-map nil))
108-
([msg data-map cause]
109-
(if-let [ctx *ctx*]
110-
(core/ex-info msg (assoc data-map :truss/ctx ctx) cause)
111-
(core/ex-info msg data-map cause))))
112-
113-
(defn ex-info!
114-
"Throws a `truss/ex-info`."
115-
([msg ] (throw (ex-info msg {} nil)))
116-
([msg data-map ] (throw (ex-info msg data-map nil)))
117-
([msg data-map cause] (throw (ex-info msg data-map cause))))
114+
data-map
115+
(if-let [ctx *ctx*]
116+
(assoc data-map :truss/ctx ctx)
117+
(do data-map))]
118+
119+
(core/ex-info msg data-map cause)))
120+
121+
#?(:clj
122+
(defmacro ex-info
123+
"Macro version of `core/ex-info` that adds extra keys to ex-info's data map:
124+
`:truss/ctx` -- Value of dynamic `truss/*ctx*` when ex-info created
125+
`:ns` --------- Namespace string of ex-info callsite
126+
`:coords` ----- [line number] of ex-info callsite, only present
127+
if ex-info isn't wrapped by another macro (or see
128+
`keep-callsite` for a workaround)."
129+
([msg ] `(ex-info* ~(str *ns*) ~(callsite-coords &form) ~msg nil nil))
130+
([msg data-map ] `(ex-info* ~(str *ns*) ~(callsite-coords &form) ~msg ~data-map nil))
131+
([msg data-map cause] `(ex-info* ~(str *ns*) ~(callsite-coords &form) ~msg ~data-map ~cause))))
132+
133+
#?(:clj
134+
(defmacro ex-info!
135+
"Throws a `truss/ex-info`."
136+
([msg ] `(throw (ex-info* ~(str *ns*) ~(callsite-coords &form) ~msg nil nil)))
137+
([msg data-map ] `(throw (ex-info* ~(str *ns*) ~(callsite-coords &form) ~msg ~data-map nil)))
138+
([msg data-map cause] `(throw (ex-info* ~(str *ns*) ~(callsite-coords &form) ~msg ~data-map ~cause)))))
139+
140+
#?(:clj
141+
(defmacro unexpected-arg!
142+
"Throws a `truss/ex-info` to indicate an unexpected argument:
143+
144+
(defn my-function [mode]
145+
(case mode
146+
:read (do <...>)
147+
:write (do <...>)
148+
(unexpected-arg! mode
149+
{:param 'mode
150+
:context `my-function
151+
:expected #{:read :write}})))
152+
153+
(my-function :invalid-mode) => throws
154+
Unexpected argument: :invalid-mode
155+
{:param 'mode,
156+
:arg {:value :unexpected, :type clojure.lang.Keyword},
157+
:context 'my-ns/my-function,
158+
:expected #{:read :write}
159+
...}"
160+
161+
([arg k1 v1 & more] (keep-callsite `(unexpected-arg! ~arg ~(apply hash-map k1 v1 more)))) ; For back compatibility
162+
([arg ] (keep-callsite `(unexpected-arg! ~arg nil)))
163+
([arg kvs]
164+
(let [argsym (gensym "arg")]
165+
`(let [~argsym ~arg]
166+
(throw
167+
(ex-info* ~(str *ns*) ~(callsite-coords &form)
168+
~(or (get kvs :msg) `(str "Unexpected argument: " (if (nil? ~argsym) "<nil>" ~argsym)))
169+
(assoc ~(dissoc kvs :msg) :arg (typed-val ~argsym)) nil)))))))
170+
171+
;;;; Context utils
118172

119173
(defn set-ctx!
120174
"Set `*ctx*` var's default (root) value. See `*ctx*` for details."
@@ -126,8 +180,6 @@
126180
"Evaluates given body with given `*ctx*` value. See `*ctx*` for details."
127181
[ctx-val & body] `(binding [*ctx* ~ctx-val] ~@body))
128182

129-
(declare unexpected-arg!)
130-
131183
(defn ^:no-doc update-ctx
132184
"Returns `new-ctx` given `old-ctx` and an update map or fn."
133185
[old-ctx update-map-or-fn]
@@ -155,8 +207,6 @@
155207

156208
;;;; Error utils
157209

158-
#?(:clj (defmacro ^:no-doc typed-val [x] `{:value ~x, :type (type ~x)}))
159-
160210
(defn error?
161211
"Returns true iff given platform error (`Throwable` or `js/Error`)."
162212
#?(:cljs {:tag 'boolean})
@@ -319,43 +369,6 @@
319369

320370
(comment (catching (zero? "9")))
321371

322-
(defn unexpected-arg!
323-
"Throws `truss/ex-info` to indicate an unexpected argument.
324-
Takes optional kvs for merging into exception's data map.
325-
326-
(let [mode :unexpected]
327-
(case mode
328-
:read (do <...>)
329-
:write (do <...>)
330-
(unexpected-arg! mode
331-
{:param 'mode
332-
:context `my-function
333-
:expected #{:read :write}}))) =>
334-
335-
Unexpected argument: :unexpected
336-
{:param 'mode,
337-
:arg {:value :unexpected, :type clojure.lang.Keyword},
338-
:context 'taoensso.encore/my-function,
339-
:expected #{:read :write}}"
340-
341-
{:arglists
342-
'([arg]
343-
[arg {:keys [msg context param expected ...]}]
344-
[arg & {:keys [msg context param expected ...]}])}
345-
346-
([arg ] (unexpected-arg! arg nil))
347-
([arg opts]
348-
(throw
349-
(ex-info (or (get opts :msg) (str "Unexpected argument: " (if (nil? arg) "<nil>" arg)))
350-
(conj {:arg (typed-val arg)} (dissoc opts :msg)))))
351-
352-
([arg k1 v1 ] (unexpected-arg! arg {k1 v1}))
353-
([arg k1 v1 k2 v2 ] (unexpected-arg! arg {k1 v1, k2 v2}))
354-
([arg k1 v1 k2 v2 k3 v3 ] (unexpected-arg! arg {k1 v1, k2 v2, k3 v3}))
355-
([arg k1 v1 k2 v2 k3 v3 k4 v4] (unexpected-arg! arg {k1 v1, k2 v2, k3 v3, k4 v4})))
356-
357-
(comment (unexpected-arg! :arg :expected '#{string?}))
358-
359372
(defn matching-error
360373
"Given a platform error and criteria for matching, returns the error if it
361374
matches all criteria. Otherwise returns nil.

test/taoensso/truss_tests.cljc

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -137,19 +137,21 @@
137137
(throws? :common {:call '(rf acc in) :args {:in {:value :a}}}))
138138
"Error in rf")])
139139

140-
;;;; Error context
141-
142-
(deftest _ex-info
143-
[(is (= (ex-data (truss/ex-info "msg")) {}))
144-
(is (= (ex-data (truss/with-ctx {:c :c1} (truss/ex-info "msg"))) {:truss/ctx {:c :c1}}))
145-
(is (= (ex-data (truss/with-ctx {:c :c1} (truss/ex-info "msg" {:d :d1}))) {:truss/ctx {:c :c1}, :d :d1}))
146-
(is (= (ex-data
147-
(truss/with-ctx {:c1 :c1a, :c2 :c2a}
148-
(truss/with-ctx+ {:c2 :c2b}
149-
(truss/with-ctx+ #(assoc % :c3 :c3a)
150-
(truss/ex-info "msg" {:d :d1})))))
151-
152-
{:truss/ctx {:c1 :c1a, :c2 :c2b, :c3 :c3a}, :d :d1}))])
140+
;;;; Truss exceptions
141+
142+
(deftest _ex-info
143+
[(is (submap? (ex-data (truss/ex-info "msg")) {:ns string?, :coords vector?}))
144+
(is (submap? (ex-data (truss/with-ctx {:c :c1} (truss/ex-info "msg"))) {:truss/ctx {:c :c1}}))
145+
(is (submap? (ex-data (truss/with-ctx {:c :c1} (truss/ex-info "msg" {:d :d1}))) {:truss/ctx {:c :c1}, :d :d1}))
146+
(is (submap? (ex-data
147+
(truss/with-ctx {:c1 :c1a, :c2 :c2a}
148+
(truss/with-ctx+ {:c2 :c2b}
149+
(truss/with-ctx+ #(assoc % :c3 :c3a)
150+
(truss/ex-info "msg" {:d :d1})))))
151+
152+
{:truss/ctx {:c1 :c1a, :c2 :c2b, :c3 :c3a}, :d :d1
153+
:ns string?
154+
:coords vector?}))])
153155

154156
;;;; Callsites
155157

0 commit comments

Comments
 (0)