Having two following specs:
(s/def ::x keyword?)
(s/def ::y keyword?)
(s/def ::z keyword?)
(s/def ::a
(s/keys :req-un [::x
::y]
:opt-un [::z]))
(s/def ::b
(s/map-of string? string?))
how do I combine ::a
and ::b
into ::m
so the following data is valid:
(s/valid? ::m
{:x :foo
:y :bar
:z :any})
(s/valid? ::m
{:x :foo
:y :bar})
(s/valid? ::m
{:x :foo
:y :bar
:z :baz})
(s/valid? ::m
{:x :foo
:y :bar
:z "baz"})
(s/valid? ::m
{:x :foo
:y :bar
:t "tic"})
additionally, how do I combine ::a
and ::b
into ::m
so the following data is invalid:
(s/valid? ::m
{"r" "foo"
"t" "bar"})
(s/valid? ::m
{:x :foo
"r" "bar"})
(s/valid? ::m
{:x :foo
:y :bar
:r :any})
Neither of :
(s/def ::m (s/merge ::a ::b))
(s/def ::m (s/or :a ::a :b ::b))
works (as expected), but is there a way to match map entries in priority of the spec order?
The way it should work is the following:
::a
spec and the other conforming the ::b
spec.You can do this by treating the map not as a map but as a collection of map entries, and then validate the map entries. Handling the "required" keys part has to be done by s/and'ing an additional predicate.
(s/def ::x keyword?)
(s/def ::y keyword?)
(s/def ::z keyword?)
(s/def ::entry (s/or :x (s/tuple #{::x} ::x)
:y (s/tuple #{::y} ::y)
:z (s/tuple #{::z} ::z)
:str (s/tuple string? string?)))
(defn req-keys? [m] (and (contains? m :x) (contains? m :y)))
(s/def ::m (s/and map? (s/coll-of ::entry :into {}) req-keys?))