Search code examples
clojurespecifications

Clojure. Create a Spec with dynamic name keywords in a map


I have this map:

{:60 {:id 60, :quote "Lorem ipsum doloret sit anem", :author "foo name", :total 61}
 :72 {:id 72, :quote "Excepteur sint occaecat cupidatat non", :author "Nietzsche", :total 61}
 :56 {:id 56, :quote "Ut enim ad minim veniam, quis nostrud ", :author "O. Wilde", :total 61}
 :58 {:id 58, :quote "Duis aute irure dolor in reprehenderit", :author "your mama", :total 61}}

I'm trying to create its spec, I think I have the map "inner part":

(s/def ::id     (s/and int? pos?))
(s/def ::quote  (s/and string? #(>= (count %) 8)))
(s/def ::author (s/and string? #(>= (count %) 6)))
(s/def ::total  (s/and int? pos?))

(s/def ::quotes (s/and
             (s/map-of ::id ::quote ::author ::total)
             #(instance? PersistentTreeMap %)             ;; is a sorted-map (not just a map)
            ))

but the map keywords are created dynamically, so they don't have a name, how can I define the spec for those kinds of keywords and add it into the (s/map-of function ?


Solution

  • map-of takes a key predicate, a value predicate, and optional arguments — not a list of keywords. To define the list of keywords in a map you can use the keys function with the :req-un argument since you’re using unqualified keys.

    Since you haven’t specified how you’d like to restrict the map keywords, I’ll assume that they can be any keywords. If that’s the case then you can change the following,

    (s/def ::key keyword?)
    
    (s/def ::quotes (s/and
                      (s/map-of ::key (s/keys :req-un [::id
                                                       ::quote
                                                       ::author
                                                       ::total]))
                      #(instance? clojure.lang.PersistentTreeMap %)))
    
    

    Using your example map above, we can see that this spec definition corresponds.

    user=> (s/valid? ::quotes 
       #=>           (into (sorted-map)
       #=>                 {:60 {:id     60
       #=>                       :quote  "Lorem ipsum doloret sit anem"
       #=>                       :author "foo name"
       #=>                       :total  61}
       #=>                  :72 {:id     72
       #=>                       :quote  "Excepteur sint occaecat cupidatat non"
       #=>                       :author "Nietzsche"
       #=>                       :total  61}
       #=>                  :56 {:id     56
       #=>                       :quote  "Ut enim ad minim veniam, quis nostrud "
       #=>                       :author "O. Wilde"
       #=>                       :total  61}
       #=>                  :58 {:id     58
       #=>                       :quote  "Duis aute irure dolor in reprehenderit"
       #=>                       :author "your mama"
       #=>                       :total  61}}))
    true