Вот способ сделать это с multi-spec
:
(defmulti country :country)
(defmethod country "Canada" [_]
(s/spec #(re-matches #"^[A-Z0-9]{5}$" (:postal-code %))))
(defmethod country :default [_]
(s/spec #(re-matches #"^[0-9]{5}$" (:postal-code %))))
(s/def ::country string?)
(s/def ::postal-code string?)
(s/def ::address
(s/merge
(s/keys :req-un [::country ::postal-code])
(s/multi-spec country :country)))
(s/explain ::address {:country "USA" :postal-code "A2345"})
;; val: {:country "USA", :postal-code "A2345"} fails spec: :sandbox.so/address at: ["USA"] predicate: (re-matches #"^[0-9]{5}$" (:postal-code %))
(s/explain ::address {:country "Canada" :postal-code "A2345"})
;; Success!
Другой вариант - and
- еще один предикат в вашей keys
спецификации:
(s/def ::address
(s/and
(s/keys :req-un [::country ::postal-code])
#(case (:country %)
"Canada" (re-matches #"^[A-Z0-9]{5}$" (:postal-code %))
(re-matches #"^[0-9]{5}$" (:postal-code %)))))
Вы можете предпочесть подход multi-spec
, потому что он открыт для расширения, т. Е. Вы можете определить больше defmethod
s для country
позже, в отличие от сохранения всей логики в предикате and
.