diff --git a/src/elin/interceptor/tap.clj b/src/elin/interceptor/tap.clj index 2a7e2bc..38734a3 100644 --- a/src/elin/interceptor/tap.clj +++ b/src/elin/interceptor/tap.clj @@ -15,24 +15,53 @@ (def ^:private tapped-atom-sym (gensym "elin-tapped")) +(defn- convert-to-edn-compliant-data-fn-code + "Return a code for converting data to EDN-compliant data + cf. https://github.com/edn-format/edn?tab=readme-ov-file#built-in-elements" + [] + `(fn datafy# [x#] + (clojure.walk/prewalk + (fn [v#] + (cond + ;; no conversion + (or + (nil? v#) + (boolean? v#) + (char? v#) + (symbol? v#) + (keyword? v#) + (number? v#) + (inst? v#) + (uuid? v#)) + v# + ;; vectors + (vector? v#) + (mapv datafy# v#) + ;; maps + (map? v#) + (update-vals v# datafy#) + ;; sets + (set? v#) + (set (map datafy# v#)) + ;; lists + (sequential? v#) + (map datafy# v#) + + :else + (let [datafied# (clojure.core.protocols/datafy v#)] + (if (not= datafied# v#) + datafied# + (str v#))))) + x#))) + (defn- initialize-code [{:keys [http-server-port max-store-size]}] (str `(do (in-ns '~ns-sym) (refer-clojure) - (defn datafy# - [x#] - (clojure.walk/prewalk - (fn [v#] - (cond - (map? v#) (update-vals v# datafy#) - (vector? v#) (mapv datafy# v#) - (sequential? v#) (map datafy# v#) - (or (keyword? v#) (symbol? v#)) v# - (instance? Object v#) (str v#) - :else (clojure.core.protocols/datafy v#))) - x#)) + (def convert-to-edn-compliant-data# + ~(convert-to-edn-compliant-data-fn-code)) (defn tap-handler-request# [value#] @@ -59,7 +88,7 @@ (defn ~tap-fn-sym [x#] - (let [value# (datafy# x#)] + (let [value# (convert-to-edn-compliant-data# x#)] (tap-handler-request# value#) (when (<= ~max-store-size (count (deref ~tapped-atom-sym))) (swap! ~tapped-atom-sym (fn [v#] (drop-last 1 v#)))) diff --git a/test/elin/interceptor/tap_test.clj b/test/elin/interceptor/tap_test.clj new file mode 100644 index 0000000..13c68df --- /dev/null +++ b/test/elin/interceptor/tap_test.clj @@ -0,0 +1,77 @@ +(ns elin.interceptor.tap-test + (:require + [clojure.string :as str] + [clojure.test :as t] + [elin.interceptor.tap :as sut] + [elin.test-helper :as h])) + +(t/use-fixtures :once h/malli-instrument-fixture) +(t/use-fixtures :once h/warn-log-level-fixture) + +(defmacro define-convert-to-edn-compliant-data-fn [] + (#'sut/convert-to-edn-compliant-data-fn-code)) + +(t/deftest convert-to-edn-compliant-data-test + ;; https://github.com/edn-format/edn?tab=readme-ov-file#built-in-elements + (let [convert (define-convert-to-edn-compliant-data-fn)] + (t/testing "nil" + (t/is (= nil (convert nil)))) + + (t/testing "boolean" + (t/is (= true (convert true))) + (t/is (= false (convert false)))) + + (t/testing "strings" + (t/is (= "foo" (convert "foo")))) + + (t/testing "characters" + (t/is (= \c (convert \c))) + (t/is (= \newline (convert \newline)))) + + (t/testing "symbols" + (t/is (= 'symbol (convert 'symbol))) + (t/is (= 'ns/symbol (convert 'ns/symbol)))) + + (t/testing "keywords" + (t/is (= :keyword (convert :keyword))) + (t/is (= :ns/keyword (convert :ns/keyword)))) + + (t/testing "integers" + (t/is (= 10 (convert 10))) + (t/is (= -10 (convert -10)))) + + (t/testing "floating point numbers" + (t/is (= 1.2 (convert 1.2))) + (t/is (= -1.2 (convert -1.2)))) + + (t/testing "lists" + (t/is (= '(1 "foo") (convert '(1 "foo")))) + (t/testing "lazy sequences" + (t/is (= '(1 2 3) (convert (map inc (range 3))))))) + + (t/testing "vectors" + (t/is (= [1 "foo"] (convert [1 "foo"])))) + + (t/testing "maps" + (t/is (= {:a 1 :b "foo"} (convert {:a 1 :b "foo"})))) + + (t/testing "sets" + (t/is (= #{1 "foo"} (convert #{1 "foo"})))) + + (t/testing "built-in tagged elements" + (t/testing "#inst" + (let [v #inst "1985-04-12T23:20:50.52Z"] + (t/is (= v (convert v))))) + + (t/testing "#uuid" + (let [v #uuid "f81d4fae-7dec-11d0-a765-00a0c91e6bf6"] + (t/is (= v (convert v)))))) + + (t/testing "object" + (t/is (str/starts-with? (convert inc) + "clojure.core$inc@") + "Should be stringified")) + + (t/testing "datafiable" + (t/testing "Exception" + (t/is (contains? (convert (Exception. "foo")) :via))))))