diff --git a/backend/src/app/http/errors.clj b/backend/src/app/http/errors.clj index bd40ded1d..fee8f2336 100644 --- a/backend/src/app/http/errors.clj +++ b/backend/src/app/http/errors.clj @@ -49,12 +49,20 @@ (defmethod handle-exception :validation [err _] - (let [data (ex-data err) - explain (us/pretty-explain data)] - (yrs/response :status 400 - :body (-> data - (dissoc ::s/problems ::s/value) - (cond-> explain (assoc :explain explain)))))) + (let [{:keys [code] :as data} (ex-data err)] + (cond + (= code :spec-validation) + (let [explain (us/pretty-explain data)] + (yrs/response :status 400 + :body (-> data + (dissoc ::s/problems ::s/value) + (cond-> explain (assoc :explain explain))))) + + (= code :request-body-too-large) + (yrs/response :status 413 :body data) + + :else + (yrs/response :status 400 :body data)))) (defmethod handle-exception :assertion [error request] diff --git a/backend/src/app/http/middleware.clj b/backend/src/app/http/middleware.clj index bdc997c05..af7f140a8 100644 --- a/backend/src/app/http/middleware.clj +++ b/backend/src/app/http/middleware.clj @@ -16,7 +16,10 @@ [yetti.middleware :as ymw] [yetti.request :as yrq] [yetti.response :as yrs]) - (:import java.io.OutputStream)) + (:import + com.fasterxml.jackson.core.io.JsonEOFException + io.undertow.server.RequestTooBigException + java.io.OutputStream)) (def server-timing {:name ::server-timing @@ -46,16 +49,29 @@ (update :params merge params)))) :else - request)))] + request))) + + (handle-error [raise cause] + (cond + (instance? RequestTooBigException cause) + (raise (ex/error :type :validation + :code :request-body-too-large + :hint (ex-message cause))) + + (instance? JsonEOFException cause) + (raise (ex/error :type :validation + :code :malformed-json + :hint (ex-message cause))) + :else + (raise cause)))] (fn [request respond raise] (when-let [request (try (process-request request) - (catch Exception cause - (raise (ex/error :type :validation - :code :malformed-params - :hint (ex-message cause) - :cause cause))))] + (catch RuntimeException cause + (handle-error raise (or (.getCause cause) cause))) + (catch Throwable cause + (handle-error raise cause)))] (handler request respond raise))))) (def parse-request diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index df77de7b8..9fe00155e 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -20,9 +20,9 @@ :read-only (cf/get :database-readonly false) :metrics (ig/ref :app.metrics/metrics) :migrations (ig/ref :app.migrations/all) - :name :main - :min-size (cf/get :database-min-pool-size 0) - :max-size (cf/get :database-max-pool-size 30)} + :name :main + :min-size (cf/get :database-min-pool-size 0) + :max-size (cf/get :database-max-pool-size 30)} ;; Default thread pool for IO operations [::default :app.worker/executor] diff --git a/frontend/src/app/main/errors.cljs b/frontend/src/app/main/errors.cljs index 549813a40..163f70308 100644 --- a/frontend/src/app/main/errors.cljs +++ b/frontend/src/app/main/errors.cljs @@ -7,8 +7,10 @@ (ns app.main.errors "Generic error handling" (:require + [app.common.data :as d] [app.common.data.macros :as dm] [app.common.exceptions :as ex] + [app.common.pprint :as pp] [app.config :as cf] [app.main.data.messages :as msg] [app.main.data.users :as du] @@ -17,7 +19,6 @@ [app.util.i18n :refer [tr]] [app.util.router :as rt] [app.util.timers :as ts] - [fipp.edn :as fpp] [potok.core :as ptk])) (defn on-error @@ -59,16 +60,15 @@ (defmethod ptk/handle-error :validation [error] (ts/schedule - (st/emitf - (msg/show {:content "Unexpected validation error." - :type :error - :timeout 3000}))) + #(st/emit! (msg/show {:content "Validation error" + :type :error + :timeout 3000}))) ;; Print to the console some debug info. (js/console.group "Validation Error:") (ex/ignoring (js/console.info - (with-out-str (fpp/pprint (dissoc error :explain))))) + (pp/pprint-str (dissoc error :explain)))) (when-let [explain (:explain error)] (js/console.group "Spec explain:") @@ -78,24 +78,46 @@ (js/console.groupEnd "Validation Error:")) +;; All the errors that happens on worker are handled here. +(defmethod ptk/handle-error :worker-error + [{:keys [code data hint] :as error}] + (let [hint (or hint (:hint data) (:message data) (d/name code)) + info (pp/pprint-str (dissoc data :explain)) + msg (dm/str "Internal Worker Error: " hint)] + + (ts/schedule + #(st/emit! + (msg/show {:content "Something wrong has happened (on worker)." + :type :error + :timeout 3000}))) + + (js/console.group msg) + (js/console.info info) + + (when-let [explain (:explain data)] + (js/console.group "Spec explain:") + (js/console.log explain) + (js/console.groupEnd "Spec explain:")) + + (js/console.groupEnd msg))) + + ;; Error on parsing an SVG ;; TODO: looks unused and deprecated (defmethod ptk/handle-error :svg-parser [_] (ts/schedule - (st/emitf - (msg/show {:content "SVG is invalid or malformed" - :type :error - :timeout 3000})))) + #(st/emit! (msg/show {:content "SVG is invalid or malformed" + :type :error + :timeout 3000})))) ;; TODO: should be handled in the event and not as general error handler (defmethod ptk/handle-error :comment-error [_] (ts/schedule - (st/emitf - (msg/show {:content "There was an error with the comment" - :type :error - :timeout 3000})))) + #(st/emit! (msg/show {:content "There was an error with the comment" + :type :error + :timeout 3000})))) ;; This is a pure frontend error that can be caused by an active ;; assertion (assertion that is preserved on production builds). From @@ -110,10 +132,9 @@ (dm/str cf/public-uri "js/cljs-runtime/" (:file error)) (:line error))] (ts/schedule - (st/emitf - (msg/show {:content "Internal error: assertion." - :type :error - :timeout 3000}))) + #(st/emit! (msg/show {:content "Internal error: assertion." + :type :error + :timeout 3000}))) ;; Print to the console some debugging info (js/console.group message) @@ -139,7 +160,7 @@ (defmethod ptk/handle-error :server-error [{:keys [data hint] :as error}] (let [hint (or hint (:hint data) (:message data)) - info (with-out-str (fpp/pprint (dissoc data :explain))) + info (pp/pprint-str (dissoc data :explain)) msg (dm/str "Internal Server Error: " hint)] (ts/schedule diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index 6ed957f11..2e3ffb9fa 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -31,6 +31,10 @@ (= 200 status) (rx/of body) + (= 413 status) + (rx/throw {:type :validation + :code :request-body-too-large}) + (and (>= status 400) (map? body)) (rx/throw body) diff --git a/frontend/src/app/worker.cljs b/frontend/src/app/worker.cljs index a37bce958..5889a8cd9 100644 --- a/frontend/src/app/worker.cljs +++ b/frontend/src/app/worker.cljs @@ -54,9 +54,11 @@ (reply-error [cause] (if (map? cause) - (post {:error cause}) - (post {:error {:type :unexpected - :code :unhandled-error-on-worker + (post {:error {:type :worker-error + :code (or (:type cause) :wrapped) + :data cause}}) + (post {:error {:type :worker-error + :code :unhandled-error :hint (ex-message cause) :data (ex-data cause)}}))) diff --git a/frontend/src/app/worker/thumbnails.cljs b/frontend/src/app/worker/thumbnails.cljs index e5e9d77de..66ac64dfb 100644 --- a/frontend/src/app/worker/thumbnails.cljs +++ b/frontend/src/app/worker/thumbnails.cljs @@ -16,22 +16,35 @@ [beicon.core :as rx] [rumext.alpha :as mf])) -(defn- not-found? - [{:keys [type]}] - (= :not-found type)) - (defn- handle-response - [response] + [{:keys [body status] :as response}] (cond (http/success? response) (rx/of (:body response)) - (http/client-error? response) - (rx/throw (:body response)) + (= status 413) + (rx/throw {:type :validation + :code :request-body-too-large + :hint "request body too large"}) + + (and (http/client-error? response) + (map? body)) + (rx/throw body) :else - (rx/throw {:type :unexpected - :code (:error response)}))) + (rx/throw {:type :unexpected-error + :code :unhandled-http-response + :http-status status + :http-body body}))) + +(defn- not-found? + [{:keys [type]}] + (= :not-found type)) + +(defn- body-too-large? + [{:keys [type code]}] + (and (= :validation type) + (= :request-body-too-large code))) (defn- request-data-for-thumbnail [file-id revn] @@ -88,6 +101,7 @@ (->> (http/send! request) (rx/map http/conditional-decode-transit) (rx/mapcat handle-response) + (rx/catch body-too-large? (constantly nil)) (rx/map (constantly params))))) (defmethod impl/handler :thumbnails/generate