http.coercion & http.spec

* TODO: need tests
This commit is contained in:
Tommi Reiman 2018-08-17 08:41:49 +03:00
parent 167de4d7a2
commit bd844bd816
4 changed files with 83 additions and 6 deletions

View file

@ -0,0 +1,53 @@
(ns reitit.http.coercion
(:require [reitit.coercion :as coercion]
[reitit.spec :as rs]
[reitit.impl :as impl]))
(def coerce-request-interceptor
"Interceptor for pluggable request coercion.
Expects a :coercion of type `reitit.coercion/Coercion`
and :parameters from route data, otherwise does not mount."
{:name ::coerce-request
:spec ::rs/parameters
:compile (fn [{:keys [coercion parameters]} opts]
(if (and coercion parameters)
(let [coercers (coercion/request-coercers coercion parameters opts)]
{:enter
(fn [ctx]
(let [request (:request ctx)
coerced (coercion/coerce-request coercers request)
request (impl/fast-assoc request :parameters coerced)]
(assoc ctx :request request)))})))})
(def coerce-response-interceptor
"Interceptor for pluggable response coercion.
Expects a :coercion of type `reitit.coercion/Coercion`
and :responses from route data, otherwise does not mount."
{:name ::coerce-response
:spec ::rs/responses
:compile (fn [{:keys [coercion responses]} opts]
(if (and coercion responses)
(let [coercers (coercion/response-coercers coercion responses opts)]
{:leave
(fn [ctx]
(let [response (coercion/coerce-response coercers (:request ctx) (:response ctx))]
(assoc ctx :response response)))})))})
(def coerce-exceptions-interceptor
"Interceptor for handling coercion exceptions.
Expects a :coercion of type `reitit.coercion/Coercion`
and :parameters or :responses from route data, otherwise does not mount."
{:name ::coerce-exceptions
:compile (fn [{:keys [coercion parameters responses]} _]
(if (and coercion (or parameters responses))
{:error (fn [ctx]
(let [data (ex-data (:error ctx))]
(if-let [status (case (:type data)
::coercion/request-coercion 400
::coercion/response-coercion 500
nil)]
(let [response {:status status, :body (coercion/encode-error data)}]
(-> ctx
(assoc :response response)
(assoc :error nil)))
ctx)))}))})

View file

@ -0,0 +1,23 @@
(ns reitit.http.spec
(:require [clojure.spec.alpha :as s]
[reitit.ring.spec :as rrs]
[reitit.interceptor :as interceptor]
[reitit.spec :as rs]))
;;
;; Specs
;;
(s/def ::interceptors (s/coll-of (partial satisfies? interceptor/IntoInterceptor)))
(s/def ::data
(s/keys :opt-un [::rs/handler ::rs/name ::interceptors]))
;;
;; Validator
;;
(defn validate-spec!
[routes {:keys [spec ::rs/explain] :or {explain s/explain-str, spec ::data}}]
(when-let [problems (rrs/validate-route-data routes :interceptors spec)]
(rs/throw-on-problems! problems explain)))

View file

@ -5,7 +5,7 @@
(defn handle-coercion-exception [e respond raise]
(let [data (ex-data e)]
(if-let [status (condp = (:type data)
(if-let [status (case (:type data)
::coercion/request-coercion 400
::coercion/response-coercion 500
nil)]

View file

@ -7,7 +7,7 @@
;; Specs
;;
(s/def ::middleware (s/coll-of #(satisfies? middleware/IntoMiddleware %)))
(s/def ::middleware (s/coll-of (partial satisfies? middleware/IntoMiddleware)))
(s/def ::data
(s/keys :req-un [::rs/handler]
@ -26,11 +26,12 @@
:non-specs non-specs})))
(s/merge-spec-impl (vec specs) (vec specs) nil))
(defn- validate-ring-route-data [routes spec]
(defn- validate-route-data [routes key spec]
(->> (for [[p _ c] routes
[method {:keys [data middleware] :as endpoint}] c
[method {:keys [data] :as endpoint}] c
:when endpoint
:let [mw-specs (seq (keep :spec middleware))
:let [target (key endpoint)
mw-specs (seq (keep :spec target))
specs (keep identity (into [spec] mw-specs))
spec (merge-specs specs)]]
(when-let [problems (and spec (s/explain-data spec data))]
@ -39,5 +40,5 @@
(defn validate-spec!
[routes {:keys [spec ::rs/explain] :or {explain s/explain-str, spec ::data}}]
(when-let [problems (validate-ring-route-data routes spec)]
(when-let [problems (validate-route-data routes :middleware spec)]
(rs/throw-on-problems! problems explain)))