Clojure Spec
Clojure Spec is a library for describing the structure of data and functions. It enables:
- Data validation
 - Destructuring/parsing
 - Error reporting
 - Instrumentation
 - Generative testing
 
Basic Specs
Predicate Specs
(require '[clojure.spec.alpha :as s])
(s/valid? even? 10) ; => true
(s/valid? string? "hello") ; => true
(s/def ::even-int (s/and int? even?))
(s/valid? ::even-int 4) ; => true
(s/valid? ::even-int 5) ; => false
                
                Registry
Specs are stored in a global registry by keyword:
(s/def ::name string?)
(s/def ::age pos-int?)
(s/def ::email (s/and string? #(re-matches #".+@.+\..+" %)))
                
                Composite Specs
Collections
(s/def ::numbers (s/coll-of number?))
(s/valid? ::numbers [1 2 3]) ; => true
(s/valid? ::numbers [1 "a"]) ; => false
(s/def ::unique-nums (s/coll-of number? :distinct true))
                
                Maps
(s/def ::person
  (s/keys :req [::name ::age]
          :opt [::email]))
(s/valid? ::person
  {::name "Alice" ::age 30}) ; => true
                
                Tuples
(s/def ::point
  (s/tuple double? double? double?))
(s/valid? ::point [1.0 2.0 3.0]) ; => true
                
                Function Specs
fdef
(defn add [x y] (+ x y))
(s/fdef add
  :args (s/cat :x number? :y number?)
  :ret number?
  :fn #(= (:ret %) (+ (-> % :args :x) (-> % :args :y))))
                
                Instrumentation
(require '[clojure.spec.test.alpha :as st])
(st/instrument `add) ; Now add is checked
(add 1 2) ; => 3
(add "1" 2) ; Throws Exception
                
                Generative Testing
Generating Data
(s/exercise ::person)
; => ([{:user/name "", :user/age 1} {:user/name "", :user/age 1}]
;     [{:user/name "E", :user/age 1} {:user/name "E", :user/age 1}]
;     ...)
                
                Testing Functions
(st/check `add)
; => {:sym clojure.core/add,
;     :spec #object[...],
;     :result true}
                
                Advanced Specs
Multi-spec
(defmulti event-type :event/type)
(defmethod event-type :login [_]
  (s/keys :req [:event/type :event/user :event/time]))
(defmethod event-type :purchase [_]
  (s/keys :req [:event/type :event/item :event/price :event/time]))
(s/def ::event (s/multi-spec event-type :event/type))
                
                Regex Specs
(s/def ::config
  (s/*
    (s/alt :prop (s/tuple keyword? any?)
           :env (s/tuple #{:env} (s/map-of keyword? string?))))
(s/conform ::config
  [:db-url "localhost"
   :env {:dev "DEV" :prod "PROD"}])
; => [[:prop [:db-url "localhost"]] 
;     [:env [:env {:dev "DEV", :prod "PROD"}]]]
                
                Error Reporting
(s/explain ::person
  {:name "Bob" :age -1})
; val: -1 fails spec: :user/age at: [:age] predicate: pos-int?
(s/explain-str ::person
  {:name 100 :age 30})
; => "100 - failed: string? in: [:name] at: [:name]"
                
                Real-world Usage
API Validation
(defn handle-request [request]
  (if (s/valid? ::api-request request)
    (process request)
    (handle-error (s/explain-data ::api-request request))))
                
                Database Schemas
(s/def ::user-id uuid?)
(s/def ::user
  (s/keys :req [::user-id ::name ::email]
          :opt [::phone ::address]))
                
                Best Practices
- Start with specs for your core domain data
 - Spec public functions first
 - Use namespaced keywords
 - Combine simple specs into complex ones
 - Use instrumentation during development
 
Next Steps
Continue learning with:
- Web Development - Building web applications with Clojure