Fork me on GitHub
#clojure-spec
<
2020-12-01
>
Kira McLean19:12:42

What’s the best to way to list the keys that a map is missing? E.g. with a problem like this for a specced map, what do you guys do to collect those key-names? I’m looking at using spec to validate data in a web app so ultimately I’d like to translate the missing keys into a message like “<key-name> is required” but I’m not sure what’s the best way to get a hold of the names of the missing keys.

{:path [],
  :pred (clojure.core/fn [%] (clojure.core/contains? % :key-name)),
  :val
  {},
  :via [,,,],
  :in []}

Kira McLean19:12:25

I was looking through expound to try to see how they do it.. it looks like the core of it is https://github.com/bhb/expound/blob/1c0d78570be3865eab8e69c1b568c4e7acee5bd8/src/expound/printer.cljc#L187-L194, where form is a :predhttps://github.com/bhb/expound/blob/1c0d78570be3865eab8e69c1b568c4e7acee5bd8/src/expound/alpha.cljc#L317.. but I don’t understand how that :expound.spec/contains-key-pred works..

Kira McLean19:12:55

I don’t see how it returns the name of the failed key.. when I try something similar it just returns ::s/invalid, which seems like what I would expect.

bbrinck20:12:12

@UPGS9BS0L Expound is just parsing the s-expression of the pred. The trick is that expound will take the pred e.g. something like (clojure.core/fn [%] (clojure.core/contains? % :expound.printer/foo)) And then it will throw away the clojure.core/fn [%] part with the (nth form 2) on line 188

bbrinck20:12:29

Then it uses spec to parse the s-expression to pull out the keyword of the spec

bbrinck20:12:38

Here’s a little script to show each step. It’s just an example to show how Expound figures out each part of the :pred

(let [first-problem (first (::s/problems (s/explain-data ::some-spec {})))]
    {
     :pred (:pred first-problem)
     :part-of-pred (nth (:pred first-problem)  2)
     :match (s/conform :expound.spec/contains-key-pred (nth (:pred first-problem)  2))
     }
    )
  ;; This returns:
  #_ {:pred (clojure.core/fn [%] (clojure.core/contains? % :expound.printer/foo)),
   :part-of-pred (clojure.core/contains? % :expound.printer/foo),
   :match [:simple {:contains clojure.core/contains?, :arg %, :kw :expound.printer/foo}]}

bbrinck20:12:53

Does that help?

Kira McLean20:12:26

ah cool, yeah that does make sense. Thank you! So is that how you’d recommend going about getting a hold of those keys? Is there a simpler way to just get spec to tell me which req keys are missing?

bbrinck20:12:34

@UPGS9BS0L Unfortunately, as of spec1, that’s the way I know of to get the missing keys. Things may change in spec2, but I don’t know of any specific plans for this.

bbrinck20:12:21

It’d be really nice if spec2 included both the missing key and the fully-qualified key in the case of keys included via :req-un.

bbrinck20:12:09

(right now if you use :req-un, sometimes Expound will warn <can't find spec for unqualified spec identifier> because it doesn’t know what spec you meant by the kw :foo)

Kira McLean21:12:12

This is really helpful, then. Thanks for all the info!