From 76e9009f19b0a2cd1cffb9ed4b4db72b1418b861 Mon Sep 17 00:00:00 2001 From: Artem Medeu Date: Mon, 24 Oct 2022 12:15:43 +0600 Subject: [PATCH 1/4] refactor: impl/error-messages -> errors/gather-error-messages --- src/k16/gx/beta/core.cljc | 4 ++-- src/k16/gx/beta/errors.cljc | 17 +++++++++++++++ src/k16/gx/beta/impl.cljc | 43 ++++++++++++------------------------- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/k16/gx/beta/core.cljc b/src/k16/gx/beta/core.cljc index 2447767..871f157 100644 --- a/src/k16/gx/beta/core.cljc +++ b/src/k16/gx/beta/core.cljc @@ -232,13 +232,13 @@ (props-fn arg-map) (catch #?(:clj Throwable :cljs :default) e (gx.err/throw-gx-err "Props function error" - {:ex-message (impl/error-message e) + {:ex-message (gx.err/gather-error-messages e) :args arg-map})))) (defn- wrap-error [e arg-map] (gx.err/gx-err-data "Signal processor error" - {:ex-message (impl/error-message e) + {:ex-message (gx.err/gather-error-messages e) :ex e :args arg-map})) diff --git a/src/k16/gx/beta/errors.cljc b/src/k16/gx/beta/errors.cljc index 10cb182..25c18e5 100644 --- a/src/k16/gx/beta/errors.cljc +++ b/src/k16/gx/beta/errors.cljc @@ -6,6 +6,23 @@ "Error context is used for creating/throwing exceptions with contextual data" (map->ErrorContext {:error-type :general})) +(defn gather-error-messages + [ex] + #?(:clj (->> ex + (iterate ex-cause) + (take-while some?) + (mapv ex-message) + (interpose "; ") + (apply str)) + :cljs (cond + (instance? cljs.core/ExceptionInfo ex) + (ex-message ex) + + (instance? js/Error ex) + (ex-message ex) + + :else ex))) + (defn gx-err-data ([internal-data] (gx-err-data nil internal-data)) diff --git a/src/k16/gx/beta/impl.cljc b/src/k16/gx/beta/impl.cljc index b65e0e8..2079993 100644 --- a/src/k16/gx/beta/impl.cljc +++ b/src/k16/gx/beta/impl.cljc @@ -142,23 +142,6 @@ [& maps] (reduce merger maps)) -(defn error-message - [ex] - #?(:clj (->> ex - (iterate ex-cause) - (take-while some?) - (mapv ex-message) - (interpose "; ") - (apply str)) - :cljs (cond - (instance? cljs.core/ExceptionInfo ex) - (ex-message ex) - - (instance? js/Error ex) - (ex-message ex) - - :else ex))) - (def locals #{'gx/ref 'gx/ref-keys}) (defn local-form? @@ -192,21 +175,23 @@ :else x)) form)) -#?(:clj - (defn quiet-requiring-resolve - [sym] - (try - (requiring-resolve sym) - (catch Throwable _ nil)))) - (defn resolve-symbol [sym] (when (symbol? sym) - #?(:cljs (namespace-symbol sym) - :clj (some-> sym - (namespace-symbol) - (quiet-requiring-resolve) - (var-get))))) + (if-let [nss #?(:cljs (namespace-symbol sym) + :clj (try + (some-> sym + (namespace-symbol) + (requiring-resolve) + (var-get)) + (catch Throwable e + (gx.err/add-err-cause + {:title :symbol-cannot-be-resolved + :data sym + :exception e}))))] + nss + (gx.err/add-err-cause {:title :symbol-cannot-be-resolved + :data sym})))) (defn form->runnable [form-def] (let [props* (atom #{}) From 40d90c2c44bb1a447869a8e2da29ca9518a66c0c Mon Sep 17 00:00:00 2001 From: Artem Medeu Date: Mon, 24 Oct 2022 12:16:30 +0600 Subject: [PATCH 2/4] feat(TRE-856): add error causes --- src/k16/gx/beta/errors.cljc | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/k16/gx/beta/errors.cljc b/src/k16/gx/beta/errors.cljc index 25c18e5..429ee5d 100644 --- a/src/k16/gx/beta/errors.cljc +++ b/src/k16/gx/beta/errors.cljc @@ -1,10 +1,12 @@ (ns k16.gx.beta.errors) -(defrecord ErrorContext [error-type node-key node-contents signal-key]) +(defrecord ErrorContext [error-type node-key + node-contents signal-key + causes]) (def ^:dynamic *err-ctx* "Error context is used for creating/throwing exceptions with contextual data" - (map->ErrorContext {:error-type :general})) + (map->ErrorContext {:error-type :general :causes []})) (defn gather-error-messages [ex] @@ -23,6 +25,12 @@ :else ex))) +(defn add-err-cause + "Adds cause to error context, evaluates to nil" + [cause] + (set! *err-ctx* (update *err-ctx* :causes conj cause)) + nil) + (defn gx-err-data ([internal-data] (gx-err-data nil internal-data)) @@ -65,14 +73,21 @@ (flatten) (apply str))) +(defn- cause->str + [{:keys [title data exception]}] + (str "cause(" title " = " data "): " (gather-error-messages exception))) + (defn humanize-error - [{:keys [node-key signal-key message]} & rest-of-error] + [{:keys [node-key signal-key message causes]} & rest-of-error] (let [rest-of-error (filter seq rest-of-error)] (apply str (concat [(or message "Error") ": " (tokenize "node = " node-key "signal = " signal-key)] (when (seq rest-of-error) (conj (interpose "\n\t• " rest-of-error) + "\n\t• ")) + (when (seq causes) + (conj (interpose "\n\t• " (map cause->str causes)) "\n\t• ")))))) (defmulti humanize :error-type) From e4e9d67bcb827008925e1c776ad74f810bd6fa42 Mon Sep 17 00:00:00 2001 From: Artem Medeu Date: Mon, 24 Oct 2022 12:16:43 +0600 Subject: [PATCH 3/4] test: update tests --- test/k16/gx/beta/core_test.cljc | 50 +++++++++++++++++++++---------- test/k16/gx/beta/system_test.cljc | 12 +++++--- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/test/k16/gx/beta/core_test.cljc b/test/k16/gx/beta/core_test.cljc index 81ad8ae..e75fe70 100644 --- a/test/k16/gx/beta/core_test.cljc +++ b/test/k16/gx/beta/core_test.cljc @@ -118,7 +118,8 @@ :node-contents '(throw (ex-info "foo" (gx/ref :a))), :message "Special forms are not supported", :internal-data - {:form-def '(throw (ex-info "foo" (gx/ref :a))), :token 'throw}} + {:form-def '(throw (ex-info "foo" (gx/ref :a))), :token 'throw} + :causes []} failure)))) (deftest component-support-test @@ -170,7 +171,8 @@ "circular :a -> :b -> :a")}, :message "Dependency errors", :error-type :deps-sort, - :signal-key :gx/start} + :signal-key :gx/start + :causes []} failure)))) (defn my-props-fn @@ -236,7 +238,7 @@ :node-contents '(throw "starting"), :message "Special forms are not supported", :internal-data {:form-def '(throw "starting"), :token 'throw}} - (-> gx-norm :failures first))))) + (-> gx-norm :failures first (dissoc :causes)))))) (testing "unresolved symbol failure" (let [graph {:a {:nested-a 1} @@ -255,7 +257,7 @@ :internal-data {:form-def '(some-not-found-symbol "stopping"), :token 'some-not-found-symbol}} - failure)))) + (dissoc failure :causes))))) #?(:clj (testing "processor failure" (let [graph {:a {:nested-a 1} @@ -274,7 +276,8 @@ :node-key :b, :node-contents #:gx{:start '(/ (gx/ref :z) 0), :stop '(println "stopping")}, - :signal-key :gx/start} + :signal-key :gx/start + :causes []} {:internal-data {:ex-message (str "class clojure.lang.Keyword cannot be cast" @@ -287,7 +290,8 @@ :error-type :node-signal, :node-key :c, :node-contents '(inc :bar), - :signal-key :gx/start}) + :signal-key :gx/start + :causes []}) p-gx-started (gx/signal gx-norm :gx/start)] (is (= expect (->> @p-gx-started @@ -318,7 +322,8 @@ :node-contents #:gx{:component 'k16.gx.beta.core-test/props-validation-component, :start #:gx{:props-fn 'k16.gx.beta.core-test/my-props-fn}}, - :signal-key :gx/start} + :signal-key :gx/start + :causes []} (first (:failures gx-started)))))))) (comment @@ -344,13 +349,15 @@ :error-type :node-signal, :node-key :d, :node-contents '(gx/ref :c), - :signal-key :gx/start} + :signal-key :gx/start + :causes []} {:internal-data {:dep-node-keys '(:b)}, :message "Failure in dependencies", :error-type :node-signal, :node-key :c, :node-contents '(gx/ref :b), - :signal-key :gx/start} + :signal-key :gx/start + :causes []} {:internal-data {:ex-message "Divide by zero", :args {:props {:a 1}, :state :uninitialised, :value nil, :instance nil}}, @@ -358,7 +365,8 @@ :error-type :node-signal, :node-key :b, :node-contents '(/ (gx/ref :a) 0), - :signal-key :gx/start}) + :signal-key :gx/start + :causes []}) p-gx-started (gx/signal gx-map :gx/start) failures (-> (:failures @p-gx-started) (vec) @@ -381,7 +389,8 @@ :signal-key :gx/start} (-> (:failures gx-map) (first) - (update :internal-data dissoc :ex))))))))) + (update :internal-data dissoc :ex) + (dissoc :causes))))))))) (def ^:export push-down-props-component {:gx/props '(gx/ref-keys [:a]) @@ -420,7 +429,9 @@ :node-contents #:gx{:component 'k16.gx.beta.core-test/non-existent} :internal-data - {:component 'k16.gx.beta.core-test/non-existent}} + {:component 'k16.gx.beta.core-test/non-existent} + :causes [{:title :symbol-cannot-be-resolved + :data 'k16.gx.beta.core-test/non-existent}]} (first (:failures gx-map))))) (let [graph {:c {:gx/component 'k16.gx.beta.core-test/invalid-component}} @@ -433,7 +444,8 @@ #:gx{:component 'k16.gx.beta.core-test/invalid-component} :internal-data {:component {:some "invalid component"} - :schema-error #{[:some ["disallowed key"]]}}} + :schema-error #{[:some ["disallowed key"]]}} + :causes []} (-> gx-map :failures first @@ -451,7 +463,8 @@ {:component #:gx{:start #:gx{:processor "non callable val"}} :schema-error #{[:gx/start - #:gx{:processor ["should be an ifn"]}]}}} + #:gx{:processor ["should be an ifn"]}]}} + :causes []} (-> gx-map :failures first @@ -552,13 +565,18 @@ (deftest unserolvable-symbol-test (let [graph {:a 'foo.bar/baz} - norm (gx/normalize {:graph graph})] + norm (gx/normalize {:graph graph}) + failure (first (:failures norm)) + cause (first (:causes failure))] + #?(:clj + (is (instance? java.io.FileNotFoundException (:exception cause)))) + (is (= :symbol-cannot-be-resolved (:title cause))) (is (= {:message "Unable to resolve symbol", :error-type :normalize-node, :node-key :a, :node-contents 'foo.bar/baz, :internal-data {:form-def 'foo.bar/baz, :token 'foo.bar/baz}} - (first (:failures norm)))))) + (dissoc failure :causes))))) (def ^:export proc-instance-component {:gx/start {:gx/processor (fn [{:keys [props]}] diff --git a/test/k16/gx/beta/system_test.cljc b/test/k16/gx/beta/system_test.cljc index c84f8cc..7c824fa 100644 --- a/test/k16/gx/beta/system_test.cljc +++ b/test/k16/gx/beta/system_test.cljc @@ -43,27 +43,31 @@ "circular :a -> :b -> :a"]}, :message "Dependency errors", :error-type :deps-sort, - :signal-key :gx/start} + :signal-key :gx/start + :causes []} {:internal-data {:errors [":c depends on :z, but :z doesn't exist" "circular :a -> :b -> :a"]}, :message "Dependency errors", :error-type :deps-sort, - :signal-key :gx/suspend} + :signal-key :gx/suspend + :causes []} {:internal-data {:errors [":c depends on :z, but :z doesn't exist" "circular :a -> :b -> :a"]}, :message "Dependency errors", :error-type :deps-sort, - :signal-key :gx/resume} + :signal-key :gx/resume + :causes []} {:internal-data {:errors [":c depends on :z, but :z doesn't exist" "circular :a -> :b -> :a"]}, :message "Dependency errors", :error-type :deps-sort, - :signal-key :gx/stop}]} + :signal-key :gx/stop + :causes []}]} (ex-data err)))))) From ee405fc56c1377dbc0e7b9236a0ec85a9e9f4c51 Mon Sep 17 00:00:00 2001 From: Artem Medeu Date: Mon, 24 Oct 2022 12:17:04 +0600 Subject: [PATCH 4/4] chore: clj-kondo configs --- .clj-kondo/funcool/promesa/config.edn | 3 ++- src/k16/gx/beta/core.cljc | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.clj-kondo/funcool/promesa/config.edn b/.clj-kondo/funcool/promesa/config.edn index 2de4fad..1844f77 100644 --- a/.clj-kondo/funcool/promesa/config.edn +++ b/.clj-kondo/funcool/promesa/config.edn @@ -5,4 +5,5 @@ promesa.core/plet clojure.core/let promesa.core/loop clojure.core/loop promesa.core/recur clojure.core/recur - promesa.core/with-redefs clojure.core/with-redefs}} + promesa.core/with-redefs clojure.core/with-redefs + promesa.core/doseq clojure.core/doseq}} diff --git a/src/k16/gx/beta/core.cljc b/src/k16/gx/beta/core.cljc index 871f157..1792787 100644 --- a/src/k16/gx/beta/core.cljc +++ b/src/k16/gx/beta/core.cljc @@ -405,6 +405,7 @@ :else gxm)))))) +#_{:clj-kondo/ignore [:unresolved-symbol]} (comment (def graph {:options {:port 8080} :router {:some "router"}