metosin / malli

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

Repeated calls to `malli.util/assoc-in` referencing non-existing maps fail #874

Closed vise890 closed 1 year ago

vise890 commented 1 year ago

Expected behavior

I'd expect malli.util/assoc-in to mirror clojure.core/assoc-in:

(-> {}
    (assoc-in [:foo :bar] :int)
    (assoc-in [:foo :baz] :int))
;; => {:foo {:bar :int, :baz :int}}

Actual behavior

(-> [:map] ; NOTE: intermediate `:foo` map does not exist
    (malli.util/assoc-in [:foo :bar] :int)
    (malli.util/assoc-in [:foo :baz] :int)
    (malli.util/closed-schema))

;; 1. Unhandled java.lang.IllegalArgumentException
;;    Duplicate key: :foo

;;    PersistentArrayMap.java:   73  clojure.lang.PersistentArrayMap/createWithCheck
;;                  core.cljc:  458  malli.core$_eager_entry_parser$_map__10301/invoke
;;                  core.cljc:  474  malli.core$_eager_entry_parser/invokeStatic
;;                  core.cljc:  453  malli.core$_eager_entry_parser/invoke
;;                  core.cljc:  489  malli.core$_create_entry_parser/invokeStatic
;;                  core.cljc:  486  malli.core$_create_entry_parser/invoke
;;                  core.cljc:  957  malli.core$_map_schema$reify__10614/_into_schema
;;                  core.cljc:  338  malli.core$_set_children/invokeStatic
;;                  core.cljc:  336  malli.core$_set_children/invoke
;;                  core.cljc: 2261  malli.core$schema_walker$fn__11256/invoke
;;                  core.cljc: 2048  malli.core$walk$reify__11190/_outer
;;                  core.cljc:  322  malli.core$_walk_entries/invokeStatic
;;                  core.cljc:  320  malli.core$_walk_entries/invoke
;;                  core.cljc: 1033  malli.core$_map_schema$reify$reify__10639/_walk
;;                  core.cljc: 2043  malli.core$walk/invokeStatic
;;                  core.cljc: 2036  malli.core$walk/invoke
;;                  util.cljc:  125  malli.util$closed_schema/invokeStatic
;;                  util.cljc:  114  malli.util$closed_schema/invoke
;;                  util.cljc:  123  malli.util$closed_schema/invokeStatic
;;                  util.cljc:  114  malli.util$closed_schema/invoke

Temporary fix

A call to malli.core/form between the repeated assoc-in calls seems to fix the issue.

(-> [:map]
    (malli.util/assoc-in [:foo :bar] :int)
    (malli/form) ; this seems to fix it
    (malli.util/assoc-in [:foo :baz] :int)
    (malli.util/closed-schema))
;; => [:map {:closed true} [:foo [:map {:closed true} [:bar :int] [:baz :int]]]]

Some exploration

I think the problem lies in the assoc call in assoc-in, which ultimately calls -set-entries for a -map-schema

(-> [:map]
    (malli.util/assoc :foo [:map [:bar :int]])
    (malli.util/assoc :foo [:map [:baz :int]])
    (malli.util/closed-schema))
;; 1. Unhandled java.lang.IllegalArgumentException
;;    Duplicate key: :foo

(-> [:map]
    (malli/schema)
    (malli/-set :foo [:map [:bar :int]])
    (malli/-set :foo [:map [:baz :int]])
    (malli.util/closed-schema))
;; 1. Unhandled java.lang.IllegalArgumentException
;;    Duplicate key: :foo

(-> [:map]
    (malli.util/assoc :foo [:map [:bar :int]])
    (malli.util/assoc :foo [:map [:baz :int]]))
;; => [:map [:foo [:map [:bar :int]]] [:foo [:map [:baz :int]]]]
;;           ^^^^~~~~~~~~~~~~~~~~~~~~~~^^^^ NOTE duplicate key

(-> [:map]
    (malli.util/assoc :foo [:map [:bar :int]])
    (malli/form)
    (malli.util/assoc :foo [:map [:baz :int]]))
;; => [:map [:foo [:map [:baz :int]]]]
;;           ^^^^~~~~~~~~~~~~~~~~~~~~~~~~~~ NOTE no duplicate
ikitommi commented 1 year ago

Oh, this is embarrassing.

ikitommi commented 1 year ago

fixed in [metosin/malli "0.10.3"]

vise890 commented 1 year ago

Wow, that was fast! Thank you so much for the fix and the amazing library!