Hi! First time posting in this channel … looking for constructive feedback and thoughts a new lib feature… I’m currently adding some additional functionality to the https://github.com/paintparty/bling library that will give users the ability to construct nice callouts based on Malli validation errors:
;; Currently on a feature branch with corresponding snapshot release
;; io.github.paintparty/bling {:mvn/version "0.7.0-SNAPSHOT"}
(require '[bling.explain :refer [explain-malli]])
(def Address
[:map
[:id string?]
[:tags [:set keyword?]]
[:address
[:map
[:street string?]
[:city string?]
[:zip int?]
[:lonlat [:tuple double? double?]]]]])
(explain-malli
Address
{:id "Lillan",
:tags #{:coffee :artesan :garden},
:address
{:street "Ahlmanintie 29", :zip 33100, :lonlat [61.4858322 87.34]}})
The above will result in:Another example, with additional options argument (turns off printing of the schema):
(explain-malli
Address
{:id "Lillan",
:tags #{"coffee" :artesan :garden},
:address
{:city "Tempare" :street "Ahlmanintie 29", :zip 33100, :lonlat [61.4858322 87.34]}}
{:display-schema? false})
This style seems to be much more verbose than humanize if there are multiple errors but maybe you could have best of both worlds if you inline the errors?
{:id "Lillan",
:tags #{"coffee" :artesan :garden},
;^^^^^^^
;Must satisfy:
; keyword?
:address
{:city "Tempare" :street "Ahlmanintie 29", :zip 33100, :lonlat [61.4858322 87.34]}}Or even:
{:id "Lillan",
:tags #{-----------
| "coffee" | :artesan :garden},
<<<<<<<<<<------------
v :address
v {:city "Tempare" :street "Ahlmanintie 29", :zip 33100, :lonlat [61.4858322 87.34]}}
v
v
Must satisfy:
keyword?
in particular I was comparing it to this example https://github.com/metosin/malli?tab=readme-ov-file#prettyexplain
The really tricky case which I don't think humanize does right is dealing with disjunctions. for example [:or :string :int] and [:and :string :int] humanize the same when passed nil https://malli.io/?value=nil&schema=%5B%3Aor%20%3Astring%20%3Aint%5D
You get the same 2 :errors back from (m/explain [:or :string :int] nil) and (m/explain [:and :string :int] nil) but the pretty printers should really handle them differently.
I think for [:and :string :int] it should be something like:
nil
^^^
Must satisfy:
:string
and
Must satisfy:
:int
And [:or :string :int] should be
nil
^^^
Must satisfy:
:string
or
Must satisfy:
:intOne way to start approaching this is to group errors using their common prefix :path 's
hmmm good point it … it looks like I will need to do some grouping, esp with multiple problems on the same value e.g. (m/explain [:or :string :int] :foo)
You could even use some heuristics to cull branches. e.g.,
(explain [:or [:map [:a :int]] :boolean] {:a "a"})
doesn't need to mention it's not a boolean.There's some ideas in this direction https://github.com/fluree/db/blob/3bd613c5f74a900179fe91454bf2542071453271/src/fluree/db/validation.cljc#L61, see https://github.com/fluree/db/pull/598 for the context.
You can preview several examples of bling.explain/explain-malli http://malli.in your terminal with the following snippet:
clj -Sdeps '{:deps {io.github.paintparty/bling {:mvn/version "0.7.0-SNAPSHOT"}}}' -e "(require '[bling.sample]) (bling.sample/explain-malli-examples)"
In order to get the syntax coloring shown in the examples above, you need to export a BLING_MOOD env var with a value of "light" or "dark" . Otherwise the printing will default to a neutral theme with no colors.
Docs here:
https://github.com/paintparty/bling/tree/light-dark-color-variants?tab=readme-ov-file#usage-with-malli (edited)• instead of (edn/read-string (with-out-str (prn (:schema problem)))) use (malli.core/form (:schema problem))
• for clean-schema see remove-swagger-keys for a better approach https://github.com/metosin/malli/blob/master/docs/tips.md#walking-schema-and-entry-properties
@ambrosebs Thank you! I now remember thinking “There’s gotta be a better way to do this with something in malli’s api” when I was implementing those bits^. I will use your suggestions 🙌
I think the doc is a little out of date, there's a https://github.com/metosin/malli/blob/bf7d50bbef8feae11944bf8b094bfd600ed164e4/test/malli/util_test.cljc#L172-L177 mu/update-properties
That will help simplify walk-properties
It might end up looking something like:
(defn walk-properties [schema f]
(m/walk
schema
(fn [s _ _ _]
(mu/update-properties s f))
{::m/walk-entry-vals true}))Oh maybe it doesn't work with :map entry properties. e.g., [:map [:a {:prop :here} :foo]]. Try it out.
now I remember, I think you should just use the documented way because there's a bug with ::m/walk-entry-vals in that it doesn't update its properties https://github.com/metosin/malli/pull/1091