mirror of
https://github.com/penpot/penpot.git
synced 2025-04-01 01:21:21 -05:00
✨ Improve error handling
This commit is contained in:
parent
ec5a4d09b8
commit
9abf4b126c
5 changed files with 96 additions and 88 deletions
|
@ -19,7 +19,7 @@
|
||||||
io.lettuce/lettuce-core {:mvn/version "6.1.6.RELEASE"}
|
io.lettuce/lettuce-core {:mvn/version "6.1.6.RELEASE"}
|
||||||
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
|
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
|
||||||
|
|
||||||
funcool/yetti {:git/tag "v8.0" :git/sha "ea7162d"
|
funcool/yetti {:git/tag "v9.0" :git/sha "e09e46c"
|
||||||
:git/url "https://github.com/funcool/yetti.git"
|
:git/url "https://github.com/funcool/yetti.git"
|
||||||
:exclusions [org.slf4j/slf4j-api]}
|
:exclusions [org.slf4j/slf4j-api]}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
|
[app.common.transit :as t]
|
||||||
[app.http.doc :as doc]
|
[app.http.doc :as doc]
|
||||||
[app.http.errors :as errors]
|
[app.http.errors :as errors]
|
||||||
[app.http.middleware :as middleware]
|
[app.http.middleware :as middleware]
|
||||||
|
@ -67,7 +68,7 @@
|
||||||
:xnio/dispatch (:executor cfg)
|
:xnio/dispatch (:executor cfg)
|
||||||
:ring/async true}
|
:ring/async true}
|
||||||
handler (if (some? router)
|
handler (if (some? router)
|
||||||
(wrap-router cfg router)
|
(wrap-router router)
|
||||||
handler)
|
handler)
|
||||||
server (yt/server handler (d/without-nils options))]
|
server (yt/server handler (d/without-nils options))]
|
||||||
(assoc cfg :server (yt/start! server))))
|
(assoc cfg :server (yt/start! server))))
|
||||||
|
@ -81,30 +82,32 @@
|
||||||
[_ respond _]
|
[_ respond _]
|
||||||
(respond (yrs/response 404)))
|
(respond (yrs/response 404)))
|
||||||
|
|
||||||
(defn- ring-handler
|
|
||||||
[router]
|
|
||||||
(fn [request respond raise]
|
|
||||||
(if-let [match (r/match-by-path router (yrq/path request))]
|
|
||||||
(let [params (:path-params match)
|
|
||||||
result (:result match)
|
|
||||||
handler (or (:handler result) not-found-handler)
|
|
||||||
request (-> request
|
|
||||||
(assoc :path-params params)
|
|
||||||
(update :params merge params))]
|
|
||||||
(handler request respond raise))
|
|
||||||
(not-found-handler request respond raise))))
|
|
||||||
|
|
||||||
(defn- wrap-router
|
(defn- wrap-router
|
||||||
[_ router]
|
[router]
|
||||||
(let [handler (ring-handler router)]
|
(letfn [(handler [request respond raise]
|
||||||
|
(if-let [match (r/match-by-path router (yrq/path request))]
|
||||||
|
(let [params (:path-params match)
|
||||||
|
result (:result match)
|
||||||
|
handler (or (:handler result) not-found-handler)
|
||||||
|
request (-> request
|
||||||
|
(assoc :path-params params)
|
||||||
|
(update :params merge params))]
|
||||||
|
(handler request respond raise))
|
||||||
|
(not-found-handler request respond raise)))
|
||||||
|
|
||||||
|
(on-error [cause request respond]
|
||||||
|
(let [{:keys [body] :as response} (errors/handle cause request)]
|
||||||
|
(respond
|
||||||
|
(cond-> response
|
||||||
|
(map? body)
|
||||||
|
(-> (update :headers assoc "content-type" "application/transit+json")
|
||||||
|
(assoc :body (t/encode-str body {:type :json-verbose})))))))]
|
||||||
|
|
||||||
(fn [request respond _]
|
(fn [request respond _]
|
||||||
(handler request respond
|
(try
|
||||||
(fn [cause]
|
(handler request respond #(on-error % request respond))
|
||||||
(l/error :hint "unexpected error processing request"
|
(catch Throwable cause
|
||||||
::l/context (errors/get-context request)
|
(on-error cause request respond))))))
|
||||||
:query-string (yrq/query request)
|
|
||||||
:cause cause)
|
|
||||||
(respond (yrs/response 500 "internal server error")))))))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; HTTP ROUTER
|
;; HTTP ROUTER
|
||||||
|
@ -130,6 +133,8 @@
|
||||||
(rr/router
|
(rr/router
|
||||||
[["" {:middleware [[middleware/server-timing]
|
[["" {:middleware [[middleware/server-timing]
|
||||||
[middleware/format-response]
|
[middleware/format-response]
|
||||||
|
[middleware/params]
|
||||||
|
[middleware/parse-request]
|
||||||
[middleware/errors errors/handle]
|
[middleware/errors errors/handle]
|
||||||
[middleware/restrict-methods]]}
|
[middleware/restrict-methods]]}
|
||||||
["/metrics" {:handler (:handler metrics)}]
|
["/metrics" {:handler (:handler metrics)}]
|
||||||
|
@ -138,9 +143,7 @@
|
||||||
["/by-file-media-id/:id" {:handler (:file-objects-handler assets)}]
|
["/by-file-media-id/:id" {:handler (:file-objects-handler assets)}]
|
||||||
["/by-file-media-id/:id/thumbnail" {:handler (:file-thumbnails-handler assets)}]]
|
["/by-file-media-id/:id/thumbnail" {:handler (:file-thumbnails-handler assets)}]]
|
||||||
|
|
||||||
["/dbg" {:middleware [[middleware/params]
|
["/dbg" {:middleware [(:middleware session)]}
|
||||||
[middleware/parse-request]
|
|
||||||
(:middleware session)]}
|
|
||||||
["" {:handler (:index debug)}]
|
["" {:handler (:index debug)}]
|
||||||
["/error-by-id/:id" {:handler (:retrieve-error debug)}]
|
["/error-by-id/:id" {:handler (:retrieve-error debug)}]
|
||||||
["/error/:id" {:handler (:retrieve-error debug)}]
|
["/error/:id" {:handler (:retrieve-error debug)}]
|
||||||
|
@ -152,15 +155,11 @@
|
||||||
["/sns" {:handler (:awsns-handler cfg)
|
["/sns" {:handler (:awsns-handler cfg)
|
||||||
:allowed-methods #{:post}}]]
|
:allowed-methods #{:post}}]]
|
||||||
|
|
||||||
["/ws/notifications" {:middleware [[middleware/params]
|
["/ws/notifications" {:middleware [(:middleware session)]
|
||||||
[middleware/parse-request]
|
|
||||||
(:middleware session)]
|
|
||||||
:handler ws
|
:handler ws
|
||||||
:allowed-methods #{:get}}]
|
:allowed-methods #{:get}}]
|
||||||
|
|
||||||
["/api" {:middleware [[middleware/cors]
|
["/api" {:middleware [[middleware/cors]
|
||||||
[middleware/params]
|
|
||||||
[middleware/parse-request]
|
|
||||||
(:middleware session)]}
|
(:middleware session)]}
|
||||||
["/health" {:handler (:health-check debug)}]
|
["/health" {:handler (:health-check debug)}]
|
||||||
["/_doc" {:handler (doc/handler rpc)
|
["/_doc" {:handler (doc/handler rpc)
|
||||||
|
|
|
@ -24,8 +24,8 @@
|
||||||
(defn get-context
|
(defn get-context
|
||||||
[request]
|
[request]
|
||||||
(merge
|
(merge
|
||||||
{:path (:uri request)
|
{:path (:path request)
|
||||||
:method (:request-method request)
|
:method (:method request)
|
||||||
:params (:params request)
|
:params (:params request)
|
||||||
:ip-addr (parse-client-ip request)
|
:ip-addr (parse-client-ip request)
|
||||||
:profile-id (:profile-id request)}
|
:profile-id (:profile-id request)}
|
||||||
|
@ -51,9 +51,10 @@
|
||||||
[err _]
|
[err _]
|
||||||
(let [data (ex-data err)
|
(let [data (ex-data err)
|
||||||
explain (us/pretty-explain data)]
|
explain (us/pretty-explain data)]
|
||||||
(yrs/response 400 (-> data
|
(yrs/response :status 400
|
||||||
(dissoc ::s/problems ::s/value)
|
:body (-> data
|
||||||
(cond-> explain (assoc :explain explain))))))
|
(dissoc ::s/problems ::s/value)
|
||||||
|
(cond-> explain (assoc :explain explain))))))
|
||||||
|
|
||||||
(defmethod handle-exception :assertion
|
(defmethod handle-exception :assertion
|
||||||
[error request]
|
[error request]
|
||||||
|
@ -73,26 +74,6 @@
|
||||||
[err _]
|
[err _]
|
||||||
(yrs/response 404 (ex-data err)))
|
(yrs/response 404 (ex-data err)))
|
||||||
|
|
||||||
(defmethod handle-exception :default
|
|
||||||
[error request]
|
|
||||||
(let [edata (ex-data error)]
|
|
||||||
;; NOTE: 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)
|
|
||||||
(do
|
|
||||||
(l/error ::l/raw (ex-message error)
|
|
||||||
::l/context (get-context request)
|
|
||||||
:cause error)
|
|
||||||
(yrs/response 500 {:type :server-error
|
|
||||||
:code :unexpected
|
|
||||||
:hint (ex-message error)
|
|
||||||
:data edata})))))
|
|
||||||
|
|
||||||
(defmethod handle-exception org.postgresql.util.PSQLException
|
(defmethod handle-exception org.postgresql.util.PSQLException
|
||||||
[error request]
|
[error request]
|
||||||
(let [state (.getSQLState ^java.sql.SQLException error)]
|
(let [state (.getSQLState ^java.sql.SQLException error)]
|
||||||
|
@ -101,24 +82,56 @@
|
||||||
:cause error)
|
:cause error)
|
||||||
(cond
|
(cond
|
||||||
(= state "57014")
|
(= state "57014")
|
||||||
(yrs/response 504 {:type :server-timeout
|
(yrs/response 504 {:type :server-error
|
||||||
:code :statement-timeout
|
:code :statement-timeout
|
||||||
:hint (ex-message error)})
|
:hint (ex-message error)})
|
||||||
|
|
||||||
(= state "25P03")
|
(= state "25P03")
|
||||||
(yrs/response 504 {:type :server-timeout
|
(yrs/response 504 {:type :server-error
|
||||||
:code :idle-in-transaction-timeout
|
:code :idle-in-transaction-timeout
|
||||||
:hint (ex-message error)})
|
:hint (ex-message error)})
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(yrs/response 500 {:type :server-error
|
(yrs/response 500 {:type :server-error
|
||||||
:code :psql-exception
|
:code :unexpected
|
||||||
:hint (ex-message error)
|
:hint (ex-message error)
|
||||||
:state state}))))
|
:state state}))))
|
||||||
|
|
||||||
|
(defmethod handle-exception :default
|
||||||
|
[error request]
|
||||||
|
(let [edata (ex-data error)]
|
||||||
|
(cond
|
||||||
|
;; This means that exception is not a controlled exception.
|
||||||
|
(nil? edata)
|
||||||
|
(do
|
||||||
|
(l/error ::l/raw (ex-message error)
|
||||||
|
::l/context (get-context request)
|
||||||
|
:cause error)
|
||||||
|
(yrs/response 500 {:type :server-error
|
||||||
|
:code :unexpected
|
||||||
|
: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
|
||||||
|
(do
|
||||||
|
(l/error ::l/raw (ex-message error)
|
||||||
|
::l/context (get-context request)
|
||||||
|
:cause error)
|
||||||
|
(yrs/response 500 {:type :server-error
|
||||||
|
:code :unhandled
|
||||||
|
:hint (ex-message error)
|
||||||
|
:data edata})))))
|
||||||
(defn handle
|
(defn handle
|
||||||
[error req]
|
[error request]
|
||||||
(if (or (instance? java.util.concurrent.CompletionException error)
|
(if (or (instance? java.util.concurrent.CompletionException error)
|
||||||
(instance? java.util.concurrent.ExecutionException error))
|
(instance? java.util.concurrent.ExecutionException error))
|
||||||
(handle-exception (.getCause ^Throwable error) req)
|
(handle-exception (.getCause ^Throwable error) request)
|
||||||
(handle-exception error req)))
|
(handle-exception error request)))
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
(ns app.http.middleware
|
(ns app.http.middleware
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.exceptions :as ex]
|
||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
[app.common.transit :as t]
|
[app.common.transit :as t]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
|
@ -45,23 +46,17 @@
|
||||||
(update :params merge params))))
|
(update :params merge params))))
|
||||||
|
|
||||||
:else
|
:else
|
||||||
request)))
|
request)))]
|
||||||
|
|
||||||
(handle-exception [cause]
|
|
||||||
(let [data {:type :validation
|
|
||||||
:code :unable-to-parse-request-body
|
|
||||||
:hint "malformed params"}]
|
|
||||||
(l/error :hint (ex-message cause) :cause cause)
|
|
||||||
(yrs/response :status 400
|
|
||||||
:headers {"content-type" "application/transit+json"}
|
|
||||||
:body (t/encode-str data {:type :json-verbose}))))]
|
|
||||||
|
|
||||||
(fn [request respond raise]
|
(fn [request respond raise]
|
||||||
(try
|
(try
|
||||||
(let [request (process-request request)]
|
(let [request (process-request request)]
|
||||||
(handler request respond raise))
|
(handler request respond raise))
|
||||||
(catch Exception cause
|
(catch Exception cause
|
||||||
(respond (handle-exception cause)))))))
|
(raise (ex/error :type :validation
|
||||||
|
:code :malformed-params
|
||||||
|
:hint (ex-message cause)
|
||||||
|
:cause cause)))))))
|
||||||
|
|
||||||
(def parse-request
|
(def parse-request
|
||||||
{:name ::parse-request
|
{:name ::parse-request
|
||||||
|
|
|
@ -52,18 +52,6 @@
|
||||||
(st/emit! (du/logout {:capture-redirect true}))
|
(st/emit! (du/logout {:capture-redirect true}))
|
||||||
(ts/schedule 500 (st/emitf (msg/warn msg)))))
|
(ts/schedule 500 (st/emitf (msg/warn msg)))))
|
||||||
|
|
||||||
|
|
||||||
;; That are special case server-errors that should be treated
|
|
||||||
;; differently.
|
|
||||||
(derive :not-found ::exceptional-state)
|
|
||||||
(derive :bad-gateway ::exceptional-state)
|
|
||||||
(derive :service-unavailable ::exceptional-state)
|
|
||||||
|
|
||||||
(defmethod ptk/handle-error ::exceptional-state
|
|
||||||
[error]
|
|
||||||
(ts/schedule
|
|
||||||
(st/emitf (rt/assign-exception error))))
|
|
||||||
|
|
||||||
;; Error that happens on an active business model validation does not
|
;; Error that happens on an active business model validation does not
|
||||||
;; passes an validation (example: profile can't leave a team). From
|
;; passes an validation (example: profile can't leave a team). From
|
||||||
;; the user perspective a error flash message should be visualized but
|
;; the user perspective a error flash message should be visualized but
|
||||||
|
@ -134,9 +122,22 @@
|
||||||
(js/console.error (with-out-str (expound/printer error)))
|
(js/console.error (with-out-str (expound/printer error)))
|
||||||
(js/console.groupEnd message)))
|
(js/console.groupEnd message)))
|
||||||
|
|
||||||
|
;; That are special case server-errors that should be treated
|
||||||
|
;; differently.
|
||||||
|
|
||||||
|
(derive :not-found ::exceptional-state)
|
||||||
|
(derive :bad-gateway ::exceptional-state)
|
||||||
|
(derive :service-unavailable ::exceptional-state)
|
||||||
|
|
||||||
|
(defmethod ptk/handle-error ::exceptional-state
|
||||||
|
[error]
|
||||||
|
(ts/schedule
|
||||||
|
(st/emitf (rt/assign-exception error))))
|
||||||
|
|
||||||
;; This happens when the backed server fails to process the
|
;; This happens when the backed server fails to process the
|
||||||
;; request. This can be caused by an internal assertion or any other
|
;; request. This can be caused by an internal assertion or any other
|
||||||
;; uncontrolled error.
|
;; uncontrolled error.
|
||||||
|
|
||||||
(defmethod ptk/handle-error :server-error
|
(defmethod ptk/handle-error :server-error
|
||||||
[{:keys [data hint] :as error}]
|
[{:keys [data hint] :as error}]
|
||||||
(let [hint (or hint (:hint data) (:message data))
|
(let [hint (or hint (:hint data) (:message data))
|
||||||
|
@ -146,8 +147,8 @@
|
||||||
(ts/schedule
|
(ts/schedule
|
||||||
#(st/emit!
|
#(st/emit!
|
||||||
(msg/show {:content "Something wrong has happened (on backend)."
|
(msg/show {:content "Something wrong has happened (on backend)."
|
||||||
:type :error
|
:type :error
|
||||||
:timeout 3000})))
|
:timeout 3000})))
|
||||||
|
|
||||||
(js/console.group msg)
|
(js/console.group msg)
|
||||||
(js/console.info info)
|
(js/console.info info)
|
||||||
|
|
Loading…
Add table
Reference in a new issue