mirror of
https://github.com/penpot/penpot.git
synced 2026-04-28 12:48:31 +00:00
170 lines
5.9 KiB
Clojure
170 lines
5.9 KiB
Clojure
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
;;
|
|
;; Copyright (c) KALEIDOS INC
|
|
|
|
(ns app.http.errors
|
|
"A errors handling for the http server."
|
|
(:require
|
|
[app.common.exceptions :as ex]
|
|
[app.common.logging :as l]
|
|
[app.http :as-alias http]
|
|
[app.http.access-token :as-alias actoken]
|
|
[app.http.session :as-alias session]
|
|
[clojure.spec.alpha :as s]
|
|
[cuerdas.core :as str]
|
|
[yetti.request :as yrq]
|
|
[yetti.response :as yrs]))
|
|
|
|
(defn- parse-client-ip
|
|
[request]
|
|
(or (some-> (yrq/get-header request "x-forwarded-for") (str/split ",") first)
|
|
(yrq/get-header request "x-real-ip")
|
|
(yrq/remote-addr request)))
|
|
|
|
(defn request->context
|
|
"Extracts error report relevant context data from request."
|
|
[request]
|
|
(let [claims (-> {}
|
|
(into (::session/token-claims request))
|
|
(into (::actoken/token-claims request)))]
|
|
{:path (:path request)
|
|
:method (:method request)
|
|
:params (:params request)
|
|
:ip-addr (parse-client-ip request)
|
|
:user-agent (yrq/get-header request "user-agent")
|
|
:profile-id (:uid claims)
|
|
:version (or (yrq/get-header request "x-frontend-version")
|
|
"unknown")}))
|
|
|
|
(defmulti handle-exception
|
|
(fn [err & _rest]
|
|
(let [edata (ex-data err)]
|
|
(or (:type edata)
|
|
(class err)))))
|
|
|
|
(defmethod handle-exception :authentication
|
|
[err _]
|
|
(yrs/response 401 (ex-data err)))
|
|
|
|
(defmethod handle-exception :authorization
|
|
[err _]
|
|
(yrs/response 403 (ex-data err)))
|
|
|
|
(defmethod handle-exception :restriction
|
|
[err _]
|
|
(yrs/response 400 (ex-data err)))
|
|
|
|
(defmethod handle-exception :rate-limit
|
|
[err _]
|
|
(let [headers (-> err ex-data ::http/headers)]
|
|
(yrs/response :status 429 :body "" :headers headers)))
|
|
|
|
(defmethod handle-exception :validation
|
|
[err _]
|
|
(let [{:keys [code] :as data} (ex-data err)]
|
|
(cond
|
|
(= code :spec-validation)
|
|
(let [explain (ex/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]
|
|
(let [edata (ex-data error)
|
|
explain (ex/explain edata)]
|
|
(binding [l/*context* (request->context request)]
|
|
(l/error :hint "Assertion error" :message (ex-message error) :cause error)
|
|
(yrs/response :status 500
|
|
:body {:type :server-error
|
|
:code :assertion
|
|
:data (-> edata
|
|
(dissoc ::s/problems ::s/value ::s/spec)
|
|
(cond-> explain (assoc :explain explain)))}))))
|
|
|
|
(defmethod handle-exception :not-found
|
|
[err _]
|
|
(yrs/response 404 (ex-data err)))
|
|
|
|
(defmethod handle-exception :internal
|
|
[error request]
|
|
(let [{:keys [code] :as edata} (ex-data error)]
|
|
(cond
|
|
(= :concurrency-limit-reached code)
|
|
(yrs/response 429)
|
|
|
|
:else
|
|
(binding [l/*context* (request->context request)]
|
|
(l/error :hint "Internal error" :message (ex-message error) :cause error)
|
|
(yrs/response 500 {:type :server-error
|
|
:code :unhandled
|
|
:hint (ex-message error)
|
|
:data edata})))))
|
|
|
|
(defmethod handle-exception org.postgresql.util.PSQLException
|
|
[error request]
|
|
(let [state (.getSQLState ^java.sql.SQLException error)]
|
|
(binding [l/*context* (request->context request)]
|
|
(l/error :hint "PSQL error" :message (ex-message error) :cause error)
|
|
(cond
|
|
(= state "57014")
|
|
(yrs/response 504 {:type :server-error
|
|
:code :statement-timeout
|
|
:hint (ex-message error)})
|
|
|
|
(= state "25P03")
|
|
(yrs/response 504 {:type :server-error
|
|
:code :idle-in-transaction-timeout
|
|
:hint (ex-message error)})
|
|
|
|
:else
|
|
(yrs/response 500 {:type :server-error
|
|
:code :unexpected
|
|
:hint (ex-message error)
|
|
: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)
|
|
(binding [l/*context* (request->context request)]
|
|
(l/error :hint "Unexpected error" :message (ex-message error) :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
|
|
(binding [l/*context* (request->context request)]
|
|
(l/error :hint "Unhandled error" :message (ex-message error) :cause error)
|
|
(yrs/response 500 {:type :server-error
|
|
:code :unhandled
|
|
:hint (ex-message error)
|
|
:data edata})))))
|
|
|
|
(defn handle
|
|
[cause request]
|
|
(if (or (instance? java.util.concurrent.CompletionException cause)
|
|
(instance? java.util.concurrent.ExecutionException cause))
|
|
(handle-exception (ex-cause cause) request)
|
|
(handle-exception cause request)))
|