♻️ Refactor internal backend error handling

This commit is contained in:
Andrey Antukh 2023-10-18 14:29:38 +02:00 committed by Andrés Moya
parent 3ceb4cf895
commit 57c83b5d53

View file

@ -40,41 +40,43 @@
:version/frontend (or (yrq/get-header request "x-frontend-version") "unknown") :version/frontend (or (yrq/get-header request "x-frontend-version") "unknown")
:version/backend (:full cf/version)})) :version/backend (:full cf/version)}))
(defmulti handle-exception (defmulti handle-error
(fn [err & _rest] (fn [cause _ _]
(let [edata (ex-data err)] (-> cause ex-data :type)))
(or (:type edata)
(class err)))))
(defmethod handle-exception :authentication (defmulti handle-exception
[err _] (fn [cause _ _]
(class cause)))
(defmethod handle-error :authentication
[err _ _]
{::yrs/status 401 {::yrs/status 401
::yrs/body (ex-data err)}) ::yrs/body (ex-data err)})
(defmethod handle-exception :authorization (defmethod handle-error :authorization
[err _] [err _ _]
{::yrs/status 403 {::yrs/status 403
::yrs/body (ex-data err)}) ::yrs/body (ex-data err)})
(defmethod handle-exception :restriction (defmethod handle-error :restriction
[err _] [err _ _]
{::yrs/status 400 {::yrs/status 400
::yrs/body (ex-data err)}) ::yrs/body (ex-data err)})
(defmethod handle-exception :rate-limit (defmethod handle-error :rate-limit
[err _] [err _ _]
(let [headers (-> err ex-data ::http/headers)] (let [headers (-> err ex-data ::http/headers)]
{::yrs/status 429 {::yrs/status 429
::yrs/headers headers})) ::yrs/headers headers}))
(defmethod handle-exception :concurrency-limit (defmethod handle-error :concurrency-limit
[err _] [err _ _]
(let [headers (-> err ex-data ::http/headers)] (let [headers (-> err ex-data ::http/headers)]
{::yrs/status 429 {::yrs/status 429
::yrs/headers headers})) ::yrs/headers headers}))
(defmethod handle-exception :validation (defmethod handle-error :validation
[err request] [err request parent-cause]
(let [{:keys [code] :as data} (ex-data err)] (let [{:keys [code] :as data} (ex-data err)]
(cond (cond
(= code :spec-validation) (= code :spec-validation)
@ -97,21 +99,23 @@
(= code :invalid-image) (= code :invalid-image)
(binding [l/*context* (request->context request)] (binding [l/*context* (request->context request)]
(l/error :hint "unexpected error on processing image" :cause err) (let [cause (or parent-cause err)]
{::yrs/status 400 ::yrs/body data}) (l/error :hint "unexpected error on processing image" :cause cause)
{::yrs/status 400 ::yrs/body data}))
:else :else
{::yrs/status 400 ::yrs/body data}))) {::yrs/status 400 ::yrs/body data})))
(defmethod handle-exception :assertion (defmethod handle-error :assertion
[error request] [error request parent-cause]
(binding [l/*context* (request->context request)] (binding [l/*context* (request->context request)]
(let [{:keys [code] :as data} (ex-data error)] (let [{:keys [code] :as data} (ex-data error)
cause (or parent-cause error)]
(cond (cond
(= code :data-validation) (= code :data-validation)
(let [explain (::sm/explain data) (let [explain (::sm/explain data)
payload (sm/humanize-data explain)] payload (sm/humanize-data explain)]
(l/error :hint "Data assertion error" :message (ex-message error) :cause error) (l/error :hint "data assertion error" :message (ex-message error) :cause cause)
{::yrs/status 500 {::yrs/status 500
::yrs/body {:type :server-error ::yrs/body {:type :server-error
:code :assertion :code :assertion
@ -121,7 +125,7 @@
(= code :spec-validation) (= code :spec-validation)
(let [explain (ex/explain data)] (let [explain (ex/explain data)]
(l/error :hint "Spec assertion error" :message (ex-message error) :cause error) (l/error :hint "spec assertion error" :message (ex-message error) :cause cause)
{::yrs/status 500 {::yrs/status 500
::yrs/body {:type :server-error ::yrs/body {:type :server-error
:code :assertion :code :assertion
@ -131,33 +135,48 @@
:else :else
(do (do
(l/error :hint "Assertion error" :message (ex-message error) :cause error) (l/error :hint "assertion error" :message (ex-message error) :cause cause)
{::yrs/status 500 {::yrs/status 500
::yrs/body {:type :server-error ::yrs/body {:type :server-error
:code :assertion :code :assertion
:data data}}))))) :data data}})))))
(defmethod handle-error :not-found
(defmethod handle-exception :not-found [err _ _]
[err _]
{::yrs/status 404 {::yrs/status 404
::yrs/body (ex-data err)}) ::yrs/body (ex-data err)})
(defmethod handle-exception :internal (defmethod handle-error :internal
[error request] [error request parent-cause]
(binding [l/*context* (request->context request)] (binding [l/*context* (request->context request)]
(l/error :hint "Internal error" :message (ex-message error) :cause error) (let [cause (or parent-cause error)]
{::yrs/status 500 (l/error :hint "internal error" :message (ex-message error) :cause cause)
::yrs/body {:type :server-error {::yrs/status 500
:code :unhandled ::yrs/body {:type :server-error
:hint (ex-message error) :code :unhandled
:data (ex-data error)}})) :hint (ex-message error)
:data (ex-data error)}})))
(defmethod handle-error :default
[error request parent-cause]
(let [edata (ex-data error)]
;; This is a special case for the idle-in-transaction error;
;; when it happens, the connection is automatically closed and
;; next-jdbc combines the two errors in a single ex-info. We
;; only need the :handling error, because the :rollback error
;; will be always "connection closed".
(if (and (ex/exception? (:rollback edata))
(ex/exception? (:handling edata)))
(handle-exception (:handling edata) request error)
(handle-exception error request parent-cause))))
(defmethod handle-exception org.postgresql.util.PSQLException (defmethod handle-exception org.postgresql.util.PSQLException
[error request] [error request parent-cause]
(let [state (.getSQLState ^java.sql.SQLException error)] (let [state (.getSQLState ^java.sql.SQLException error)
cause (or parent-cause error)]
(binding [l/*context* (request->context request)] (binding [l/*context* (request->context request)]
(l/error :hint "PSQL error" :message (ex-message error) :cause error) (l/error :hint "PSQL error" :message (ex-message error)
:cause cause)
(cond (cond
(= state "57014") (= state "57014")
{::yrs/status 504 {::yrs/status 504
@ -179,39 +198,44 @@
:state state}})))) :state state}}))))
(defmethod handle-exception :default (defmethod handle-exception :default
[error request] [error request parent-cause]
(let [edata (ex-data error)] (let [edata (ex-data error)
cause (or parent-cause error)]
(cond (cond
;; This means that exception is not a controlled exception. ;; This means that exception is not a controlled exception.
(nil? edata) (nil? edata)
(binding [l/*context* (request->context request)] (binding [l/*context* (request->context request)]
(l/error :hint "Unexpected error" :message (ex-message error) :cause error) (l/error :hint "unexpected error" :message (ex-message error) :cause cause)
{::yrs/status 500 {::yrs/status 500
::yrs/body {:type :server-error ::yrs/body {:type :server-error
:code :unexpected :code :unexpected
:hint (ex-message error)}}) :hint (ex-message error)}})
;; This is a special case for the idle-in-transaction error;
;; when it happens, the connection is automatically closed and
;; next-jdbc combines the two errors in a single ex-info. We
;; only need the :handling error, because the :rollback error
;; will be always "connection closed".
(and (ex/exception? (:rollback edata))
(ex/exception? (:handling edata)))
(handle-exception (:handling edata) request)
:else :else
(binding [l/*context* (request->context request)] (binding [l/*context* (request->context request)]
(l/error :hint "Unhandled error" :message (ex-message error) :cause error) (l/error :hint "unhandled error" :message (ex-message error) :cause cause)
{::yrs/status 500 {::yrs/status 500
::yrs/body {:type :server-error ::yrs/body {:type :server-error
:code :unhandled :code :unhandled
:hint (ex-message error) :hint (ex-message error)
:data edata}})))) :data edata}}))))
(defmethod handle-exception java.util.concurrent.CompletionException
[cause request _]
(let [cause' (ex-cause cause)]
(if (ex/error? cause)
(handle-error cause' request cause)
(handle-exception cause' request cause))))
(defmethod handle-exception java.util.concurrent.ExecutionException
[cause request _]
(let [cause' (ex-cause cause)]
(if (ex/error? cause')
(handle-error cause' request cause)
(handle-exception cause' request cause))))
(defn handle (defn handle
[cause request] [cause request]
(if (or (instance? java.util.concurrent.CompletionException cause) (if (ex/error? cause)
(instance? java.util.concurrent.ExecutionException cause)) (handle-error cause request nil)
(handle-exception (ex-cause cause) request) (handle-exception cause request nil)))
(handle-exception cause request)))