Passed
Push — develop ( 591d1d...018914 )
by
unknown
01:26
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 {'message': 'this operation violates the data integrity and could not be executed '}
83
84
85
@view_config(context=Exception, renderer='json')
86
def failed(exc, request):  # pragma no cover
87
    """
88
    View invoked when bad data was submitted to Atramhasis.
89
    """
90
    log.error(exc, exc_info=sys.exc_info())
91
    request.response.status_int = 500
92
    return {'message': 'unexpected server error'}
93
94
95
@view_config(context=HTTPMethodNotAllowed, renderer='json')
96
def failed_not_method_not_allowed(exc, request):
97
    """
98
    View invoked when a method is not allowed.
99
    """
100
    log.debug(exc.explanation)
101
    request.response.status_int = 405
102
    return {'message': exc.explanation}
103
104
105
@view_config(context=RequestValidationError, renderer="json")
106
@view_config(context=ResponseValidationError, renderer="json")
107
def failed_openapi_validation(exc, request):
108
    try:
109
        errors = [
110
            _handle_validation_error(validation_error)
111
            for error in exc.errors
112
            if isinstance(error, InvalidSchemaValue)
113
            for validation_error in error.schema_errors
114
        ]
115
        # noinspection PyTypeChecker
116
        errors.extend(
117
            [
118
                str(error)
119
                for error in exc.errors
120
                if not isinstance(error, InvalidSchemaValue)
121
            ]
122
        )
123
        request.response.status_int = 400
124
        if isinstance(exc, RequestValidationError):
125
            subject = "Request"
126
        else:
127
            subject = "Response"
128
        return {"message": f"{subject} was not valid for schema.", "errors": errors}
129
    except Exception:
130
        log.exception("Issue with exception handling.")
131
        return openapi_validation_error(exc, request)
132
133
134
def _handle_validation_error(error, path=""):
135
    if error.validator in ("anyOf", "oneOf", "allOf"):
136
        for schema_type in ("anyOf", "oneOf", "allOf"):
137
            if schema_type not in error.schema:
138
                continue
139
            schemas = error.schema.get(schema_type)
140
            break
141
        else:
142
            return None
143
144
        response = []
145
        for i, schema in enumerate(schemas):
146
            schema.pop("x-scope", None)
147
            errors = [
148
                sub_error
149
                for sub_error in error.context
150
                if sub_error.relative_schema_path[0] == i
151
            ]
152
            if error.path:
153
                schema = {".".join(str(p) for p in error.path): schema}
154
            response.append(
155
                {
156
                    "schema": schema,
157
                    "errors": [_handle_validation_error(error) for error in errors],
158
                }
159
            )
160
        return {schema_type: response}
161
    if path:
162
        path += "."
163
    path += ".".join(str(item) for item in error.path)
164
    if not path:
165
        path = "<root>"
166
    return f"{path}: {error.message}"
167