forked from fedora-copr/copr
-
Notifications
You must be signed in to change notification settings - Fork 0
/
error_handlers.py
152 lines (131 loc) · 5 KB
/
error_handlers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
"""
A place for exception-handling logic
"""
import flask
from sqlalchemy.exc import SQLAlchemyError
from werkzeug.exceptions import (
ClientDisconnected,
GatewayTimeout,
HTTPException,
NotFound,
)
from coprs import app
from coprs.exceptions import CoprHttpException
def get_error_handler():
"""
Determine what error handler should be used for this request
See http://flask.pocoo.org/docs/1.0/blueprints/#error-handlers
"""
path = flask.request.path
if path.startswith('/api_3') and "gssapi_login/web-ui" not in path:
return APIErrorHandler()
return UIErrorHandler()
class BaseErrorHandler:
"""
Do not use this class for handling errors. It is only a parent class for
the actual error-handler classes.
"""
def handle_error(self, error):
"""
Return a flask response suitable for the current situation (e.g. reder
HTML page for UI failures, send JSON back to API client, etc).
"""
code = self.code(error)
message = self.message(error)
headers = getattr(error, "headers", None)
message = self.override_message(message, error)
app.logger.error("Response error: %s %s", code, message)
return self.render(message, code), code, headers
@staticmethod
def render(message, code):
"""
Using the message and code, generate the appropriate error page content.
"""
raise NotImplementedError
def override_message(self, message, error):
"""
For particular sub-class, it may be desired to override/reformat the
message for particular error. This method should be overridden then.
"""
_used_in_subclass = self, error
return message
@staticmethod
def code(error):
"""
Return status code for a given exception
"""
code = getattr(error, "code", 500)
return code if code is not None else 500
def message(self, error):
"""
Return an error message for a given exception. We want to obtain messages
differently for `CoprHttpException`, `HTTPException`, or others.
"""
message = "Unknown problem"
# Every `CoprHttpException` and `HTTPException` failure has a valuable
# message for the end user. It holds information that e.g. some value
# is missing or incorrect, something cannot be done, something doesn't
# exist.
if isinstance(error, HTTPException):
message = error.description
if isinstance(error, ClientDisconnected):
message = "Client disconnected: " + message
return message
if isinstance(error, CoprHttpException):
return str(error)
# Everything else would normally be an uncaught exception caused by
# either not properly running all frontend requirements (PostgreSQL,
# Redis), or having a bug in the code. For such cases we try to do
# our best to identify the failure reason (but we stay with
# error_code=500).
message = ("Request wasn't successful, "
"there is probably a bug in the Copr code.")
if isinstance(error, SQLAlchemyError):
message = "Database error, contact admin"
self._log_admin_only_exception()
return message
def _log_admin_only_exception(self):
# pylint: disable=no-self-use
app.logger.exception("Admin-only exception\nRequest: %s %s\nUser: %s\n",
flask.request.method,
flask.request.url,
flask.g.user.name if flask.g.user else None)
class UIErrorHandler(BaseErrorHandler):
"""
Handle exceptions raised from the web user interface
"""
@staticmethod
def render(message, code):
title = {
400: "Bad Request",
409: "Conflict",
403: "Access Restricted",
404: "Page Not Found",
422: "Unprocessable Entity",
}.get(code, "Unknown error")
return flask.render_template("html-error.html",
message=message,
error_code=code,
error_title=title)
class APIErrorHandler(BaseErrorHandler):
"""
Handle exceptions raised from API (v3) - routes without flask-restx
"""
def override_message(self, message, error):
return {
NotFound: "Such API endpoint doesn't exist",
GatewayTimeout: "The API request timeouted",
}.get(error.__class__, message)
@staticmethod
def render(message, code):
return flask.jsonify(error=message)
class RestXErrorHandler(APIErrorHandler):
"""
Handle exceptions raised from API (v3) - routes that use flask-restx
"""
@staticmethod
def render(message, code):
return {"error": message}
def handle_error(self, error):
body, code, headers = super().handle_error(error)
return body, code, headers or {}