I'm speccing http responses from Google Calendar API and I want different specs for each response type.
I've defined a spec for HTTP response as
(s/def ::http-resp
(s/keys :req-un [:status] :opt-un [:body]))
But how do I define a spec for each HTTP status?
I know I can do :s.http-error-401/status
, but I would prefer something like
(s/and ::http-response
(key-in-a-map :status :s.http-statuses.error/gone))
Maybe there's an example of good specs for HTTP responses? I've found only ring-spec so far.
IMO ring-spec
already serve your purpose. You can find the spec for HTTP status there:
(s/def :ring.response/status (s/int-in 100 600))
Note that HTTP status code is a number. If you need to fine tune the spec, you can always do something like this:
(def ^:const OK 200)
(def ^:const UNAUTHORIZED 401)
(s/def ::status #{OK UNAUTHORIZED})
(s/def ::body string?)
(s/def ::response (s/keys :req-un [::status] :opt [::body]))
(s/valid? ::response {:status 200
:body "Hello"})
;; => true
(s/valid? ::response {:status 1000
:body "Hello"})
;; => false
Updated based on comment from 2021.09.26
You can also define each return code as individual spec and combine them with or
. Then you can use conform
to get the code:
(s/def ::status (status-spec [200 OK
400 BAD_REUEST
404 NOT_FOUND]))
(s/def ::response (s/keys :req-un [::status] :opt [::body]))
(s/conform ::response {:status 200
:body "Hello"})
;; => {:status [:user/OK 200], :body "Hello"}
(s/conform ::status 200)
;; => [:user/OK 200]
Macro to make defining individual return code spec easier:
(defn- destructure-kv [kvs]
(let [xs (partition 2 kvs)]
(interleave (map (comp (partial keyword (str *ns*))
str
last)
xs)
(map (comp set vector first) xs))))
(defmacro status-spec [kvs]
`(s/or ~@(destructure-kv kvs)))
which basically generate an or
:
(status-spec [200 OK
400 BAD_REUEST
404 NOT_FOUND])
=>
(s/or :user/OK #{200}
:user/BAD_REUEST #{400}
:user/NOT_FOUND #{404})