-
- Downloads
Clarify stacktraces from malli validation errors (#34712)
* Improve stacktraces for validation errors in malli-instrumented fns First, when we throw exceptions in `metabase.util.malli/validate`, manually edit the stacktrace to remove those StackTraceElements that start with `metabase.util.malli.fn`. Second, instead of rewriting `mu/fn` bodies like so: ``` (let [f& ...] (fn [args] (do-validation) (f& args))) ``` Rewrite them like this: ``` (fn [args] (do-validation) ((fn [args'] ...) args)) ``` I wasn't entirely sure that this wouldn't have a negative performance impact, so I tested it with these toy examples: ``` (def option-1 (let [f (fn [x] (+ x 10))] (fn [x] (f x)))) (def option-2 (fn [x] ((fn [x'] (+ x 10)) x))) ``` Then evaluated them with Criterium to benchmark. The first option (our current behavior) looked like this: ``` Evaluation count : 10184236800 in 60 samples of 169737280 calls. Execution time sample mean : 4.117003 ns Execution time mean : 4.117119 ns Execution time sample std-deviation : 0.017140 ns Execution time std-deviation : 0.017686 ns Execution time lower quantile : 4.100963 ns ( 2.5%) Execution time upper quantile : 4.149603 ns (97.5%) Overhead used : 1.783065 ns ``` The second option (the new behavior) looked like this: ``` Evaluation count : 13861192260 in 60 samples of 231019871 calls. Execution time sample mean : 2.553532 ns Execution time mean : 2.553510 ns Execution time sample std-deviation : 0.019674 ns Execution time std-deviation : 0.019824 ns Execution time lower quantile : 2.524266 ns ( 2.5%) Execution time upper quantile : 2.599826 ns (97.5%) Overhead used : 1.783065 ns ``` So overall the performance does not look to be worse and may actually be slightly better. In the end, we end up with stacktraces like: ``` (mu/defn bad-input :- :string [x :- :int] (str x)) (mu/defn bad-output :- :string [x :- :int] x) (defn call-bad-input [] (bad-input "1")) (call-bad-input) ;; Invalid input: ["should be an integer"] ;; {:type :metabase.util.malli.fn/invalid-input, ;; :error {:schema :int, :value "1", :errors ({:path [], :in [], :schema :int, :value "1"})}, ;; :humanized ["should be an integer"], ;; :schema :int, ;; :value "1", ;; :fn-name playground/bad-input} ;; REPL: 19 playground/bad-input ;; REPL: 19 playground/bad-input ;; REPL: 28 playground/call-bad-input ;; REPL: 27 playground/call-bad-input (defn call-bad-output [] (bad-output 1)) (call-bad-output) ;; Invalid output: ["should be a string"] ;; {:type :metabase.util.malli.fn/invalid-output, ;; :error {:schema :string, :value 1, :errors ({:path [], :in [], :schema :string, :value 1})}, ;; :humanized ["should be a string"], ;; :schema :string, ;; :value 1, ;; :fn-name playground/bad-output} ;; REPL: 23 playground/bad-output ;; REPL: 23 playground/bad-output ;; REPL: 31 playground/call-bad-output ;; REPL: 30 playground/call-bad-output ```
Please register or sign in to comment