malli

2025-05-29T20:25:11.357469Z

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:

❤️ 1
1
2025-05-29T20:27:14.774499Z

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})

👏 1
❤️ 2
2025-05-29T22:42:36.493549Z

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]}}

2025-05-29T22:45:04.612019Z

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?

2025-05-29T22:47:24.102659Z

in particular I was comparing it to this example https://github.com/metosin/malli?tab=readme-ov-file#prettyexplain

2025-05-29T22:53:43.421159Z

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&amp;schema=%5B%3Aor%20%3Astring%20%3Aint%5D

2025-05-29T22:56:56.594239Z

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.

2025-05-29T23:01:03.384639Z

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:
  :int

2025-05-29T23:03:18.976609Z

One way to start approaching this is to group errors using their common prefix :path 's

2025-05-29T23:30:32.946009Z

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)

2025-05-29T23:33:00.484079Z

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.

🙏 1
2025-05-29T20:28:13.798129Z

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)

2025-05-29T22:27:35.034309Z

• 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

2025-05-29T23:03:01.970559Z

@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 🙌

😁 1
2025-05-29T23:07:47.123659Z

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

👀 1
2025-05-29T23:08:20.117919Z

That will help simplify walk-properties

🙏 1
2025-05-29T23:08:56.211329Z

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}))

2025-05-29T23:10:41.748449Z

Oh maybe it doesn't work with :map entry properties. e.g., [:map [:a {:prop :here} :foo]]. Try it out.

👍 1
2025-05-29T23:13:31.322489Z

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

👍 1