Skip to content

Commit

Permalink
ref(middleware): move exception handling
Browse files Browse the repository at this point in the history
Create a new file under reitit.middleware to process and create
exception middleware.
  • Loading branch information
kkharji committed Jan 2, 2022
1 parent 10f44a3 commit 9e8a221
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 34 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ Full configuration demo:
{:muuntaja true ; default true, can be a modified instance of muuntaja.
:coercion ;; coercion configuration, default nil.
{:coercer 'spec ; coercer to be used
:pretty-coercion? true ; whether to pretty print coercion errors
:error-formater nil} ; function that takes spec validation error map and format it
:pretty-print? true ; whether to pretty print coercion errors requires expound
:formater nil} ; function that takes spec validation error map and format it
:environment ;; Keywords to be injected in requests for convenience.
{:db (ig/ref :foo/database)}
:middleware [] ;; Global middleware to be injected. expected registry key only
Expand Down
56 changes: 25 additions & 31 deletions src/duct/reitit/middleware.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,39 @@
[reitit.ring.coercion :as rcc]
[reitit.ring.middleware.muuntaja :refer [format-middleware]]
[reitit.ring.middleware.parameters :refer [parameters-middleware]]
[reitit.ring.middleware.exception :as exception :refer [create-exception-middleware]]))
[duct.reitit.middleware.exception :as exception :refer [get-exception-middleware]]))

;; TODO: inject environment keys instead
(defm environment-middleware [{:keys [environment]} _ handler request]
(let [inject #(handler (into request %))]
(inject {:environment environment
:id (java.util.UUID/randomUUID)
:start-date (java.util.Date.)})))

(defn- extend-middleware [middleware defaults]
(->> (or middleware [])
(concat defaults)
(vec)
(compact)))
(defn- get-coercion-middleware [{:keys [pretty-print?] :as coercion}]
(when coercion
{:coerce-exceptions (when-not pretty-print? rcc/coerce-exceptions-middleware)
:coerce-request rcc/coerce-request-middleware
:coerce-response rcc/coerce-response-middleware}))

(defn- get-format-middleware [muuntaja]
(when muuntaja format-middleware))

(defn coercion-error-handler [expound-printer status]
(let [printer (expound-printer {:theme :figwheel-theme, :print-specs? false})
handler (exception/create-coercion-handler status)]
(fn [exception request]
(printer (-> exception ex-data :problems))
(handler (-> exception) request))))

;; TODO: use custom coercion error formater for response
(def pretty-coercion-errors
(if-let [expound-printer (try-resolve-sym 'expound.alpha/custom-printer)]
[(create-exception-middleware
(merge exception/default-handlers
{:reitit.coercion/request-coercion (coercion-error-handler expound-printer 400)
:reitit.coercion/response-coercion (coercion-error-handler expound-printer 500)}))]))
(defn- extend-middleware [middleware & defaults]
(->> defaults (conj (or middleware [])) (apply concat) vec compact))

(defmethod init-key :duct.reitit/middleware [_ options]
(let [{:keys [muuntaja middleware coercion]} options]
(->> [parameters-middleware
environment-middleware
(when muuntaja format-middleware)
(when coercion
(if (coercion :pretty-coercion?)
pretty-coercion-errors
rcc/coerce-exceptions-middleware))
(when coercion rcc/coerce-request-middleware)
(when coercion rcc/coerce-response-middleware)]
(extend-middleware middleware))))
(let [{:keys [muuntaja middleware coercion]} options
{:keys [coerce-response coerce-request coerce-exceptions]} (get-coercion-middleware coercion)
format-middleware (get-format-middleware muuntaja)
exception-middleware (get-exception-middleware options)
create-middleware (partial extend-middleware middleware)]
(create-middleware
parameters-middleware
environment-middleware
format-middleware
exception-middleware
coerce-exceptions
coerce-request
coerce-response)))

42 changes: 42 additions & 0 deletions src/duct/reitit/middleware/exception.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
(ns duct.reitit.middleware.exception
(:require [duct.reitit.util :refer [try-resolve-sym]]
[reitit.ring.middleware.exception :as exception :refer [default-handlers create-exception-middleware]]))

(derive ::error ::exception)
(derive ::failure ::exception)
(derive ::horror ::exception)

(defn handler [message exception request]
{:status 500
:body {:message message
:exception (str exception)
:uri (:uri request)}})

(defn coercion-error-handler [status expound-printer _formatter]
(let [printer (expound-printer {:theme :figwheel-theme, :print-specs? false})
handler (exception/create-coercion-handler status)]
(if printer
(fn [exception request]
(printer (-> exception ex-data :problems))
(handler exception request))
(fn [exception request] ;; TODO: format
(handler exception request)))))

(defn coercion-handlers [{:keys [pretty-print? formatter]}]
(let [printer (when pretty-print? (try-resolve-sym 'expound.alpha/custom-printer))]
(when (or printer formatter)
#:reitit.coercion
{:request-coercion (coercion-error-handler 400 printer formatter)
:response-coercion (coercion-error-handler 500 printer formatter)})))

(defn- with-default-exceptions [& handlers]
(->> (cons default-handlers handlers)
(apply merge)
(create-exception-middleware)))

(defn get-exception-middleware
"Create custom exception middleware."
[{:keys [coercion _exception]}]
(with-default-exceptions
(coercion-handlers coercion)))

11 changes: 10 additions & 1 deletion test/duct/reitit/middleware_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,19 @@
(is (not (str/includes? (:body requestc) "github"))))))

(testing "Coercion Pretty Exception"
(let [middleware (new-middleware {:munntaja false :coercion {:pretty-coercion? true}})
(let [middleware (new-middleware {:munntaja false :coercion {:pretty-print? true}})
app (->> {:middleware middleware :environment environment}
(new-router routes)
(ring/ring-handler))
request {:request-method :get :uri (str "/identity/company/users/tami")}]
(is (str/includes? (with-out-str (app request)) "-- Spec failed --------------------")
"Should only print to stdout and not return it")))))

(deftest custom-error-handling
(let [error-handlers {:error "error" ;; ex-data with :type ::error
:exception "exception" ;; ex-data with ::exception or ::failure
java.sql.SQLException "sql-exception" ;; SQLException and all it's child classes
:exception/default "default" ;; override the default handler
:exception/wrap {:log true}} ;; print stack-traces for all exceptions
middleware (new-middleware {:munntaja false :coercion {} :error-handling error-handlers})]))

0 comments on commit 9e8a221

Please sign in to comment.