Passed
Push — master ( c88b19...443080 )
by Koen
02:19
created

_handle_validation_error()   C

Complexity

Conditions 9

Size

Total Lines 33
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

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