Fork me on GitHub
#clojure-spec
<
2018-01-08
>
aengelberg01:01:00

I'm trying to write a util that prints out a more friendly error message based on the data returned from s/explain-data. I'm noticing that :in contains misleading data for certain cases:

user=> (s/explain-data (s/map-of integer? integer?) {345 "a"})
{:clojure.spec.alpha/problems ({:in [345 1],
                                :path [1],
                                :pred clojure.core/integer?,
                                :val "a",
                                :via []}),
 :clojure.spec.alpha/spec #<clojure.spec.alpha$every_impl$reify__2182@40147821>,
 :clojure.spec.alpha/value {345 "a"}}
user=> (s/explain-data (s/coll-of integer? :kind set?) #{1 2 "a"})
{:clojure.spec.alpha/problems ({:in [1],
                                :path [],
                                :pred integer?,
                                :val "a",
                                :via []}),
 :clojure.spec.alpha/spec #<clojure.spec.alpha$every_impl$reify__2182@71b6c727>,
 :clojure.spec.alpha/value #{1 2 "a"}}
So for a map-of, it is giving me the key as well as 1 (to indicate the val of the map pair), and for a set, it is giving me an index into some arbitrary ordered version of that set. Neither of these would work if I tried to get-in on those paths. If these values were somehow prefaced with an indicator of what special case triggered them, I could work with that, but the fact that the data may or may not be misleading with no additional context makes the overall data unhelpful for writing a universal tool. Is that a bug? Or is there another way of introspecting the failed specs that I'm missing?

Alex Miller (Clojure team)01:01:30

on the first case, this is a side effect of treating maps as a sequence of tuples. there is at least one ticket about this particular one with a pending patch.

Alex Miller (Clojure team)01:01:48

for the set case, I don’t think anything is logged but a ticket for that would be fine

aengelberg09:01:53

Thanks, I'll try to find time to write up a ticket. I'll definitely check out the other ticket you mentioned for map-of, since I'm curious what the proposed solution was, so I know what the expected behavior should be for sets.

bbrinck16:01:12

I ended up writing a lot of code to disambiguate these cases in Expound. https://github.com/bhb/expound/blob/master/src/expound/paths.cljc . The ticket for map-of is https://dev.clojure.org/jira/browse/CLJ-2192 .

bbrinck16:01:16

IIRC, https://github.com/athos/Pinpointer takes a different approach to the problem: it analyzed the specs directly to help understand the explain-data (I could be mis-stating something here, @U0508956F please correct me if I’m wrong 🙂 )

aengelberg19:01:50

@U08EKSQMS thanks! do you have any specific thoughts on the set issue as well? are you also handling that specially from expound?

bbrinck19:01:21

I haven’t traced the execution too closely, but I suspect that the set case is handled by this code https://github.com/bhb/expound/blob/master/src/expound/paths.cljc#L134-L135

bbrinck19:01:27

This could have bugs though. It took awhile to figure out an approach that would work for most :in paths that I could find.

bbrinck20:01:05

Re: sets, I would vote for any JIRA ticket that makes :in paths non-ambiguous, because I think this is one of the biggest hurdles with building pretty-printers for spec. If you make an issue for the set case, send me the link 😄

cfleming21:01:01

Thanks @U08EKSQMS, I’ve been meaning to take a closer look at Expound actually

bbrinck22:01:37

Cool! Feedback is welcome and appreciated

cfleming10:01:21

@aengelberg I’d like to see that too if you find it.

misha12:01:23

is it overkill?

(s/nonconforming (s/or :i pos-int? :z #{0}))

misha12:01:10

- zero? throws on strings, keywords, etc. - just (complement neg-int?) accepts everything but neg ints: strings, maps, etc.

misha12:01:13

is this in any "under-the-hood" way lighter?

(s/and int? (complement neg-int?))

misha14:01:48

missed it opieop

dadair20:01:07

Given the following data: {:a {:name "a"} :b {:name "b"} :c {:name "not-c"}}, how could I construct a spec to say "the key to a value-map must be the keywordized name within the value-map"? This spec would complain that (keyword "not-c") does not match :c

Alex Miller (Clojure team)21:01:49

You can use any arbitrary predicate like #(= (keys %) (->> % vals (map :name) (map keyword))) to check validity

Alex Miller (Clojure team)21:01:11

but it’s not going to give you that specific error

dadair21:01:05

great thank you