forked from mozilla-services/cliquet
/
errors.py
185 lines (154 loc) · 7.26 KB
/
errors.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
import six
from pyramid import httpexceptions
from cliquet import logger
from cliquet.utils import Enum, json, reapply_cors, encode_header
ERRORS = Enum(
MISSING_AUTH_TOKEN=104,
INVALID_AUTH_TOKEN=105,
BADJSON=106,
INVALID_PARAMETERS=107,
MISSING_PARAMETERS=108,
INVALID_POSTED_DATA=109,
INVALID_RESOURCE_ID=110,
MISSING_RESOURCE=111,
MISSING_CONTENT_LENGTH=112,
REQUEST_TOO_LARGE=113,
MODIFIED_MEANWHILE=114,
METHOD_NOT_ALLOWED=115,
VERSION_NOT_AVAILABLE=116,
CLIENT_REACHED_CAPACITY=117,
FORBIDDEN=121,
CONSTRAINT_VIOLATED=122,
UNDEFINED=999,
BACKEND=201,
SERVICE_DEPRECATED=202
)
"""Predefined errors as specified by the protocol.
+-------------+-------+------------------------------------------------+
| status code | errno | description |
+-------------+-------+------------------------------------------------+
| 401 | 104 | Missing Authorization Token |
+-------------+-------+------------------------------------------------+
| 401 | 105 | Invalid Authorization Token |
+-------------+-------+------------------------------------------------+
| 400 | 106 | request body was not valid JSON |
+-------------+-------+------------------------------------------------+
| 400 | 107 | invalid request parameter |
+-------------+-------+------------------------------------------------+
| 400 | 108 | missing request parameter |
+-------------+-------+------------------------------------------------+
| 400 | 109 | invalid posted data |
+-------------+-------+------------------------------------------------+
| 404 | 110 | Invalid Token / id |
+-------------+-------+------------------------------------------------+
| 404 | 111 | Missing Token / id |
+-------------+-------+------------------------------------------------+
| 411 | 112 | Content-Length header was not provided |
+-------------+-------+------------------------------------------------+
| 413 | 113 | Request body too large |
+-------------+-------+------------------------------------------------+
| 412 | 114 | Resource was modified meanwhile |
+-------------+-------+------------------------------------------------+
| 405 | 115 | Method not allowed on this end point |
+-------------+-------+------------------------------------------------+
| 404 | 116 | Requested version not available on this server |
+-------------+-------+------------------------------------------------+
| 429 | 117 | Client has sent too many requests |
+-------------+-------+------------------------------------------------+
| 403 | 121 | Resource's access forbidden for this user |
+-------------+-------+------------------------------------------------+
| 409 | 122 | Another resource violates constraint |
+-------------+-------+------------------------------------------------+
| 500 | 999 | Internal Server Error |
+-------------+-------+------------------------------------------------+
| 503 | 201 | Service Temporary unavailable due to high load |
+-------------+-------+------------------------------------------------+
| 410 | 202 | Service deprecated |
+-------------+-------+------------------------------------------------+
"""
def http_error(httpexception, errno=None,
code=None, error=None, message=None, info=None, details=None):
"""Return a JSON formated response matching the error protocol.
:param httpexception: Instance of :mod:`~pyramid:pyramid.httpexceptions`
:param errno: stable application-level error number (e.g. 109)
:param code: matches the HTTP status code (e.g 400)
:param error: string description of error type (e.g. "Bad request")
:param message: context information (e.g. "Invalid request parameters")
:param info: information about error (e.g. URL to troubleshooting)
:param details: additional structured details (conflicting record)
:returns: the formatted response object
:rtype: pyramid.httpexceptions.HTTPException
"""
errno = errno or ERRORS.UNDEFINED
# Track error number for request summary
logger.bind(errno=errno)
body = {
"code": code or httpexception.code,
"errno": errno,
"error": error or httpexception.title
}
if message is not None:
body['message'] = message
if info is not None:
body['info'] = info
if details is not None:
body['details'] = details
response = httpexception
response.body = json.dumps(body).encode("utf-8")
response.content_type = 'application/json'
return response
def json_error_handler(errors):
"""Cornice JSON error handler, returning consistant JSON formatted errors
from schema validation errors.
This is meant to be used is custom services in your applications.
.. code-block:: python
upload = Service(name="upload", path='/upload',
error_handler=errors.json_error_handler)
.. warning::
Only the first error of the list is formatted in the response.
(c.f. protocol).
"""
assert len(errors) != 0
sorted_errors = sorted(errors, key=lambda x: six.text_type(x['name']))
error = sorted_errors[0]
name = error['name']
description = error['description']
if name is not None:
if name in description:
message = description
else:
message = '%(name)s in %(location)s: %(description)s' % error
else:
message = '%(location)s: %(description)s' % error
response = http_error(httpexceptions.HTTPBadRequest(),
errno=ERRORS.INVALID_PARAMETERS,
error='Invalid parameters',
message=message,
details=errors)
response.status = errors.status
response = reapply_cors(errors.request, response)
return response
def raise_invalid(request, location='body', name=None, description=None,
**kwargs):
"""Helper to raise a validation error.
:param location: location in request (e.g. ``'querystring'``)
:param name: field name
:param description: detailed description of validation error
:raises: :class:`~pyramid:pyramid.httpexceptions.HTTPBadRequest`
"""
request.errors.add(location, name, description, **kwargs)
response = json_error_handler(request.errors)
raise response
def send_alert(request, message=None, url=None, code='soft-eol'):
"""Helper to add an Alert header to the response.
:param code: The type of error 'soft-eol', 'hard-eol'
:param message: The description message.
:param url: The URL for more information, default to the documentation url.
"""
if url is None:
url = request.registry.settings['cliquet.project_docs']
request.response.headers['Alert'] = encode_header(json.dumps({
'code': code,
'message': message,
'url': url
}))