metosin / malli

High-performance data-driven data specification library for Clojure/Script.
Eclipse Public License 2.0
1.44k stars 205 forks source link

[Proposal] map-of index should be part of `:in` during a walk #906

Open green-coder opened 1 year ago

green-coder commented 1 year ago
(defprotocol LensSchema
  (-keep [this] "returns truthy if schema contributes to value path")

I think that the index in :map-of should be part of :in during a walk as it contributes to the value path, otherwise we don't differentiate between the key and the value.

green-coder commented 1 year ago

Other proposal: to replace the 0 and 1 indices by something like ::m/key and ::m/val for this schema, similarly to how ::m/in is used for collections.

ikitommi commented 1 month ago

Thanks for reporting. I think this is even harder than that. There should be conversion from explain result of :in to :path and vice versa and that doesn't hold for :map-of.

(defn e! [schema value]
  (let [{:keys [schema] :as explain} (m/explain schema value)]
    (for [{:keys [path in] :as error} (-> explain :errors)]
      {:schema schema
       :error error
       :get-in (mu/get-in schema path)
       :path->in (mu/path->in schema path)
       :in->paths (mu/in->paths schema in)})))

(e! [:map ["kikka" :keyword]] {"kikka" "x"})
;({:schema [:map ["kikka" :keyword]],
;  :error {:path ["kikka"], :in ["kikka"], :schema :keyword, :value "x"},
;  :get-in :keyword,
;  :path->in ["kikka"],
;  :in->paths [["kikka"]]})

(e! [:map-of :keyword :keyword] {:kikka :a, :kukka "b"})
;({:schema [:map-of :keyword :keyword],
;  :error {:path [1], :in [:kukka], :schema :keyword, :value "b"},
;  :get-in :keyword,
;  :path->in [1], <-- this is wrong!
;  :in->paths []}) <-- this too

I think there are many ways to fix this. First that comes to mind is to make m/-explain contain both schema index (e.g. :path) + the value itself, wrapped in a vector. This should be possible as the :path is fully managed via the Schema instance, so it can write anything + read it back via the LensSchema protocol.

In this the above case, we would not conj just 1 to the path when explaining, but a vector of index + value instead, e.g. [1 :kikka]. This would be unwrapped in the LensSchema.

The example would return:

(e! [:map-of :keyword :keyword] {:kikka :a, :kukka "b"})
;({:schema [:map-of :keyword :keyword],
;  :error {:path [[1 :kukka]], :in [:kukka], :schema :keyword, :value "b"},
;  :get-in :keyword,
;  :path->in [:kukka]
;  :in->paths [[1 :kukka]})

would this work for you?

PR welcome.