Skip to content

Commit

Permalink
Merge pull request #946 from metosin/fix-910
Browse files Browse the repository at this point in the history
Fallback to use first branch result with :or and :orn when decoding.
  • Loading branch information
ikitommi authored Sep 5, 2023
2 parents c750cfd + 0ab9170 commit aa49c3e
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 62 deletions.
58 changes: 23 additions & 35 deletions src/malli/core.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,27 @@
x))))
:cljs (fn [x] (into (when x empty) (map t) x))))

(defn -or-transformer [this transformer child-schemas method options]
(let [this-transformer (-value-transformer transformer this method options)]
(if (seq child-schemas)
(let [transformers (-vmap #(or (-transformer % transformer method options) identity) child-schemas)
validators (-vmap -validator child-schemas)]
(-intercepting this-transformer
(if (= :decode method)
(fn [x]
(reduce-kv
(fn [acc i transformer]
(let [x* (transformer x)]
(if ((nth validators i) x*)
(reduced x*)
(if (-equals acc ::nil) x* acc))))
::nil transformers))
(fn [x]
(reduce-kv
(fn [x i validator] (if (validator x) (reduced ((nth transformers i) x)) x))
x validators)))))
(-intercepting this-transformer))))

;;
;; ast
;;
Expand Down Expand Up @@ -757,23 +778,7 @@
(-parser [_] (->parser -parser))
(-unparser [_] (->parser -unparser))
(-transformer [this transformer method options]
(let [this-transformer (-value-transformer transformer this method options)]
(if (seq children)
(let [transformers (-vmap #(or (-transformer % transformer method options) identity) children)
validators (-vmap -validator children)]
(-intercepting this-transformer
(if (= :decode method)
(fn [x]
(reduce-kv
(fn [x i transformer]
(let [x* (transformer x)]
(if ((nth validators i) x*) (reduced x*) x)))
x transformers))
(fn [x]
(reduce-kv
(fn [x i validator] (if (validator x) (reduced ((nth transformers i) x)) x))
x validators)))))
(-intercepting this-transformer))))
(-or-transformer this transformer children method options))
(-walk [this walker path options] (-walk-indexed this walker path options))
(-properties [_] properties)
(-options [_] options)
Expand Down Expand Up @@ -831,24 +836,7 @@
::invalid)
::invalid))))
(-transformer [this transformer method options]
(let [this-transformer (-value-transformer transformer this method options)]
(if (seq (-children this))
(let [transformers (-vmap (fn [[_ _ c]] (or (-transformer c transformer method options) identity))
(-children this))
validators (-vmap (fn [[_ _ c]] (-validator c)) (-children this))]
(-intercepting this-transformer
(if (= :decode method)
(fn [x]
(reduce-kv
(fn [x i transformer]
(let [x* (transformer x)]
(if ((nth validators i) x*) (reduced x*) x)))
x transformers))
(fn [x]
(reduce-kv
(fn [x i validator] (if (validator x) (reduced ((nth transformers i) x)) x))
x validators)))))
(-intercepting this-transformer))))
(-or-transformer this transformer (-vmap #(nth % 2) (-children this)) method options))
(-walk [this walker path options] (-walk-entries this walker path options))
(-properties [_] properties)
(-options [_] options)
Expand Down
69 changes: 42 additions & 27 deletions test/malli/core_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -292,25 +292,42 @@
(is (= [:and 'int? [:orn [:pos 'pos-int?] [:neg 'neg-int?]]] (m/form schema*))))

(testing "transforming :or"
(testing "first valid transformed branch is used"
(doseq [schema [[:or
[:map [:x keyword?]]
int?
[:map [:y keyword?]]
keyword?]
[:orn
[:äxy [:map [:x keyword?]]]
[:n int?]
[:yxy [:map [:y keyword?]]]
[:kw keyword?]]]]
(are [input result]
(= (m/decode schema input mt/string-transformer) result)

{:x "true", :y "true"} {:x :true, :y "true"}
{:x false, :y "true"} {:x false, :y :true}
{:x false, :y false} {:x false, :y false}
1 1
"kikka" :kikka)))
(let [math (mt/transformer {:name :math})
math-string [:string {:decode/math (partial str "math_")}]
math-kw-string [:and math-string [:any {:decode/math keyword}]]
bono-string [:string {:decode/math (partial str "such_")}]]

(testing "first successful branch is selected"
(is (= "math_1"
(m/decode math-string 1 math)
(m/decode [:and math-string] 1 math)
(m/decode [:or math-string] 1 math)
(m/decode [:or
math-kw-string
math-string
bono-string] 1 math)
(m/decode [:orn ["string" math-string]] 1 math)
(m/decode [:orn
["kw-math" math-kw-string]
["math" math-string]
["bono" bono-string]] 1 math))))

(testing "first branch value is selected as fallback, even if invalid"
(is (= :math_1
(m/decode [:or
math-kw-string
:string] 1 math)
(m/decode [:orn
["kw-math" math-kw-string]
["string" :string]] 1 math))))

(testing "first branch nil can be selected as a fallback"
(is (= nil (m/decode
[:or
[:keyword {:decode/math (constantly nil)}]
:keyword]
"kikka"
(mt/transformer {:name :math}))))))

(testing "top-level transformations are retained"
(doseq [schema [[:or {:decode/string {:enter (fn [m] (update m :enter #(or % true)))
Expand All @@ -330,15 +347,13 @@
[:y keyword?]
[:enter boolean?]]]]]]
(are [input result]
(= (m/decode (mu/closed-schema schema) input mt/string-transformer) result)

{:x "true"} {:x :true, :enter true, :leave true}
{:x "true", :enter "invalid"} {:x "true", :enter "invalid", :leave true}

{:y "true"} {:y :true, :enter true, :leave true}
{:y "true", :leave "invalid"} {:y "true", :enter true, :leave "invalid"}
(= (m/decode (mu/closed-schema schema) input (mt/string-transformer)) result)

{:x "true", :y "true"} {:x "true", :y "true", :enter true, :leave true}))))
{:x "true"} {:x :true, :enter true, :leave true} ;; first
{:x "true", :enter "invalid"} {:x :true, :enter "invalid", :leave true} ;; first (fallback)
{:y "true"} {:y :true, :enter true, :leave true} ;; second
{:y "true", :leave "invalid"} {:y "true", :enter true, :leave "invalid"} ;; no match
{:x "true", :y "true"} {:x :true, :y "true", :enter true, :leave true})))) ;; first (fallback))

(testing "explain with branches"
(let [schema [:and pos-int? neg-int?]]
Expand Down

0 comments on commit aa49c3e

Please sign in to comment.