failed_not_found()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nop 2
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
"""
2
Module containing error views.
3
"""
4
5
import logging
6
import sys
7
8
from openapi_core.validation.schemas.exceptions import InvalidSchemaValue
9
from pyramid.httpexceptions import HTTPMethodNotAllowed
10
from pyramid.view import notfound_view_config
11
from pyramid.view import view_config
12
from pyramid_openapi3 import RequestValidationError
13
from pyramid_openapi3 import ResponseValidationError
14
from pyramid_openapi3 import extract_errors
15
from pyramid_openapi3 import openapi_validation_error
16
from skosprovider.exceptions import ProviderUnavailableException
17
from sqlalchemy.exc import IntegrityError
18
19
from atramhasis.errors import SkosRegistryNotFoundException
20
from atramhasis.errors import ValidationError
21
from atramhasis.protected_resources import ProtectedResourceException
22
23
24
log = logging.getLogger(__name__)
25
26
27
@notfound_view_config(accept="application/json", renderer="json")
28
@notfound_view_config(accept="text/html", renderer="notfound.jinja2")
29
def failed_not_found(exc, request):
30
    """
31
    View invoked when a resource could not be found.
32
    """
33
    log.debug(exc.explanation)
34
    request.response.status_int = 404
35
    return {"message": exc.explanation}
36
37
38
@view_config(
39
    accept="application/json", context=SkosRegistryNotFoundException, renderer="json"
40
)
41
@view_config(
42
    accept="text/html",
43
    context=SkosRegistryNotFoundException,
44
    renderer="notfound.jinja2",
45
)
46
def failed_skos(exc, request):
47
    """
48
    View invoked when Atramhasis can't find a SKOS registry.
49
    """
50
    log.error(exc.value, exc_info=sys.exc_info())
51
    request.response.status_int = 404
52
    return {"message": exc.value}
53
54
55
@view_config(accept="application/json", context=ValidationError, renderer="json")
56
def failed_validation(exc, request):
57
    """
58
    View invoked when bad data was submitted to Atramhasis.
59
    """
60
    log.debug(f"'message': {exc.value}, 'errors': {exc.errors}")
61
    request.response.status_int = 400
62
    return {"message": exc.value, "errors": exc.errors}
63
64
65
@view_config(
66
    accept="application/json", context=ProtectedResourceException, renderer="json"
67
)
68
def protected(exc, request):
69
    """
70
    when a protected operation is called on a resource that is still referenced
71
    """
72
    log.warning(f"'message': {exc.value}, 'referenced_in': {exc.referenced_in}")
73
    request.response.status_int = 409
74
    return {"message": exc.value, "referenced_in": exc.referenced_in}
75
76
77
@view_config(
78
    accept="application/json", context=ProviderUnavailableException, renderer="json"
79
)
80
def provider_unavailable(exc, request):
81
    """
82
    View invoked when ProviderUnavailableException was raised.
83
    """
84
    log.error(exc, exc_info=sys.exc_info())
85
    request.response.status_int = 503
86
    return {"message": exc.message}
87
88
89
@view_config(accept="application/json", context=IntegrityError, renderer="json")
90
def data_integrity(exc, request):
91
    """
92
    View invoked when IntegrityError was raised.
93
    """
94
    log.warning(exc)
95
    request.response.status_int = 409
96
    return {
97
        "message": "this operation violates the data integrity and could not be executed"
98
    }
99
100
101
@view_config(accept="application/json", context=Exception, renderer="json")
102
@view_config(accept="text/html", context=Exception, renderer="error.jinja2")
103
def failed(exc, request):  # pragma no cover
104
    """
105
    View invoked when bad data was submitted to Atramhasis.
106
    """
107
    log.error(exc, exc_info=sys.exc_info())
108
    request.response.status_int = 500
109
    return {"message": "unexpected server error"}
110
111
112
@view_config(accept="application/json", context=HTTPMethodNotAllowed, renderer="json")
113
def failed_not_method_not_allowed(exc, request):
114
    """
115
    View invoked when a method is not allowed.
116
    """
117
    log.debug(exc.explanation)
118
    request.response.status_int = 405
119
    return {"message": exc.explanation}
120
121
122
@view_config(accept="application/json", context=RequestValidationError, renderer="json")
123
@view_config(
124
    accept="application/json", context=ResponseValidationError, renderer="json"
125
)
126
def failed_openapi_validation(exc, request):
127
    try:
128
        errors = [
129
            _handle_validation_error(validation_error)
130
            for error in exc.errors
131
            if isinstance(error, InvalidSchemaValue)
132
            for validation_error in error.schema_errors
133
        ]
134
        # noinspection PyTypeChecker
135
        errors.extend(
136
            [
137
                f'{error.get("field")}: {error.get("message")}'
138
                for error in list(extract_errors(request, exc.errors))
139
            ]
140
        )
141
        request.response.status_int = 400
142
        return {"message": "Request was not valid for schema.", "errors": errors}
143
    except Exception:
144
        log.exception("Issue with exception handling.")
145
        return openapi_validation_error(exc, request)
146
147
148
def _handle_validation_error(error, path=""):
149
    if error.validator in ("anyOf", "oneOf", "allOf"):
150
        for schema_type in ("anyOf", "oneOf", "allOf"):
151
            if schema_type not in error.schema:
152
                continue
153
            schemas = error.schema.get(schema_type)
154
            break
155
        else:
156
            return None
157
158
        response = []
159
        for i, schema in enumerate(schemas):
160
            schema.pop("x-scope", None)
161
            errors = [
162
                sub_error
163
                for sub_error in error.context
164
                if sub_error.relative_schema_path[0] == i
165
            ]
166
            if error.path:
167
                schema = {".".join(str(p) for p in error.path): schema}
168
            response.append(
169
                {
170
                    "schema": schema,
171
                    "errors": [_handle_validation_error(error) for error in errors],
172
                }
173
            )
174
        return {schema_type: response}
175
    if path:
176
        path += "."
177
    path += ".".join(str(item) for item in error.path)
178
    if not path:
179
        path = "<root>"
180
    return f"{path}: {error.message}"
181