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-name
s? 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 []}
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 :pred
https://github.com/bhb/expound/blob/1c0d78570be3865eab8e69c1b568c4e7acee5bd8/src/expound/alpha.cljc#L317.. but I don’t understand how that :expound.spec/contains-key-pred
works..
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.
@kiraemclean 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
Then it uses spec to parse the s-expression to pull out the keyword of the spec
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}]}
Does that help?
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?
@kiraemclean 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.
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
.
(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
)
@kiraemclean This is apparently how Phrase works as well https://cljdoc.org/d/phrase/phrase/0.3-alpha4/doc/examples#required-keys
This is really helpful, then. Thanks for all the info!