[mod] Rework (simplify) with-signal API

This commit is contained in:
Peter Taoussanis 2024-03-29 12:30:02 +01:00
parent 46e629504b
commit dae36ef549
10 changed files with 63 additions and 80 deletions

View file

@ -24,7 +24,7 @@ Examples:
Tips: Tips:
- Test using `with-signals`: (with-signals (catch->error! ...)). - Test using `with-signal`: (with-signal (catch->error! ...)).
- Supports the same options as other signals [1]. - Supports the same options as other signals [1].
- Useful for recording errors in forms, futures, callbacks, etc. - Useful for recording errors in forms, futures, callbacks, etc.

View file

@ -18,7 +18,7 @@ Examples:
Tips: Tips:
- Test using `with-signals`: (with-signals (error! ...)). - Test using `with-signal`: (with-signal (error! ...)).
- Supports the same options as other signals [3]. - Supports the same options as other signals [3].
- `error` arg is a platform error (`java.lang.Throwable` or `js/Error`). - `error` arg is a platform error (`java.lang.Throwable` or `js/Error`).

View file

@ -18,7 +18,7 @@ Examples:
Tips: Tips:
- Test using `with-signals`: (with-signals (error! ...)). - Test using `with-signal`: (with-signal (event! ...)).
- Supports the same options as other signals [3]. - Supports the same options as other signals [3].
- A good general-purpose signal, prefer to `log!` by default, since it - A good general-purpose signal, prefer to `log!` by default, since it

View file

@ -19,7 +19,7 @@ Examples:
Tips: Tips:
- Test using `with-signals`: (with-signals (log! ...)). - Test using `with-signal`: (with-signal (log! ...)).
- Supports the same options as other signals [3]. - Supports the same options as other signals [3].
- Prefer `event!` to `log!` by default, since it better encourages structured - Prefer `event!` to `log!` by default, since it better encourages structured

View file

@ -26,7 +26,7 @@ their defaults and the focus of their call APIs (args and return values):
Tips: Tips:
- Test using `with-signals`: (with-signals (signal! ...)). - Test using `with-signal`: (with-signal (signal! ...)).
- Supports the same options as other signals [3]. - Supports the same options as other signals [3].
---------------------------------------- ----------------------------------------

View file

@ -21,7 +21,7 @@ Examples:
Tips: Tips:
- Test using `with-signals`: (with-signals (spy! ...)). - Test using `with-signal`: (with-signal (spy! ...)).
- Supports the same options as other signals [3]. - Supports the same options as other signals [3].
- Identical to `trace!`, but focused on form + level rather than form + id. - Identical to `trace!`, but focused on form + level rather than form + id.

View file

@ -21,7 +21,7 @@ Examples:
Tips: Tips:
- Test using `with-signals`: (with-signals (trace! ...)). - Test using `with-signal`: (with-signal (trace! ...)).
- Supports the same options as other signals [3]. - Supports the same options as other signals [3].
- Identical to `spy!`, but focused on form + id rather than form + level. - Identical to `spy!`, but focused on form + id rather than form + level.

View file

