Nice, thanks. I had not thought to use map-of for this. And, s/merge 
certainly helps too.
The only remaining issue for me is that this requires supplying the list of 
keys twice.
AI think this case is general enough that it is worth extending the s/keys 
macro to support: (s/keys :req [::a ::b] :allow-other-keys false)

Or, if is is objectionable to have a keyword default to true when not 
supplied, perhaps: (s/keys :req [::a ::b] :strict-keys true)

On Tuesday, September 20, 2016 at 9:47:43 PM UTC+3, Alex Miller wrote:
>
> For stuff like this s/merge is probably preferable to s/and (when 
> combining map specs) - the difference being that merge does not flow 
> conformed results, will combine all failures, and that gen can work better 
> in some cases.
>
> (s/def ::a int?)
> (s/def ::b string?)  ;; changed for this example
> (s/explain ::my-map {::a 1 ::b 2 ::BAD 3})
> In: [:user/b] val: 2 fails spec: :user/b at: [:user/b] predicate: string?
>
> ;; vs:
>
> (s/def ::my-map2 (s/merge (s/keys :req [::a ::b])  (s/map-of #{::a ::b} 
> any?)))
> (s/explain ::my-map2 {::a 1 ::b 2 ::BAD 3})
> In: [:user/b] val: 2 fails spec: :user/b at: [:user/b] predicate: string?
> In: [:user/BAD 0] val: :user/BAD fails spec: :user/my-map2 at: [0] 
> predicate: #{:user/a :user/b}
>
> ^^ Note you get *both* failures here - both bad attribute value AND the 
> invalid key vs the prior one where you only get the first failure.
>
>
> On Tuesday, September 20, 2016 at 11:38:47 AM UTC-5, Beau Fabry wrote:
>>
>> boot.user=> (s/def ::my-map (s/and (s/keys :req [::a ::b])  (s/map-of 
>> #{::a ::b} any?)))
>> boot.user=> (s/explain ::my-map {::a 1 ::b 2 ::BAD 3})
>> In: [:boot.user/BAD 0] val: :boot.user/BAD fails spec: :boot.user/my-map 
>> at: [0] predicate: #{:boot.user/a :boot.user/b}
>>
>> Seems better
>>
>> On Tuesday, September 20, 2016 at 5:38:10 AM UTC-7, David Goldfarb wrote:
>>>
>>> In clojure.spec, how can I declare a map that accepts only certain keys? 
>>>  
>>>
>>> *{::a 1 ::b 2 ::BAD 3}* does conform to *(s/keys :req :req [::a ::b])*, 
>>> but I want a spec that will be bothered by ::BAD or any other undeclared 
>>> key.
>>>
>>> My use case: I am introducing spec to some legacy code, and I want to be 
>>> warned if I have failed to specify some elements that may appear in my map.
>>>
>>>
>>> Question 2:
>>>
>>>  So, assuming that this is not possible currently, I brute-forced it 
>>> with:
>>>
>>> *(defn- key-checker [valid-keys]*
>>> *  (fn [map-to-check]*
>>> *    (empty? (clojure.set/difference (into #{} (keys map-to-check)) 
>>> valid-keys))))*
>>>
>>> *(s/def ::my-map (s/and (s/keys :req [::a ::b])  (key-checker #{::a 
>>> ::b})))*
>>>
>>>
>>> Ignoring the ugly, and easily fixable, smell of the duplicated set of 
>>> keys, this has a bigger problem:
>>>
>>> If the predicate fails, the error that assert gives me is *"{... big 
>>> ugly map ...} fails predicate: (key-checker #{::a ::b})"* with no easy 
>>> way for the viewer to see which key failed. Can I somehow hook into the 
>>> explain mechanism to give a more useful message?
>>>
>>

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to