mirror of
https://github.com/metosin/reitit.git
synced 2025-12-25 03:08:25 +00:00
http.coercion & http.spec
* TODO: need tests
This commit is contained in:
parent
167de4d7a2
commit
bd844bd816
4 changed files with 83 additions and 6 deletions
53
modules/reitit-http/src/reitit/http/coercion.cljc
Normal file
53
modules/reitit-http/src/reitit/http/coercion.cljc
Normal 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)))}))})
|
||||
23
modules/reitit-http/src/reitit/http/spec.cljc
Normal file
23
modules/reitit-http/src/reitit/http/spec.cljc
Normal 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)))
|
||||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
Loading…
Reference in a new issue