_handle_validation_error()   C
last analyzed

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