Skip to content

Commit ff5f018

Browse files
authored
Merge pull request #144 from p1c2u/fix/parameters-on-path-item-object
Parameters on path item object support
2 parents 83456ee + e8d98df commit ff5f018

File tree

6 files changed

+149
-7
lines changed

6 files changed

+149
-7
lines changed
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import attr
2+
3+
from openapi_core.schema.exceptions import OpenAPIMappingError
4+
5+
6+
class OpenAPIPathError(OpenAPIMappingError):
7+
pass
8+
9+
10+
@attr.s(hash=True)
11+
class InvalidPath(OpenAPIPathError):
12+
path_pattern = attr.ib()
13+
14+
def __str__(self):
15+
return "Unknown path {0}".format(self.path_pattern)

openapi_core/schema/specs/models.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from openapi_core.compat import partialmethod
66
from openapi_core.schema.operations.exceptions import InvalidOperation
7+
from openapi_core.schema.paths.exceptions import InvalidPath
78
from openapi_core.schema.servers.exceptions import InvalidServer
89

910

@@ -19,8 +20,8 @@ def __init__(self, info, paths, servers=None, components=None):
1920
self.servers = servers or []
2021
self.components = components
2122

22-
def __getitem__(self, path_name):
23-
return self.paths[path_name]
23+
def __getitem__(self, path_pattern):
24+
return self.get_path(path_pattern)
2425

2526
@property
2627
def default_url(self):
@@ -36,6 +37,12 @@ def get_server(self, full_url_pattern):
3637
def get_server_url(self, index=0):
3738
return self.servers[index].default_url
3839

40+
def get_path(self, path_pattern):
41+
try:
42+
return self.paths[path_pattern]
43+
except KeyError:
44+
raise InvalidPath(path_pattern)
45+
3946
def get_operation(self, path_pattern, http_method):
4047
try:
4148
return self.paths[path_pattern].operations[http_method]

openapi_core/validation/request/models.py

+10
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ def __getitem__(self, location):
1616
def __setitem__(self, location, value):
1717
raise NotImplementedError
1818

19+
def __add__(self, other):
20+
if not isinstance(other, self.__class__):
21+
raise ValueError("Invalid type")
22+
23+
for location in self.valid_locations:
24+
if location in other:
25+
self[location].update(other[location])
26+
27+
return self
28+
1929
@classmethod
2030
def validate_location(cls, location):
2131
if location not in cls.valid_locations:

openapi_core/validation/request/validators.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,26 @@ def validate(self, request):
2626
server.default_url, request.full_url_pattern
2727
)
2828

29+
try:
30+
path = self.spec[operation_pattern]
31+
# don't process if operation errors
32+
except OpenAPIMappingError as exc:
33+
return RequestValidationResult([exc, ], None, None)
34+
35+
path_params, path_params_errors = self._get_parameters(request, path)
36+
2937
try:
3038
operation = self.spec.get_operation(
3139
operation_pattern, request.method)
3240
# don't process if operation errors
3341
except OpenAPIMappingError as exc:
3442
return RequestValidationResult([exc, ], None, None)
3543

36-
params, params_errors = self._get_parameters(request, operation)
44+
op_params, op_params_errors = self._get_parameters(request, operation)
3745
body, body_errors = self._get_body(request, operation)
3846

39-
errors = params_errors + body_errors
40-
return RequestValidationResult(errors, body, params)
47+
errors = path_params_errors + op_params_errors + body_errors
48+
return RequestValidationResult(errors, body, path_params + op_params)
4149

4250
def _get_parameters(self, request, operation):
4351
errors = []

tests/integration/test_minimal.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22

33
from openapi_core.schema.operations.exceptions import InvalidOperation
4+
from openapi_core.schema.paths.exceptions import InvalidPath
45
from openapi_core.shortcuts import create_spec
56
from openapi_core.validation.request.validators import RequestValidator
67
from openapi_core.wrappers.mock import MockRequest
@@ -39,11 +40,26 @@ def test_invalid_operation(self, factory, server, spec_path):
3940
spec_dict = factory.spec_from_file(spec_path)
4041
spec = create_spec(spec_dict)
4142
validator = RequestValidator(spec)
42-
request = MockRequest(server, "get", "/nonexistent")
43+
request = MockRequest(server, "post", "/status")
4344

4445
result = validator.validate(request)
4546