@ -20,7 +20,7 @@
[set-ctx! with-ctx with-ctx+ [set-ctx! with-ctx with-ctx+
set-middleware! with-middleware set-middleware! with-middleware
with-signals with-signal with-signal with-signals
signal! event! log! trace! spy! catch->error! signal! event! log! trace! spy! catch->error!
;; Via `sigs/def-api` ;; Via `sigs/def-api`
@ -72,8 +72,8 @@
impl/msg-splice impl/msg-splice
impl/msg-skip impl/msg-skip
#?(:clj impl/with-signals)
#?(:clj impl/with-signal) #?(:clj impl/with-signal)
#?(:clj impl/with-signals)
#?(:clj impl/signal!)) #?(:clj impl/signal!))
;;;; Signal help ;;;; Signal help

View file

@ -5,7 +5,11 @@
(:refer-clojure :exclude [binding]) (:refer-clojure :exclude [binding])
(:require (:require
[taoensso.encore :as enc :refer [binding have have?]] [taoensso.encore :as enc :refer [binding have have?]]
[taoensso.encore.signals :as sigs])) [taoensso.encore.signals :as sigs])
#?(:cljs
(:require-macros
[taoensso.telemere.impl :refer [with-signal]])))
(comment (comment
(remove-ns 'taoensso.telemere.impl) (remove-ns 'taoensso.telemere.impl)
@ -243,92 +247,72 @@
(enc/defonce ^:dynamic *sig-spy* "To support `with-signals`, etc." nil) (enc/defonce ^:dynamic *sig-spy* "To support `with-signals`, etc." nil)
(enc/defonce ^:dynamic *sig-handlers* "?[<wrapped-handler-fn>]" nil) (enc/defonce ^:dynamic *sig-handlers* "?[<wrapped-handler-fn>]" nil)
(defn- force-msg [sig] (defn force-msg-in-sig [sig]
(if-not (map? sig) (if-not (map? sig)
sig sig
(if-let [e (find sig :msg_)] (if-let [e (find sig :msg_)]
(assoc sig :msg_ (force (val e))) (assoc sig :msg_ (force (val e)))
(do sig)))) (do sig))))
(defn -with-signals #?(:clj
"Private util to support `with-signals` macro." (defmacro ^:public with-signal
[form-fn "Experimental.
{:keys [handle? trap-errors? force-msg?] Executes given form, trapping errors. Returns the LAST signal triggered by form.
:or {handle? true}}] Useful for tests/debugging.
(let [sigs_ (volatile! nil)] Options:
(binding [*sig-spy* [sigs_ (not :last-only?) handle?]] `trap-signals?` (default: false)
(let [form-result Should ALL signals triggered by form be trapped to prevent normal dispatch
(if-not trap-errors? to registered handlers?
(form-fn)
(enc/try*
(do [(form-fn) nil])
(catch :all t t [nil t])))]
[form-result `raw-msg?` (default: false)
(when-let [sigs @sigs_] Should delayed `:msg_` in returned signal be retained as-is?
(if force-msg? Delay is otherwise replaced by realized string.
(mapv force-msg sigs)
(do sigs)))])))) See also `with-signals`."
([ form] `(with-signal false false ~form))
([ trap-signals? form] `(with-signal false ~trap-signals? ~form))
([raw-msg? trap-signals? form]
`(let [sig_# (volatile! nil)]
(binding [*sig-spy* [sig_# :last-only ~trap-signals?]]
(enc/try* ~form (catch :all _#)))
(if ~raw-msg?
(do @sig_#)
(force-msg-in-sig @sig_#))))))
#?(:clj #?(:clj
(defmacro ^:public with-signals (defmacro ^:public with-signals
"Experimental. "Experimental.
Executes given form and records any signals triggered by it. Like `with-signal` but returns [[<form-value> <form-error>] [<signal1> ...]].
Return value depends on given options. Useful for tests/debugging. Useful for tests/debugging."
([ form] `(with-signals false false ~form))
([ trap-signals? form] `(with-signals false ~trap-signals? ~form))
([raw-msgs? trap-signals? form]
`(let [sigs_# (volatile! nil)
form-result#
(binding [*sig-spy* [sigs_# (not :last-only) ~trap-signals?]]
(enc/try*
(do [~form nil])
(catch :all t# [nil t#])))
Options: sigs#
(if ~raw-msgs?
(do @sigs_#)
(mapv force-msg-in-sig @sigs_#))]
`handle?` [form-result# (not-empty sigs#)]))))
Should registered handlers receive signals triggered by form, as usual?
Default: true.
`trap-errors?`
If true: returns [[form-value form-error] signals], trapping any form error.
If false: returns [ form-value signals], throwing on form error.
Default: false.
`force-msg?`
Should delayed `:msg_` keys in signals be replaced with realized strings?
Default: false.
See also `with-signal` for a simpler API."
{:arglists
'([form]
[{:keys [handle? trap-errors? force-msg?]
:or {handle? true}} form])}
([ form] `(-with-signals (fn [] ~form) nil))
([opts form] `(-with-signals (fn [] ~form) ~opts))))
#?(:clj
(defmacro ^:public with-signal
"Experimental
Minimal version of `with-signals`.
Executes given form and returns the last signal triggered by it.
Useful for tests/debugging.
- Always allows registered handlers to receive signals as usual.
- Always traps form errors.
- Never forces `:msg_` key.
See also `with-signals` for more options."
[form]
`(let [sig_# (volatile! nil)]
(binding [*sig-spy* [sig_# :last-only :handle]]
(enc/catching ~form))
@sig_#)))
(defn dispatch-signal! (defn dispatch-signal!
"Dispatches given signal to registered handlers, supports `with-signals`." "Dispatches given signal to registered handlers, supports `with-signal/s`."
[signal] [signal]
(or (or
(when-let [[v_ last-only? handle?] *sig-spy*] (when-let [[v_ last-only? trap-signals?] *sig-spy*]
(let [sv (sigs/signal-value signal nil)] (let [sv (sigs/signal-value signal nil)]
(if last-only? (if last-only?
(vreset! v_ sv) (vreset! v_ sv)
(vswap! v_ #(conj (or % []) sv)))) (vswap! v_ #(conj (or % []) sv))))
(when-not handle? :stop)) (when trap-signals? :stop))
(sigs/call-handlers! *sig-handlers* signal))) (sigs/call-handlers! *sig-handlers* signal)))
@ -618,7 +602,7 @@
true)))))))) true))))))))
(comment (comment
(with-signals (signal! {:level :warn :let [x :x] :msg ["Test" "message" x] :data {:a :A :x x} :run (+ 1 2)})) (with-signal (signal! {:level :warn :let [x :x] :msg ["Test" "message" x] :data {:a :A :x x} :run (+ 1 2)}))
(macroexpand '(signal! {:level :warn :let [x :x] :msg ["Test" "message" x] :data {:a :A :x x} :run (+ 1 2)})) (macroexpand '(signal! {:level :warn :let [x :x] :msg ["Test" "message" x] :data {:a :A :x x} :run (+ 1 2)}))
(do (do
@ -664,9 +648,8 @@
(defn test-interop! [msg test-fn] (defn test-interop! [msg test-fn]
(let [msg (str "Interop test: " msg " (" (enc/uuid-str) ")") (let [msg (str "Interop test: " msg " (" (enc/uuid-str) ")")
[_ [signal]] signal
(binding [*rt-sig-filter* nil] ; Without runtime filters (binding [*rt-sig-filter* nil] ; Without runtime filters
(-with-signals (fn [] (test-fn msg)) (with-signal :raw :trap (test-fn msg)))]
{:handle? false}))]
(= (force (get signal :msg_)) msg))) (= (force (get signal :msg_)) msg)))

View file

@ -72,7 +72,7 @@
*err* (if-let [err# ~err] (osw (telemere-print-stream err#)) *err*)] *err* (if-let [err# ~err] (osw (telemere-print-stream err#)) *err*)]
~form))) ~form)))
(comment (impl/with-signals (with-out->telemere (println "hello")))) (comment (impl/with-signal (with-out->telemere (println "hello"))))
(enc/defonce ^:private orig-out_ "Original `System/out`, or nil" (atom nil)) (enc/defonce ^:private orig-out_ "Original `System/out`, or nil" (atom nil))
(enc/defonce ^:private orig-err_ "Original `System/err`, or nil" (atom nil)) (enc/defonce ^:private orig-err_ "Original `System/err`, or nil" (atom nil))