Skip to content

Parameters on path item object support #144

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions openapi_core/schema/paths/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import attr

from openapi_core.schema.exceptions import OpenAPIMappingError


class OpenAPIPathError(OpenAPIMappingError):
pass


@attr.s(hash=True)
class InvalidPath(OpenAPIPathError):
path_pattern = attr.ib()

def __str__(self):
return "Unknown path {0}".format(self.path_pattern)
11 changes: 9 additions & 2 deletions openapi_core/schema/specs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from openapi_core.compat import partialmethod
from openapi_core.schema.operations.exceptions import InvalidOperation
from openapi_core.schema.paths.exceptions import InvalidPath
from openapi_core.schema.servers.exceptions import InvalidServer


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

def __getitem__(self, path_name):
return self.paths[path_name]
def __getitem__(self, path_pattern):
return self.get_path(path_pattern)

@property
def default_url(self):
Expand All @@ -36,6 +37,12 @@ def get_server(self, full_url_pattern):
def get_server_url(self, index=0):
return self.servers[index].default_url

def get_path(self, path_pattern):
try:
return self.paths[path_pattern]
except KeyError:
raise InvalidPath(path_pattern)

def get_operation(self, path_pattern, http_method):
try:
return self.paths[path_pattern].operations[http_method]
Expand Down
10 changes: 10 additions & 0 deletions openapi_core/validation/request/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ def __getitem__(self, location):
def __setitem__(self, location, value):
raise NotImplementedError

def __add__(self, other):
if not isinstance(other, self.__class__):
raise ValueError("Invalid type")

for location in self.valid_locations:
if location in other:
self[location].update(other[location])

return self

@classmethod
def validate_location(cls, location):
if location not in cls.valid_locations:
Expand Down
14 changes: 11 additions & 3 deletions openapi_core/validation/request/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,26 @@ def validate(self, request):
server.default_url, request.full_url_pattern
)

try:
path = self.spec[operation_pattern]
# don't process if operation errors
except OpenAPIMappingError as exc:
return RequestValidationResult([exc, ], None, None)

path_params, path_params_errors = self._get_parameters(request, path)

try:
operation = self.spec.get_operation(
operation_pattern, request.method)
# don't process if operation errors
except OpenAPIMappingError as exc:
return RequestValidationResult([exc, ], None, None)

params, params_errors = self._get_parameters(request, operation)
op_params, op_params_errors = self._get_parameters(request, operation)
body, body_errors = self._get_body(request, operation)

errors = params_errors + body_errors
return RequestValidationResult(errors, body, params)
errors = path_params_errors + op_params_errors + body_errors
return RequestValidationResult(errors, body, path_params + op_params)

def _get_parameters(self, request, operation):
errors = []
Expand Down
18 changes: 17 additions & 1 deletion tests/integration/test_minimal.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest

from openapi_core.schema.operations.exceptions import InvalidOperation
from openapi_core.schema.paths.exceptions import InvalidPath
from openapi_core.shortcuts import create_spec
from openapi_core.validation.request.validators import RequestValidator
from openapi_core.wrappers.mock import MockRequest
Expand Down Expand Up @@ -39,11 +40,26 @@ def test_invalid_operation(self, factory, server, spec_path):
spec_dict = factory.spec_from_file(spec_path)
spec = create_spec(spec_dict)
validator = RequestValidator(spec)
request = MockRequest(server, "get", "/nonexistent")
request = MockRequest(server, "post", "/status")

result = validator.validate(request)

assert len(result.errors) == 1
assert isinstance(result.errors[0], InvalidOperation)
assert result.body is None
assert result.parameters == {}

@pytest.mark.parametrize("server", servers)
@pytest.mark.parametrize("spec_path", spec_paths)
def test_invalid_path(self, factory, server, spec_path):
spec_dict = factory.spec_from_file(spec_path)
spec = create_spec(spec_dict)
validator = RequestValidator(spec)
request = MockRequest(server, "get", "/nonexistent")

result = validator.validate(request)

assert len(result.errors) == 1
assert isinstance(result.errors[0], InvalidPath)
assert result.body is None
assert result.parameters == {}
88 changes: 87 additions & 1 deletion tests/integration/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from openapi_core.extensions.models.models import BaseModel
from openapi_core.schema.operations.exceptions import InvalidOperation
from openapi_core.schema.parameters.exceptions import MissingRequiredParameter
from openapi_core.schema.parameters.exceptions import InvalidParameterValue
from openapi_core.schema.paths.exceptions import InvalidPath
from openapi_core.schema.request_bodies.exceptions import MissingRequestBody
from openapi_core.schema.responses.exceptions import (
MissingResponseContent, InvalidResponse,
Expand Down Expand Up @@ -54,11 +56,21 @@ def test_request_server_error(self, validator):
assert result.body is None
assert result.parameters == {}

def test_invalid_operation(self, validator):
def test_invalid_path(self, validator):
request = MockRequest(self.host_url, 'get', '/v1')

result = validator.validate(request)

assert len(result.errors) == 1
assert type(result.errors[0]) == InvalidPath
assert result.body is None
assert result.parameters == {}

def test_invalid_operation(self, validator):
request = MockRequest(self.host_url, 'patch', '/v1/pets')

result = validator.validate(request)

assert len(result.errors) == 1
assert type(result.errors[0]) == InvalidOperation
assert result.body is None
Expand Down Expand Up @@ -220,6 +232,80 @@ def test_get_pet(self, validator):
}


class TestPathItemParamsValidator(object):

@pytest.fixture
def spec_dict(self, factory):
return {
"openapi": "3.0.0",
"info": {
"title": "Test path item parameter validation",
"version": "0.1",
},
"paths": {
"/resource": {
"parameters": [
{
"name": "resId",
"in": "query",
"required": True,
"schema": {
"type": "integer",
},
},
],
"get": {
"responses": {
"default": {
"description": "Return the resource."
}
}
}
}
}
}

@pytest.fixture
def spec(self, spec_dict):
return create_spec(spec_dict)

@pytest.fixture
def validator(self, spec):
return RequestValidator(spec)

def test_request_missing_param(self, validator):
request = MockRequest('http://example.com', 'get', '/resource')
result = validator.validate(request)

assert len(result.errors) == 1
assert type(result.errors[0]) == MissingRequiredParameter
assert result.body is None
assert result.parameters == {}

def test_request_invalid_param(self, validator):
request = MockRequest(
'http://example.com', 'get', '/resource',
args={'resId': 'invalid'},
)
result = validator.validate(request)

assert len(result.errors) == 1
assert type(result.errors[0]) == InvalidParameterValue
assert result.body is None
assert result.parameters == {}

def test_request_valid_param(self, validator):
request = MockRequest(
'http://example.com', 'get', '/resource',
args={'resId': '10'},
)
result = validator.validate(request)

assert len(result.errors) == 0
assert result.body is None
assert result.parameters == {'query': {'resId': 10}}


class TestResponseValidator(object):

host_url = 'http://petstore.swagger.io'
Expand Down