metosin / spec-tools

Clojure(Script) tools for clojure.spec
Eclipse Public License 2.0
592 stars 94 forks source link

strip-extra-keys-transformer not working with s/or if ors predicates are non- map validating specs #255

Open tylernisonoff opened 3 years ago

tylernisonoff commented 3 years ago

This is similar to Issue 178 (Fixed by PR 219, but for a specific case.

I'm not sure if this is intended, but it was surprising to me.

Consider the following case where we want to allow something like either {:a 1} or {:b "foo"}

  (s/def ::a int?)
  (s/def ::b string?)
  (s/def ::c (s/or
               :a (s/keys :req-un [::a])
               :b (s/keys :req-un [::b])))
  ((s/valid? ::c {:a 1})
  ; => true
  (st/coerce ::c {:a 1} st/strip-extra-keys-transformer)
  ; => {}

However, If i change ::c to:

(s/def ::c (s/or
               :a (st/spec (s/keys :req-un [::a]))
               :b (st/spec (s/keys :req-un [::b]))))

NOTE: the st/spec s

Now we get:

 (s/valid? ::c {:a 1})
  ; => true
  (st/coerce ::c {:a 1} st/strip-extra-keys-transformer)
  ; => {:a 1}

My expectation is either of these two should be able to be coerced, is that correct?

opqdonut commented 5 days ago

Thanks for finding this, great repro & workaround of the or + strip-extra-keys-transformer behaviour!

opqdonut commented 5 days ago

Some proposed solutions here: https://github.com/metosin/spec-tools/issues/212#issuecomment-919869195

opqdonut commented 5 days ago

e31461db229312c9cb05cc4de07adcde78380f39 adds a failing test case to the existing issue-179 test, namely:

      (is (= (st/coerce ::vehicle bike st/strip-extra-keys-transformer)
             {:wheels 2}))

It looks like whether you use named or inline specs affects this. Note how both forks of this or work:

(s/def ::s (s/or :x (s/keys :req-un [::keyword ::int])
                 :y (s/keys :req-un [::keyword ::date])))

but here only the first one (:car) works:

(s/def ::car (s/keys :req-un [::doors]))
(s/def ::bike (s/keys :req-un [::wheels]))
(s/def ::tires (s/coll-of (s/and int?) :into #{}))
(s/def ::vehicle (s/or :car ::car
                       :bike ::bike))