Skip to content

Instantly share code, notes, and snippets.

@Charlynux
Created January 9, 2018 12:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Charlynux/35c754f3f213012397da8ae61b97cb1c to your computer and use it in GitHub Desktop.
Save Charlynux/35c754f3f213012397da8ae61b97cb1c to your computer and use it in GitHub Desktop.
Play with clojure-spec to validate server responses
(require '[clojure.spec.alpha :as s])
(s/def ::response-ok
(s/and
#(= (:status %) 200)
#(= (get-in % [:body :message]) "OK")))
(s/def ::response-not-found #(= (:status %) 404))
(s/explain-data ::response-ok { :status 200 :body { :message "OK" }})
(s/explain-data ::response-ok { :status 200 :body {}})
(s/explain-data ::response-ok { :status 404 })
(s/explain-data ::response-not-found { :status 404 })
(s/explain-data ::response-not-found { :status 200 :body { :message "OK" }})
@bhb
Copy link

bhb commented Jan 9, 2018

You could also use s/or to categorize each response with conform, if that is helpful.

Here is an alternate version that uses multispec. Depending on your use case, this may or may not be any better:

;;;;; Another approach
(require '[clojure.spec.alpha :as s])
(require '[clojure.string :as string])

(s/def :response/status #{200 201 404})
;; You could get more specific here if you know what body will contain
(s/def :response/body map?)

(defmulti response-spec :status)
(defmethod response-spec 200 [m]
  (s/and (s/keys :req-un [:response/body])
         #(= (get-in % [:body :message]) "OK")))

(defmethod response-spec 201 [m]
  (s/and (s/keys :req-un [:response/body])
         #(= (get-in m [:body :message]) "Created")))

(defmethod response-spec 404 [m]
  (s/keys))

(s/def :response/valid-response (s/multi-spec response-spec :status))

;; This will just make sure that the response is well formed, not whether it's successful or not
(s/explain :response/response {:status 404})

(s/def :response/response
  (s/and
   :response/valid-response
   (s/or
    :success #(string/starts-with? (str (:status %)) "2")
    :failed #(string/starts-with? (str (:status %)) "4"))))

(s/conform :response/response {:status 404})
;; => [:failed {:status 404}]

(s/conform :response/response {:status 200 :body {:message "OK"}})
;; => [:success {:status 200, :body {:message "OK"}}]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment