Welcome! Please see the About page for a little more info on how this works.

0 votes
in Spec by
Consider this example:


$ clj
Clojure 1.9.0
user=> (require '[clojure.spec.alpha :as s])
nil
user=> (s/def ::a int?)
:user/a
user=> (s/def ::b int?)
:user/b
user=> (s/def ::c int?)
:user/c
user=> (s/def ::d (s/and (s/keys :req [::a ::b ::c]) #(< (::a %) (::b %))))
:user/d
user=> (s/explain-data ::d {::a 1 ::b 0})
#:clojure.spec.alpha{:problems ({:path [], :pred (clojure.core/fn [%] (clojure.core/contains? % :user/c)), :val #:user{:a 1, :b 0}, :via [:user/d], :in []}), :spec :user/d, :value #:user{:a 1, :b 0}}
user=> (s/explain-data ::d {::a 1 ::b 0 ::c 2})
#:clojure.spec.alpha{:problems [{:path [], :pred (clojure.core/fn [%] (clojure.core/< (:user/a %) (:user/b %))), :val #:user{:a 1, :b 0, :c 2}, :via [:user/d], :in []}], :spec :user/d, :value #:user{:a 1, :b 0, :c 2}}


I'd like a way to get the first invocation of explain-data to include the missing ::c _and_ the failing < predicate. I understand that spec/and is designed to short circuit, so perhaps this could be solved with a new function that processes n specs independently.

7 Answers

0 votes
by
_Comment made by: alexmiller_

In this particular case, you can use s/merge and you'll get all problems...


user=> (s/def ::e (s/merge (s/keys :req [::a ::b ::c]) #(< (::a %) (::b %))))
:user/e
user=> (s/explain ::e {::a 1 ::b 0})
val: #:user{:a 1, :b 0} fails spec: :user/e predicate: (contains? % :user/c)
val: #:user{:a 1, :b 0} fails spec: :user/e predicate: (< (:user/a %) (:user/b %))


s/merge does have the semantics of "satisfies all", but is of course specific to maps.
0 votes
by

Comment made by: dchelimsky

Got it. The other part of my specific problem, not reflected in this example, is that I've got a conformer in the middle that the last predicate relies on. s/merge won't work in that case because the conformer can't generate. Would you like a separate issue for that?

0 votes
by

Comment made by: dchelimsky

So I've got this close to working with a custom generator. Do you want to leave this issue open?

0 votes
by

Comment made by: alexmiller

Re the conformer - no, it's by design that conformed values don't flow in s/merge (and is in line with the notion of "not flowing" that goes with "satisfies all"). Re leaving it open, that's up to you. We have talked about creating a non-flowing and variant but I don't think there's a ticket for it.

0 votes
by
_Comment made by: dchelimsky_

Actually, I think I just discovered a bug related to this. Following on the examples above:


user=> (s/explain ::e {::a 1 ::b 2})
val: #:user{:a 1, :b 2} fails spec: :user/e predicate: (contains? % :user/c)
val: #:user{:a 1, :b 2} fails spec: :user/e predicate: (< (:user/a %) (:user/b %))
nil


This should pass {{(< (:user/a %) (:user/b %))}}
0 votes
by

Comment made by: dchelimsky

I'll file a separate issue for that.

0 votes
by
Reference: https://clojure.atlassian.net/browse/CLJ-2320 (reported by dchelimsky)
...