|
11 | 11 |
|
12 | 12 | #?(:cljs |
13 | 13 | (: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]])) |
15 | 19 |
|
16 | 20 | #?(:clj |
17 | 21 | (:import |
|
51 | 55 |
|
52 | 56 | ;;;; Misc |
53 | 57 |
|
| 58 | +#?(:clj (defmacro ^:no-doc typed-val [x] `{:value ~x, :type (type ~x)})) |
| 59 | + |
54 | 60 | (defn ^:no-doc submap? |
55 | 61 | "Returns true iff `sub-map` is a (possibly nested) submap of `super-map`, |
56 | 62 | i.e. iff every (nested) value in `sub-map` has the same (nested) value in `super-map`. |
|
85 | 91 | true |
86 | 92 | sub-map)) |
87 | 93 |
|
88 | | -;;;; Error context |
| 94 | +;;;; Truss exceptions |
89 | 95 |
|
90 | 96 | (def ^:dynamic *ctx* |
91 | 97 | "Context map to assoc to `:truss/ctx` key of `truss/ex-info` data map. |
|
97 | 103 | using futures, agents, etc." |
98 | 104 | nil) |
99 | 105 |
|
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)) |
103 | 113 |
|
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 |
118 | 172 |
|
119 | 173 | (defn set-ctx! |
120 | 174 | "Set `*ctx*` var's default (root) value. See `*ctx*` for details." |
|
126 | 180 | "Evaluates given body with given `*ctx*` value. See `*ctx*` for details." |
127 | 181 | [ctx-val & body] `(binding [*ctx* ~ctx-val] ~@body)) |
128 | 182 |
|
129 | | -(declare unexpected-arg!) |
130 | | - |
131 | 183 | (defn ^:no-doc update-ctx |
132 | 184 | "Returns `new-ctx` given `old-ctx` and an update map or fn." |
133 | 185 | [old-ctx update-map-or-fn] |
|
155 | 207 |
|
156 | 208 | ;;;; Error utils |
157 | 209 |
|
158 | | -#?(:clj (defmacro ^:no-doc typed-val [x] `{:value ~x, :type (type ~x)})) |
159 | | - |
160 | 210 | (defn error? |
161 | 211 | "Returns true iff given platform error (`Throwable` or `js/Error`)." |
162 | 212 | #?(:cljs {:tag 'boolean}) |
|
319 | 369 |
|
320 | 370 | (comment (catching (zero? "9"))) |
321 | 371 |
|
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 | | - |
359 | 372 | (defn matching-error |
360 | 373 | "Given a platform error and criteria for matching, returns the error if it |
361 | 374 | matches all criteria. Otherwise returns nil. |
|
0 commit comments