4647
assert len(result.errors) == 1
4748
assert isinstance(result.errors[0], InvalidOperation)
4849
assert result.body is None
4950
assert result.parameters == {}
51+
52+
@pytest.mark.parametrize("server", servers)
53+
@pytest.mark.parametrize("spec_path", spec_paths)
54+
def test_invalid_path(self, factory, server, spec_path):
55+
spec_dict = factory.spec_from_file(spec_path)
56+
spec = create_spec(spec_dict)
57+
validator = RequestValidator(spec)
58+
request = MockRequest(server, "get", "/nonexistent")
59+
60+
result = validator.validate(request)
61+
62+
assert len(result.errors) == 1
63+
assert isinstance(result.errors[0], InvalidPath)
64+
assert result.body is None
65+
assert result.parameters == {}

tests/integration/test_validators.py

+87-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
from openapi_core.extensions.models.models import BaseModel
1010
from openapi_core.schema.operations.exceptions import InvalidOperation
1111
from openapi_core.schema.parameters.exceptions import MissingRequiredParameter
12+
from openapi_core.schema.parameters.exceptions import InvalidParameterValue
13+
from openapi_core.schema.paths.exceptions import InvalidPath
1214
from openapi_core.schema.request_bodies.exceptions import MissingRequestBody
1315
from openapi_core.schema.responses.exceptions import (
1416
MissingResponseContent, InvalidResponse,
@@ -54,11 +56,21 @@ def test_request_server_error(self, validator):
5456
assert result.body is None
5557
assert result.parameters == {}
5658

57-
def test_invalid_operation(self, validator):
59+
def test_invalid_path(self, validator):
5860
request = MockRequest(self.host_url, 'get', '/v1')
5961

6062
result = validator.validate(request)
6163

64+
assert len(result.errors) == 1
65+
assert type(result.errors[0]) == InvalidPath
66+
assert result.body is None
67+
assert result.parameters == {}
68+
69+
def test_invalid_operation(self, validator):
70+
request = MockRequest(self.host_url, 'patch', '/v1/pets')
71+
72+
result = validator.validate(request)
73+
6274
assert len(result.errors) == 1
6375
assert type(result.errors[0]) == InvalidOperation
6476
assert result.body is None
@@ -220,6 +232,80 @@ def test_get_pet(self, validator):
220232
}
221233

222234

235+
class TestPathItemParamsValidator(object):
236+
237+
@pytest.fixture
238+
def spec_dict(self, factory):
239+
return {
240+
"openapi": "3.0.0",
241+
"info": {
242+
"title": "Test path item parameter validation",
243+
"version": "0.1",
244+
},
245+
"paths": {
246+
"/resource": {
247+
"parameters": [
248+
{
249+
"name": "resId",
250+
"in": "query",
251+
"required": True,
252+
"schema": {
253+
"type": "integer",
254+
},
255+
},
256+
],
257+
"get": {
258+
"responses": {
259+
"default": {
260+
"description": "Return the resource."
261+
}
262+
}
263+
}
264+
}
265+
}
266+
}
267+
268+
@pytest.fixture
269+
def spec(self, spec_dict):
270+
return create_spec(spec_dict)
271+
272+
@pytest.fixture
273+
def validator(self, spec):
274+
return RequestValidator(spec)
275+
276+
def test_request_missing_param(self, validator):
277+
request = MockRequest('http://example.com', 'get', '/resource')
278+
result = validator.validate(request)
279+
280+
assert len(result.errors) == 1
281+
assert type(result.errors[0]) == MissingRequiredParameter
282+
assert result.body is None
283+
assert result.parameters == {}
284+
285+
def test_request_invalid_param(self, validator):
286+
request = MockRequest(
287+
'http://example.com', 'get', '/resource',
288+
args={'resId': 'invalid'},
289+
)
290+
result = validator.validate(request)
291+
292+
assert len(result.errors) == 1
293+
assert type(result.errors[0]) == InvalidParameterValue
294+
assert result.body is None
295+
assert result.parameters == {}
296+
297+
def test_request_valid_param(self, validator):
298+
request = MockRequest(
299+
'http://example.com', 'get', '/resource',
300+
args={'resId': '10'},
301+
)
302+
result = validator.validate(request)
303+
304+
assert len(result.errors) == 0
305+
assert result.body is None
306+
assert result.parameters == {'query': {'resId': 10}}
307+
308+
223309
class TestResponseValidator(object):
224310

225311
host_url = 'http://petstore.swagger.io'

0 commit comments

Comments
 (0)