Fork me on GitHub
#malli
<
2023-05-21
>
Karol Wójcik20:05:22

Hey, I’m using function schemas and I’m wondering if it’s possible to include function name and schema name in the instrumentation error?

p-himik20:05:22

When you provide a custom report function to instrument!, the function name will be included. The schema is included by default. Adding to the power example at https://github.com/metosin/malli/blob/master/docs/function-schemas.md#defn-instrumentation:

(mi/instrument! {:report m/-fail!})
..instrumented #'dev/power
=> nil
(power 6)
Execution error (ExceptionInfo) at malli.core/-exception (core.cljc:138).
:malli.core/invalid-output
(ex-data *e)
=>
{:type :malli.core/invalid-output,
 :message :malli.core/invalid-output,
 :data {:output [:int {:max 6}], :value 36, :args [6], :schema [:=> [:cat :int] [:int {:max 6}]], :fn-name dev/power}}

Karol Wójcik10:05:04

Can it also return the schema name?

p-himik10:05:32

What is "schema name"?

p-himik10:05:14

If you mean that in

(def my-schema [:map [:a int?]])
the my-schema symbol names the schema, then no. Schemas are just data (unless you have already called m/schema on it), so the don't know the var that was used to store them. You can, however, attach arbitrary data to a schema and it will be reported as well:
(m/explain [:map {::schema-name "An int under :a"} [:a int?]] {:a ""})
=>
{:schema [:map #:dev{:schema-name "An int under :a"} [:a int?]],
 :value {:a ""},
 :errors ({:path [:a], :in [:a], :schema int?, :value ""})}

👍 2
p-himik10:05:26

It's mentioned in the documentation here https://github.com/metosin/malli#validation in the code block under "Schemas can have properties".

escherize21:05:36

I did a little experiment to make a little pattern matching macro that works with malli multi-schemas. source in 🧵 Any thoughts?

❤️ 4
escherize21:05:05

(defmacro match-maker
  "Return a function that takes 2 things:
  a malli multi schema M,
  pairs of multi schema dispatch values -> functions

  And returns a function, that when given a value that adheres to the
  multischema M, calls the dispatch function and calls the right value on it.
  "
  [schema & efp]
  `(let [_# (#'clojure.core/assert-args (even? (count '~efp)) "an even number of forms in evp vector")
         enum->fn-pairs# (mapv vec (partition 2 '~efp))
         children# (m/children ~schema)
         expected-branches# (mapv first children#)
         found-branches# (mapv first enum->fn-pairs#)
         dispatch-val->f# (into {} (eval enum->fn-pairs#))
         dispatch-fn# (-> ~schema m/properties :dispatch)]
     (when (not= expected-branches# found-branches#)
       (throw (ex-info (str "Error: mismatched branches in match-maker."
                            "\nExpected: " (pr-str expected-branches#)
                            "\nReceived: " (pr-str found-branches#))
                       {:exptected expected-branches#
                        :found found-branches#})))
     (fn [value#]
       (let [branch# (dispatch-fn# value#)
             branch-fn# (get dispatch-val->f# branch#)]
         (branch-fn# value#)))))

escherize21:05:17

;; Given a multi schema:
(def PetSchema
  [:multi {:dispatch :type}
   [:dog [:map [:good-dog? :boolean]]]
   [:cat [:map [:temperment [:enum :nice :mean]]]]])
;; make a function that can dispatch on the keys
(def pet-noise
  (match-maker PetSchema
               :dog (fn [{:keys [good-dog?]}] (if good-dog? "good dog." "bad dog."))
               :cat (fn [{:keys [temperment]}] (str "kitty is " (name temperment)))))
;; call the function
(mapv pet-noise
      [{:type :dog :good-dog? true} {:type :dog :good-dog? false}
       {:type :cat :temperment :nice} {:type :cat :temperment :mean}])
;; => ["good dog." "bad dog." "kitty is nice" "kitty is mean"]

escherize21:05:17

The nice thing about it is when you update the schema:

escherize21:05:23

update the schema with a new branch

(def PetSchema
  [:multi {:dispatch :type}
   [:dog [:map [:good-dog? :boolean]]]
   [:cat [:map [:temperment [:enum :nice :mean]]]]
   [:bird [:map]]])
And try to use match-maker on the updated schema, you’ll get an error because you didn’t handle :bird!
(try (def pet-noise
       (match-maker PetSchema
                    :dog (fn [{:keys [good-dog?]}] (if good-dog? "good dog." "bad dog."))
                    :cat (fn [{:keys [temperment]}] (str "kitty is " (name temperment)))))
     (catch Exception e [(ex-message e) (ex-data e)]))
;; => ["Missing Branches.
;;      Expected: [:dog :cat :bird]
;;      Received: [:dog :cat]"
;;      {:exptected [:dog :cat :bird], :found [:dog :cat]}]