diff --git a/modules/reitit-http/src/reitit/http/coercion.cljc b/modules/reitit-http/src/reitit/http/coercion.cljc new file mode 100644 index 00000000..e14019a7 --- /dev/null +++ b/modules/reitit-http/src/reitit/http/coercion.cljc @@ -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)))}))}) diff --git a/modules/reitit-http/src/reitit/http/spec.cljc b/modules/reitit-http/src/reitit/http/spec.cljc new file mode 100644 index 00000000..2834a705 --- /dev/null +++ b/modules/reitit-http/src/reitit/http/spec.cljc @@ -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))) diff --git a/modules/reitit-ring/src/reitit/ring/coercion.cljc b/modules/reitit-ring/src/reitit/ring/coercion.cljc index e5c91da2..489c058e 100644 --- a/modules/reitit-ring/src/reitit/ring/coercion.cljc +++ b/modules/reitit-ring/src/reitit/ring/coercion.cljc @@ -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)] diff --git a/modules/reitit-ring/src/reitit/ring/spec.cljc b/modules/reitit-ring/src/reitit/ring/spec.cljc index 9039482a..d95a4bef 100644 --- a/modules/reitit-ring/src/reitit/ring/spec.cljc +++ b/modules/reitit-ring/src/reitit/ring/spec.cljc @@ -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)))