Skip to content

Commit 7139643

Browse files
committed
Finder exception
1 parent 6d71cad commit 7139643

File tree

13 files changed

+111
-39
lines changed

13 files changed

+111
-39
lines changed

openapi_core/contrib/flask/handlers.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
from flask.json import dumps
44

55
from openapi_core.schema.media_types.exceptions import InvalidContentType
6+
from openapi_core.templating.paths.exceptions import (
7+
OperationNotFound, PathNotFound,
8+
)
69
from openapi_core.schema.servers.exceptions import InvalidServer
710

811

912
class FlaskOpenAPIErrorsHandler(object):
1013

1114
OPENAPI_ERROR_STATUS = {
12-
InvalidServer: 500,
15+
OperationNotFound: 500,
16+
PathNotFound: 500,
1317
InvalidContentType: 415,
1418
}
1519

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import attr
2+
3+
from openapi_core.exceptions import OpenAPIError
4+
5+
6+
class PathError(OpenAPIError):
7+
"""Path error"""
8+
9+
10+
@attr.s(hash=True)
11+
class PathNotFound(PathError):
12+
"""Find path error"""
13+
path_pattern = attr.ib()
14+
15+
def __str__(self):
16+
return "Path not found for {0}".format(self.path_pattern)
17+
18+
19+
@attr.s(hash=True)
20+
class OperationNotFound(PathError):
21+
"""Find path operation error"""
22+
path_pattern = attr.ib()
23+
operation = attr.ib()
24+
25+
def __str__(self):
26+
return "Operation {0} not found for {0}".format(
27+
self.operation, self.path_pattern)

openapi_core/templating/paths/finders.py

+21-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
from parse import parse
22
from six.moves.urllib.parse import urljoin
33

4-
from openapi_core.templating.paths.exceptions import PathNotFound
4+
from openapi_core.schema.operations.exceptions import InvalidOperation
5+
from openapi_core.schema.paths.exceptions import InvalidPath
6+
from openapi_core.schema.servers.exceptions import InvalidServer
7+
from openapi_core.templating.paths.exceptions import (
8+
PathNotFound, OperationNotFound,
9+
)
510
from openapi_core.templating.paths.util import get_operation_pattern
611

712

@@ -11,16 +16,21 @@ def __init__(self, spec):
1116
self.spec = spec
1217

1318
def find(self, request):
14-
operation_pattern = self._get_operation_pattern(request)
15-
16-
path = self.spec[operation_pattern]
17-
path_variables = {}
18-
operation = self.spec.get_operation(operation_pattern, request.method)
19-
servers = path.servers or operation.servers or self.spec.servers
20-
server = servers[0]
21-
server_variables = {}
22-
23-
return path, operation, server, path_variables, server_variables
19+
try:
20+
operation_pattern = self._get_operation_pattern(request)
21+
22+
path = self.spec[operation_pattern]
23+
path_variables = {}
24+
operation = self.spec.get_operation(operation_pattern, request.method)
25+
servers = path.servers or operation.servers or self.spec.servers
26+
server = servers[0]
27+
server_variables = {}
28+
except (InvalidServer, InvalidPath):
29+
raise PathNotFound(request.full_url_pattern)
30+
except InvalidOperation:
31+
raise OperationNotFound(request.full_url_pattern, request.method)
32+
else:
33+
return path, operation, server, path_variables, server_variables
2434

2535
def _get_operation_pattern(self, request):
2636
server = self.spec.get_server(request.full_url_pattern)
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from parse import parse
2+
from six.moves.urllib.parse import urljoin
3+
4+
from openapi_core.templating.paths.exceptions import PathNotFound
5+
6+
7+
class PathFinder(object):
8+
9+
def __init__(self, spec):
10+
self.spec = spec
11+
12+
def find(self, request):
13+
for path in self.spec.paths:
14+
if request.method not in path.operations:
15+
continue
16+
operation = path.operations[request.method]
17+
servers = path.servers or operations.servers or self.spec.servers
18+
for server in servers:
19+
path_pattern = urljoin(server.url, path.name)
20+
if request.path_pattern == path_pattern:
21+
return path, operation, server, {}
22+
parsed = parse(path_pattern, request.path_pattern)
23+
if parsed:
24+
return path, operation, server, parsed.named
25+
26+
raise PathNotFound(request.path_pattern)

