I'm trying to write specs for a map and I would prefer to not write explicit (s/def ...) for each keyword of the map.
Therefore, looking at the docs it looks like I can use map-of . This works if the map is homogeneous, but my map has groups of keywords which are homogenous amongst them, while different from other groups. I tried merge but that does not work. Here's an example and the error message from spec:
(def filter-screens-set
#{:pca/m1cs
:pca/m1cr})
(def nav-screens-set
#{:pca/main-screen})
(s/def ::filter-screens
(s/map-of filter-screens-set ::filter-screen))
(s/def ::nav-screens
(s/map-of nav-screens-set ::nav-screen))
(s/def ::all-screens (s/merge ::nav-screens ::filter-screens))
(s/explain
::all-screens
{:pca/main-screen {:title "Prostate cancer"
:children [{:title "Castration sensitive PCA"
:group "Metastatic disease"
:screen-id :filters
:target :pca/m1cs}]}
:pca/m1cs {:title "M1 CS prostate cancer"
:tags #{:pca :m1 :castration-sens}
:filters [{:group-label "CHAARTED level"
:tags #{:CHAARTED-low :CHAARTED-high}}]}})
; eval (root-form): (s/explain ::all-screens {:pca/main-screen {...
; (out) :pca/m1cs - failed: nav-screens-set in: [:pca/m1cs 0] at: [0] spec: :test/nav-screens
; (out) {:title "M1 CS prostate cancer", :tags #{:pca :castration-sens :m1}, :filters [{:group-label "CHAARTED level", :tags #{:CHAARTED-high :CHAARTED-low}}]} - failed: (contains? % :children) in: [:pca/m1cs 1] at: [1] spec: :test/nav-screen
; (out) :pca/main-screen - failed: filter-screens-set in: [:pca/main-screen 0] at: [0] spec: :test/filter-screens
; (out) {:title "Prostate cancer", :children [{:title "Castration sensitive PCA", :group "Metastatic disease", :screen-id :filters, :target :pca/m1cs}]} - failed: (contains? % :tags) in: [:pca/main-screen 1] at: [1] spec: :test/filter-screen
; (out) {:title "Prostate cancer", :children [{:title "Castration sensitive PCA", :group "Metastatic disease", :screen-id :filters, :target :pca/m1cs}]} - failed: (contains? % :filters) in: [:pca/main-screen 1] at: [1] spec: :test/filter-screen
My understanding is that merge is working like an and which means it always fails one of the specs? Is there a way to do this, or must I be explicit with keys?I don't think what you're trying will work - you are defining a spec where keys have to pass both specs and they are not compatible
Right. Is the only option here to split the map and check the spec conforms on each split? I dont see a way to define a spec that conforms a map like I describe without defining each key with keys or modifying the structure eg nesting
you could have each map spec spec the keys from the other set as any? - those specs could then be merged, but I'm not sure that's any better
it is also possible to spec a map as a collection of kvs, but this is also fairly tricky. I used a similar technique here https://www.cognitect.com/blog/2017/1/3/spec-destructuring - see the "collection of tuple forms" part with s/every of s/or
thanks Alex! I've concluded I'm going against the system here so instead I added some nested keys to the map. This way I can still use map-of at the cost of a little nesting.