Skip to content

Commit e63e02f

Browse files
committed
Validation errors refactor
1 parent dcc431c commit e63e02f

File tree

17 files changed

+215
-122
lines changed

17 files changed

+215
-122
lines changed

openapi_core/contrib/django/handlers.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@
1313
from openapi_core.templating.paths.exceptions import OperationNotFound
1414
from openapi_core.templating.paths.exceptions import PathNotFound
1515
from openapi_core.templating.paths.exceptions import ServerNotFound
16-
from openapi_core.validation.exceptions import InvalidSecurity
17-
from openapi_core.validation.exceptions import MissingRequiredParameter
16+
from openapi_core.templating.security.exceptions import SecurityNotFound
17+
from openapi_core.validation.exceptions import ValidationError
1818

1919

2020
class DjangoOpenAPIErrorsHandler:
2121

2222
OPENAPI_ERROR_STATUS: Dict[Type[Exception], int] = {
23-
MissingRequiredParameter: 400,
23+
ValidationError: 400,
2424
ServerNotFound: 400,
25-
InvalidSecurity: 403,
25+
SecurityNotFound: 403,
2626
OperationNotFound: 405,
2727
PathNotFound: 404,
2828
MediaTypeNotFound: 415,

openapi_core/contrib/falcon/handlers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@
1414
from openapi_core.templating.paths.exceptions import OperationNotFound
1515
from openapi_core.templating.paths.exceptions import PathNotFound
1616
from openapi_core.templating.paths.exceptions import ServerNotFound
17-
from openapi_core.validation.exceptions import InvalidSecurity
18-
from openapi_core.validation.exceptions import MissingRequiredParameter
17+
from openapi_core.templating.security.exceptions import SecurityNotFound
18+
from openapi_core.validation.request.exceptions import MissingRequiredParameter
1919

2020

2121
class FalconOpenAPIErrorsHandler:
2222

2323
OPENAPI_ERROR_STATUS: Dict[Type[Exception], int] = {
2424
MissingRequiredParameter: 400,
2525
ServerNotFound: 400,
26-
InvalidSecurity: 403,
26+
SecurityNotFound: 403,
2727
OperationNotFound: 405,
2828
PathNotFound: 404,
2929
MediaTypeNotFound: 415,

openapi_core/templating/security/__init__.py

Whitespace-only changes.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from dataclasses import dataclass
2+
from typing import List
3+
4+
from openapi_core.exceptions import OpenAPIError
5+
6+
7+
class SecurityFinderError(OpenAPIError):
8+
"""Security finder error"""
9+
10+
11+
@dataclass
12+
class SecurityNotFound(SecurityFinderError):
13+
"""Find security error"""
14+
15+
schemes: List[List[str]]
16+
17+
def __str__(self) -> str:
18+
return f"Security not found. Schemes not valid for any requirement: {str(self.schemes)}"

openapi_core/validation/exceptions.py

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,57 +10,3 @@ class ValidatorDetectError(OpenAPIError):
1010

1111
class ValidationError(OpenAPIError):
1212
pass
13-
14-
15-
@dataclass
16-
class InvalidSecurity(ValidationError):
17-
def __str__(self) -> str:
18-
return "Security not valid for any requirement"
19-
20-
21-
class OpenAPIParameterError(OpenAPIError):
22-
pass
23-
24-
25-
class MissingParameterError(OpenAPIParameterError):
26-
"""Missing parameter error"""
27-
28-
29-
@dataclass
30-
class MissingParameter(MissingParameterError):
31-
name: str
32-
33-
def __str__(self) -> str:
34-
return f"Missing parameter (without default value): {self.name}"
35-
36-
37-
@dataclass
38-
class MissingRequiredParameter(MissingParameterError):
39-
name: str
40-
41-
def __str__(self) -> str:
42-
return f"Missing required parameter: {self.name}"
43-
44-
45-
class OpenAPIHeaderError(OpenAPIError):
46-
pass
47-
48-
49-
class MissingHeaderError(OpenAPIHeaderError):
50-
"""Missing header error"""
51-
52-
53-
@dataclass
54-
class MissingHeader(MissingHeaderError):
55-
name: str
56-
57-
def __str__(self) -> str:
58-
return f"Missing header (without default value): {self.name}"
59-
60-
61-
@dataclass
62-
class MissingRequiredHeader(MissingHeaderError):
63-
name: str
64-
65-
def __str__(self) -> str:
66-
return f"Missing required header: {self.name}"

openapi_core/validation/request/exceptions.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
from dataclasses import dataclass
33
from typing import Iterable
44

5-
from openapi_core.exceptions import OpenAPIError
5+
from openapi_core.unmarshalling.schemas.exceptions import ValidateError
6+
from openapi_core.validation.exceptions import ValidationError
67
from openapi_core.validation.request.datatypes import Parameters
7-
from openapi_core.validation.request.protocols import Request
88

99

1010
@dataclass
@@ -22,11 +22,11 @@ def context(self) -> Iterable[Exception]:
2222
return self.errors
2323

2424

25-
class OpenAPIRequestBodyError(OpenAPIError):
25+
class RequestBodyError(ValidationError):
2626
pass
2727

2828

29-
class MissingRequestBodyError(OpenAPIRequestBodyError):
29+
class MissingRequestBodyError(RequestBodyError):
3030
"""Missing request body error"""
3131

3232

@@ -38,3 +38,31 @@ def __str__(self) -> str:
3838
class MissingRequiredRequestBody(MissingRequestBodyError):
3939
def __str__(self) -> str:
4040
return "Missing required request body"
41+
42+
43+
class RequestBodyValidateError(RequestBodyError, ValidateError):
44+
pass
45+
46+
47+
@dataclass
48+
class ParameterError(ValidationError):
49+
name: str
50+
location: str
51+
52+
53+
class ParameterValidateError(ParameterError, ValidateError):
54+
pass
55+
56+
57+
class MissingParameterError(ParameterError):
58+
"""Missing parameter error"""
59+
60+
61+
class MissingParameter(MissingParameterError):
62+
def __str__(self) -> str:
63+
return f"Missing parameter (without default value): {self.name}"
64+
65+
66+
class MissingRequiredParameter(MissingParameterError):
67+
def __str__(self) -> str:
68+
return f"Missing required parameter: {self.name}"

openapi_core/validation/request/validators.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from openapi_core.templating.paths.exceptions import PathError
3333
from openapi_core.templating.paths.finders import APICallPathFinder
3434
from openapi_core.templating.paths.finders import WebhookPathFinder
35+
from openapi_core.templating.security.exceptions import SecurityNotFound
3536
from openapi_core.unmarshalling.schemas import (
3637
oas30_request_schema_unmarshallers_factory,
3738
)
@@ -44,17 +45,18 @@
4445
SchemaUnmarshallersFactory,
4546
)
4647
from openapi_core.util import chainiters
47-
from openapi_core.validation.exceptions import InvalidSecurity
48-
from openapi_core.validation.exceptions import MissingParameter
49-
from openapi_core.validation.exceptions import MissingRequiredParameter
5048
from openapi_core.validation.request.datatypes import Parameters
5149
from openapi_core.validation.request.datatypes import RequestParameters
5250
from openapi_core.validation.request.datatypes import RequestValidationResult
51+
from openapi_core.validation.request.exceptions import MissingParameter
5352
from openapi_core.validation.request.exceptions import MissingRequestBody
53+
from openapi_core.validation.request.exceptions import MissingRequiredParameter
5454
from openapi_core.validation.request.exceptions import (
5555
MissingRequiredRequestBody,
5656
)
5757
from openapi_core.validation.request.exceptions import ParametersError
58+
from openapi_core.validation.request.exceptions import ParameterValidateError
59+
from openapi_core.validation.request.exceptions import RequestBodyValidateError
5860
from openapi_core.validation.request.protocols import BaseRequest
5961
from openapi_core.validation.request.protocols import Request
6062
from openapi_core.validation.request.protocols import WebhookRequest
@@ -91,7 +93,7 @@ def _validate(
9193
) -> RequestValidationResult:
9294
try:
9395
security = self._get_security(request.parameters, operation)
94-
except InvalidSecurity as exc:
96+
except SecurityNotFound as exc:
9597
return RequestValidationResult(errors=[exc])
9698

9799
try:
@@ -175,7 +177,7 @@ def _validate_security(
175177
) -> RequestValidationResult:
176178
try:
177179
security = self._get_security(request.parameters, operation)
178-
except InvalidSecurity as exc:
180+
except SecurityNotFound as exc:
179181
return RequestValidationResult(errors=[exc])
180182

181183
return RequestValidationResult(
@@ -241,11 +243,13 @@ def _get_parameter(
241243
location = parameters[param_location]
242244
try:
243245
return self._get_param_or_header_value(param, location)
246+
except ValidateError as exc:
247+
raise ParameterValidateError(name, param_location) from exc
244248
except KeyError:
245249
required = param.getkey("required", False)
246250
if required:
247-
raise MissingRequiredParameter(name)
248-
raise MissingParameter(name)
251+
raise MissingRequiredParameter(name, param_location)
252+
raise MissingParameter(name, param_location)
249253

250254
def _get_security(
251255
self, parameters: RequestParameters, operation: Spec
@@ -259,18 +263,21 @@ def _get_security(
259263
if not security:
260264
return {}
261265

266+
schemes = []
262267
for security_requirement in security:
263268
try:
269+
scheme_names = list(security_requirement.keys())
270+
schemes.append(scheme_names)
264271
return {
265272
scheme_name: self._get_security_value(
266273
parameters, scheme_name
267274
)
268-
for scheme_name in list(security_requirement.keys())
275+
for scheme_name in scheme_names
269276
}
270277
except SecurityError:
271278
continue
272279

273-
raise InvalidSecurity
280+
raise SecurityNotFound(schemes)
274281

275282
def _get_security_value(
276283
self, parameters: RequestParameters, scheme_name: str
@@ -301,7 +308,10 @@ def _get_body(
301308
return casted
302309

303310
schema = media_type / "schema"
304-
unmarshalled = self._unmarshal(schema, casted)
311+
try:
312+
unmarshalled = self._unmarshal(schema, casted)
313+
except ValidateError as exc:
314+
raise RequestBodyValidateError() from exc
305315
return unmarshalled
306316

307317
def _get_body_value(self, body: Optional[str], request_body: Spec) -> Any:

openapi_core/validation/response/exceptions.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
from typing import Iterable
55

66
from openapi_core.exceptions import OpenAPIError
7-
from openapi_core.validation.response.protocols import Response
7+
from openapi_core.unmarshalling.schemas.exceptions import ValidateError
8+
from openapi_core.validation.exceptions import ValidationError
89

910

1011
@dataclass
@@ -13,6 +14,38 @@ class HeadersError(Exception):
1314
context: Iterable[OpenAPIError]
1415

1516

17+
@dataclass
18+
class DataError(ValidationError):
19+
pass
20+
21+
22+
class DataValidateError(DataError, ValidateError):
23+
pass
24+
25+
26+
@dataclass
27+
class HeaderError(ValidationError):
28+
name: str
29+
30+
31+
class MissingHeaderError(HeaderError):
32+
"""Missing header error"""
33+
34+
35+
class MissingHeader(MissingHeaderError):
36+
def __str__(self) -> str:
37+
return f"Missing header (without default value): {self.name}"
38+
39+
40+
class MissingRequiredHeader(MissingHeaderError):
41+
def __str__(self) -> str:
42+
return f"Missing required header: {self.name}"
43+
44+
45+
class HeaderValidateError(HeaderError, ValidateError):
46+
pass
47+
48+
1649
class OpenAPIResponseError(OpenAPIError):
1750
pass
1851

openapi_core/validation/response/validators.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@
2727
from openapi_core.unmarshalling.schemas.exceptions import UnmarshalError
2828
from openapi_core.unmarshalling.schemas.exceptions import ValidateError
2929
from openapi_core.util import chainiters
30-
from openapi_core.validation.exceptions import MissingHeader
31-
from openapi_core.validation.exceptions import MissingRequiredHeader
3230
from openapi_core.validation.request.protocols import Request
3331
from openapi_core.validation.request.protocols import WebhookRequest
3432
from openapi_core.validation.response.datatypes import ResponseValidationResult
33+
from openapi_core.validation.response.exceptions import DataValidateError
3534
from openapi_core.validation.response.exceptions import HeadersError
35+
from openapi_core.validation.response.exceptions import HeaderValidateError
36+
from openapi_core.validation.response.exceptions import MissingHeader
37+
from openapi_core.validation.response.exceptions import MissingRequiredHeader
3638
from openapi_core.validation.response.exceptions import MissingResponseContent
3739
from openapi_core.validation.response.protocols import Response
3840
from openapi_core.validation.validators import BaseAPICallValidator
@@ -64,7 +66,6 @@ def _validate(
6466
MissingResponseContent,
6567
DeserializeError,
6668
CastError,
67-
ValidateError,
6869
UnmarshalError,
6970
) as exc:
7071
validated_data = None
@@ -169,7 +170,10 @@ def _get_data(
169170
return casted
170171

171172
schema = media_type / "schema"
172-
data = self._unmarshal(schema, casted)
173+
try:
174+
data = self._unmarshal(schema, casted)
175+
except ValidateError as exc:
176+
raise DataValidateError() from exc
173177

174178
return data
175179

@@ -226,6 +230,8 @@ def _get_header(
226230

227231
try:
228232
return self._get_param_or_header_value(header, headers, name=name)
233+
except ValidateError as exc:
234+
raise HeaderValidateError(name) from exc
229235
except KeyError:
230236
required = header.getkey("required", False)
231237
if required:

0 commit comments

Comments
 (0)