openapi_core/validation/request/validators.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from openapi_core.schema.request_bodies.exceptions import MissingRequestBody
1414
from openapi_core.schema.servers.exceptions import InvalidServer
1515
from openapi_core.security.exceptions import SecurityError
16+
from openapi_core.templating.paths.exceptions import PathError
1617
from openapi_core.unmarshalling.schemas.enums import UnmarshalContext
1718
from openapi_core.unmarshalling.schemas.exceptions import (
1819
UnmarshalError, ValidateError,
@@ -30,7 +31,7 @@ def validate(self, request):
3031
try:
3132
path, operation, _, _, _ = self._find_path(request)
3233
# don't process if operation errors
33-
except (InvalidServer, InvalidPath, InvalidOperation) as exc:
34+
except PathError as exc:
3435
return RequestValidationResult([exc, ], None, None, None)
3536

3637
try:
@@ -53,7 +54,7 @@ def validate(self, request):
5354
def _validate_parameters(self, request):
5455
try:
5556
path, operation, _, _, _ = self._find_path(request)
56-
except (InvalidServer, InvalidPath, InvalidOperation) as exc:
57+
except PathError as exc:
5758
return RequestValidationResult([exc, ], None, None)
5859

5960
params, params_errors = self._get_parameters(
@@ -67,7 +68,7 @@ def _validate_parameters(self, request):
6768
def _validate_body(self, request):
6869
try:
6970
_, operation, _, _, _ = self._find_path(request)
70-
except (InvalidServer, InvalidOperation) as exc:
71+
except PathError as exc:
7172
return RequestValidationResult([exc, ], None, None)
7273

7374
body, body_errors = self._get_body(request, operation)

openapi_core/validation/response/validators.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
InvalidResponse, MissingResponseContent,
99
)
1010
from openapi_core.schema.servers.exceptions import InvalidServer
11+
from openapi_core.templating.paths.exceptions import PathError
1112
from openapi_core.unmarshalling.schemas.enums import UnmarshalContext
1213
from openapi_core.unmarshalling.schemas.exceptions import (
1314
UnmarshalError, ValidateError,
@@ -22,7 +23,7 @@ def validate(self, request, response):
2223
try:
2324
_, operation, _, _, _ = self._find_path(request)
2425
# don't process if operation errors
25-
except (InvalidServer, InvalidPath, InvalidOperation) as exc:
26+
except PathError as exc:
2627
return ResponseValidationResult([exc, ], None, None)
2728

2829
try:
@@ -47,7 +48,7 @@ def _validate_data(self, request, response):
4748
try:
4849
_, operation, _, _, _ = self._find_path(request)
4950
# don't process if operation errors
50-
except (InvalidServer, InvalidPath, InvalidOperation) as exc:
51+
except PathError as exc:
5152
return ResponseValidationResult([exc, ], None, None)
5253

5354
try:

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ lazy-object-proxy
44
strict_rfc3339
55
isodate
66
attrs
7+
parse==1.14.0

setup.cfg

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ install_requires =
3030
isodate
3131
attrs
3232
werkzeug
33+
parse
3334
backports.functools-lru-cache; python_version<"3.0"
3435
backports.functools-partialmethod; python_version<"3.0"
3536
tests_require =

tests/integration/contrib/flask/test_flask_decorator.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,12 @@ def test_server_error(self, client):
8080
'errors': [
8181
{
8282
'class': (
83-
"<class 'openapi_core.schema.servers.exceptions."
84-
"InvalidServer'>"
83+
"<class 'openapi_core.templating.paths.exceptions."
84+
"PathNotFound'>"
8585
),
8686
'status': 500,
8787
'title': (
88-
'Invalid request server '
88+
'Path not found for '
8989
'https://localhost/browse/{id}/'
9090
),
9191
}

tests/integration/contrib/flask/test_flask_views.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,12 @@ def test_server_error(self, client):
6868
'errors': [
6969
{
7070
'class': (
71-
"<class 'openapi_core.schema.servers.exceptions."
72-
"InvalidServer'>"
71+
"<class 'openapi_core.templating.paths.exceptions."
72+
"PathNotFound'>"
7373
),
7474
'status': 500,
7575
'title': (
76-
'Invalid request server '
76+
'Path not found for '
7777
'https://localhost/browse/{id}/'
7878
),
7979
}

tests/integration/validation/test_minimal.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import pytest
22

3-
from openapi_core.schema.operations.exceptions import InvalidOperation
4-
from openapi_core.schema.paths.exceptions import InvalidPath
53
from openapi_core.shortcuts import create_spec
4+
from openapi_core.templating.paths.exceptions import (
5+
PathNotFound, OperationNotFound,
6+
)
67
from openapi_core.testing import MockRequest
78
from openapi_core.validation.request.validators import RequestValidator
89

@@ -45,7 +46,7 @@ def test_invalid_operation(self, factory, server, spec_path):
4546
result = validator.validate(request)
4647

4748
assert len(result.errors) == 1
48-
assert isinstance(result.errors[0], InvalidOperation)
49+
assert isinstance(result.errors[0], OperationNotFound)
4950
assert result.body is None
5051
assert result.parameters is None
5152

@@ -60,6 +61,6 @@ def test_invalid_path(self, factory, server, spec_path):
6061
result = validator.validate(request)
6162

6263
assert len(result.errors) == 1
63-
assert isinstance(result.errors[0], InvalidPath)
64+
assert isinstance(result.errors[0], PathNotFound)
6465
assert result.body is None
6566
assert result.parameters is None

tests/integration/validation/test_petstore.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616
MissingRequiredParameter,
1717
)
1818
from openapi_core.schema.schemas.enums import SchemaType
19-
from openapi_core.schema.servers.exceptions import InvalidServer
2019
from openapi_core.shortcuts import (
2120
create_spec, validate_parameters, validate_body,
2221
)
22+
from openapi_core.templating.paths.exceptions import PathNotFound
2323
from openapi_core.testing import MockRequest, MockResponse
2424
from openapi_core.unmarshalling.schemas.exceptions import InvalidSchemaValue
2525
from openapi_core.validation.request.datatypes import RequestParameters
@@ -748,10 +748,10 @@ def test_post_pets_raises_invalid_server_error(self, spec):
748748
headers=headers, cookies=cookies,
749749
)
750750

751-
with pytest.raises(InvalidServer):
751+
with pytest.raises(PathNotFound):
752752
validate_parameters(spec, request)
753753

754-
with pytest.raises(InvalidServer):
754+
with pytest.raises(PathNotFound):
755755
validate_body(spec, request)
756756

757757
def test_get_pet(self, spec, response_validator):

tests/integration/validation/test_validators.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
InvalidContentType,
1010
)
1111
from openapi_core.extensions.models.models import BaseModel
12-
from openapi_core.schema.operations.exceptions import InvalidOperation
1312
from openapi_core.schema.parameters.exceptions import MissingRequiredParameter
14-
from openapi_core.schema.paths.exceptions import InvalidPath
1513
from openapi_core.schema.request_bodies.exceptions import MissingRequestBody
1614
from openapi_core.schema.responses.exceptions import (
1715
MissingResponseContent, InvalidResponse,
1816
)
19-
from openapi_core.schema.servers.exceptions import InvalidServer
2017
from openapi_core.shortcuts import create_spec
18+
from openapi_core.templating.paths.exceptions import (
19+
PathNotFound, OperationNotFound,
20+
)
2121
from openapi_core.testing import MockRequest, MockResponse
2222
from openapi_core.unmarshalling.schemas.exceptions import InvalidSchemaValue
2323
from openapi_core.validation.exceptions import InvalidSecurity
@@ -56,7 +56,7 @@ def test_request_server_error(self, validator):
5656
result = validator.validate(request)
5757

5858
assert len(result.errors) == 1
59-
assert type(result.errors[0]) == InvalidServer
59+
assert type(result.errors[0]) == PathNotFound
6060
assert result.body is None
6161
assert result.parameters is None
6262

@@ -66,7 +66,7 @@ def test_invalid_path(self, validator):
6666
result = validator.validate(request)
6767

6868
assert len(result.errors) == 1
69-
assert type(result.errors[0]) == InvalidPath
69+
assert type(result.errors[0]) == PathNotFound
7070
assert result.body is None
7171
assert result.parameters is None
7272

@@ -76,7 +76,7 @@ def test_invalid_operation(self, validator):
7676
result = validator.validate(request)
7777

7878
assert len(result.errors) == 1
79-
assert type(result.errors[0]) == InvalidOperation
79+
assert type(result.errors[0]) == OperationNotFound
8080
assert result.body is None
8181
assert result.parameters is None
8282

@@ -428,18 +428,18 @@ def test_invalid_server(self, validator):
428428
result = validator.validate(request, response)
429429

430430
assert len(result.errors) == 1
431-
assert type(result.errors[0]) == InvalidServer
431+
assert type(result.errors[0]) == PathNotFound
432432
assert result.data is None
433433
assert result.headers is None
434434

435435
def test_invalid_operation(self, validator):
436-
request = MockRequest(self.host_url, 'get', '/v1')
436+
request = MockRequest(self.host_url, 'patch', '/v1/pets')
437437
response = MockResponse('Not Found', status_code=404)
438438

439439
result = validator.validate(request, response)
440440

441441
assert len(result.errors) == 1
442-
assert type(result.errors[0]) == InvalidPath
442+
assert type(result.errors[0]) == OperationNotFound
443443
assert result.data is None
444444
assert result.headers is None
445445

0 commit comments

Comments
 (0)