Expound's error messages are more verbose than the clojure.spec
messages, which can help you quickly determine why a value fails a spec. Here are some examples.
If the invalid value is nested, Expound will help locate the problem
(s/def :db/id clojure.core/pos-int?)
(s/def :db/ids (s/coll-of :db/id))
(s/def :app/request (s/keys :req-un [:db/ids]))
{:ids [123 "456" 789]}
"456" - failed: pos-int? in: [:ids 1] at: [:ids] spec: :db/id
-- Spec failed --------------------
{:ids [... "456" ...]}
^^^^^
should satisfy
pos-int?
-------------------------
Detected 1 error
If a key is missing from a map, Expound will display the associated spec
(s/def :address.west-coast/city clojure.core/string?)
(s/def :address.west-coast/state #{"CA" "WA" "OR"})
(s/def :app/address (s/keys :req-un [:address.west-coast/city :address.west-coast/state]))
{}
{} - failed: (contains? % :city) spec: :app/address
{} - failed: (contains? % :state) spec: :app/address
-- Spec failed --------------------
{}
should contain keys: :city, :state
| key | spec |
|========+===================|
| :city | string? |
|--------+-------------------|
| :state | #{"CA" "WA" "OR"} |
-------------------------
Detected 1 error
If a value doesn't match a set-based spec, Expound will list the possible values
(s/def :address.west-coast/city clojure.core/string?)
(s/def :address.west-coast/state #{"CA" "WA" "OR"})
(s/def :app/address (s/keys :req-un [:address.west-coast/city :address.west-coast/state]))
{:city "Seattle", :state "ID"}
"ID" - failed: #{"CA" "WA" "OR"} in: [:state] at: [:state] spec: :address.west-coast/state
-- Spec failed --------------------
{:city ..., :state "ID"}
^^^^
should be one of: "CA", "OR", "WA"
-------------------------
Detected 1 error
Expound will group alternatives
(s/def :address.west-coast/zip (s/or :str clojure.core/string? :num clojure.core/pos-int?))
:98109
:98109 - failed: string? at: [:str] spec: :address.west-coast/zip
:98109 - failed: pos-int? at: [:num] spec: :address.west-coast/zip
-- Spec failed --------------------
:98109
should satisfy
string?
or
pos-int?
-------------------------
Detected 1 error
If you provide a predicate description, Expound will display them
(def email-regex #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$")
(defn valid-email? [s] (re-matches email-regex s))
(s/def :app.user/email (s/and string? valid-email?))
(expound/defmsg :app.user/email "should be a valid email address")
"@example.com"
"@example.com" - failed: valid-email? spec: :app.user/email
-- Spec failed --------------------
"@example.com"
should be a valid email address
-------------------------
Detected 1 error
If you are missing elements, Expound will describe what must come next
(s/def :app/ingredient (s/cat :quantity clojure.core/number? :unit clojure.core/keyword?))
[100]
() - failed: Insufficient input at: [:unit] spec: :app/ingredient
-- Syntax error -------------------
[100]
should have additional elements. The next element ":unit" should satisfy
keyword?
-------------------------
Detected 1 error
If you have extra elements, Expound will point out which elements should be removed
(s/def :app/ingredient (s/cat :quantity clojure.core/number? :unit clojure.core/keyword?))
[100 :teaspoon :sugar]
(:sugar) - failed: Extra input in: [2] spec: :app/ingredient
-- Syntax error -------------------
[... ... :sugar]
^^^^^^
has extra input
-------------------------
Detected 1 error