schema strict validation

This commit is contained in:
Artur Maciag 2017-09-25 15:15:00 +01:00
parent aaab71c94e
commit be7f55d967
3 changed files with 147 additions and 26 deletions

View file

@ -13,9 +13,21 @@ class MissingParameterError(OpenAPIMappingError):
pass pass
class MissingPropertyError(OpenAPIMappingError):
pass
class InvalidContentTypeError(OpenAPIMappingError): class InvalidContentTypeError(OpenAPIMappingError):
pass pass
class InvalidServerError(OpenAPIMappingError): class InvalidServerError(OpenAPIMappingError):
pass pass
class InvalidValueType(OpenAPIMappingError):
pass
class UndefinedSchemaProperty(OpenAPIMappingError):
pass

View file

@ -7,6 +7,9 @@ from functools import lru_cache
from json import loads from json import loads
from six import iteritems from six import iteritems
from openapi_core.exceptions import (
InvalidValueType, UndefinedSchemaProperty, MissingPropertyError,
)
from openapi_core.models import ModelFactory from openapi_core.models import ModelFactory
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -23,13 +26,14 @@ class Schema(object):
def __init__( def __init__(
self, schema_type, model=None, properties=None, items=None, self, schema_type, model=None, properties=None, items=None,
spec_format=None, required=False): spec_format=None, required=False, default=None):
self.type = schema_type self.type = schema_type
self.model = model self.model = model
self.properties = properties and dict(properties) or {} self.properties = properties and dict(properties) or {}
self.items = items self.items = items
self.format = spec_format self.format = spec_format
self.required = required self.required = required
self.default = default
def __getitem__(self, name): def __getitem__(self, name):
return self.properties[name] return self.properties[name]
@ -57,10 +61,9 @@ class Schema(object):
try: try:
return cast_callable(value) return cast_callable(value)
except ValueError: except ValueError:
log.warning( raise InvalidValueType(
"Failed to cast value of %s to %s", value, self.type, "Failed to cast value of %s to %s", value, self.type,
) )
return value
def unmarshal(self, value): def unmarshal(self, value):
"""Unmarshal parameter from the value.""" """Unmarshal parameter from the value."""
@ -78,9 +81,24 @@ class Schema(object):
if isinstance(value, (str, bytes)): if isinstance(value, (str, bytes)):
value = loads(value) value = loads(value)
properties_keys = self.properties.keys()
value_keys = value.keys()
extra_props = set(value_keys) - set(properties_keys)
if extra_props:
raise UndefinedSchemaProperty(
"Undefined properties in schema: {0}".format(extra_props))
properties = {} properties = {}
for prop_name, prop in iteritems(self.properties): for prop_name, prop in iteritems(self.properties):
prop_value = value.get(prop_name) try:
prop_value = value[prop_name]
except KeyError:
if prop_name in self.required:
raise MissingPropertyError(
"Missing schema property {0}".format(prop_name))
prop_value = prop.default
properties[prop_name] = prop.unmarshal(prop_value) properties[prop_name] = prop.unmarshal(prop_value)
return ModelFactory().create(properties, name=self.model) return ModelFactory().create(properties, name=self.model)

View file

@ -4,6 +4,7 @@ from six import iteritems
from openapi_core.exceptions import ( from openapi_core.exceptions import (
MissingParameterError, InvalidContentTypeError, InvalidServerError, MissingParameterError, InvalidContentTypeError, InvalidServerError,
InvalidValueType, UndefinedSchemaProperty, MissingPropertyError,
) )
from openapi_core.media_types import MediaType from openapi_core.media_types import MediaType
from openapi_core.operations import Operation from openapi_core.operations import Operation
@ -139,6 +140,25 @@ class TestPetstore(object):
} }
assert body is None assert body is None
def test_get_pets_wrong_parameter_type(self, spec):
host_url = 'http://petstore.swagger.io/v1'
path_pattern = '/v1/pets'
query_params = {
'limit': 'twenty',
}
request = RequestMock(
host_url, 'GET', '/pets',
path_pattern=path_pattern, args=query_params,
)
with pytest.raises(InvalidValueType):
request.get_parameters(spec)
body = request.get_body(spec)
assert body is None
def test_get_pets_raises_missing_required_param(self, spec): def test_get_pets_raises_missing_required_param(self, spec):
host_url = 'http://petstore.swagger.io/v1' host_url = 'http://petstore.swagger.io/v1'
path_pattern = '/v1/pets' path_pattern = '/v1/pets'
@ -154,28 +174,6 @@ class TestPetstore(object):
assert body is None assert body is None
def test_get_pets_failed_to_cast(self, spec):
host_url = 'http://petstore.swagger.io/v1'
path_pattern = '/v1/pets'
query_params = {
'limit': 'non_integer_value',
}
request = RequestMock(
host_url, 'GET', '/pets',
path_pattern=path_pattern, args=query_params,
)
parameters = request.get_parameters(spec)
body = request.get_body(spec)
assert parameters == {
'query': {
'limit': 'non_integer_value',
}
}
assert body is None
def test_get_pets_empty_value(self, spec): def test_get_pets_empty_value(self, spec):
host_url = 'http://petstore.swagger.io/v1' host_url = 'http://petstore.swagger.io/v1'
path_pattern = '/v1/pets' path_pattern = '/v1/pets'
@ -260,6 +258,99 @@ class TestPetstore(object):
assert body.address.street == pet_street assert body.address.street == pet_street
assert body.address.city == pet_city assert body.address.city == pet_city
def test_post_pets_empty_body(self, spec, spec_dict):
host_url = 'http://petstore.swagger.io/v1'
path_pattern = '/v1/pets'
data_json = {}
data = json.dumps(data_json)
request = RequestMock(
host_url, 'POST', '/pets',
path_pattern=path_pattern, data=data,
)
parameters = request.get_parameters(spec)
assert parameters == {}
with pytest.raises(MissingPropertyError):
request.get_body(spec)
def test_post_pets_extra_body_properties(self, spec, spec_dict):
host_url = 'http://petstore.swagger.io/v1'
path_pattern = '/v1/pets'
pet_name = 'Cat'
alias = 'kitty'
data_json = {
'name': pet_name,
'alias': alias,
}
data = json.dumps(data_json)
request = RequestMock(
host_url, 'POST', '/pets',
path_pattern=path_pattern, data=data,
)
parameters = request.get_parameters(spec)
assert parameters == {}
with pytest.raises(UndefinedSchemaProperty):
request.get_body(spec)
def test_post_pets_only_required_body(self, spec, spec_dict):
host_url = 'http://petstore.swagger.io/v1'
path_pattern = '/v1/pets'
pet_name = 'Cat'
data_json = {
'name': pet_name,
}
data = json.dumps(data_json)
request = RequestMock(
host_url, 'POST', '/pets',
path_pattern=path_pattern, data=data,
)
parameters = request.get_parameters(spec)
assert parameters == {}
body = request.get_body(spec)
schemas = spec_dict['components']['schemas']
pet_model = schemas['PetCreate']['x-model']
assert body.__class__.__name__ == pet_model
assert body.name == pet_name
assert body.tag is None
assert body.address is None
def test_get_pets_wrong_body_type(self, spec):
host_url = 'http://petstore.swagger.io/v1'
path_pattern = '/v1/pets'
pet_name = 'Cat'
pet_tag = 'cats'
pet_address = 'address text'
data_json = {
'name': pet_name,
'tag': pet_tag,
'address': pet_address,
}
data = json.dumps(data_json)
request = RequestMock(
host_url, 'POST', '/pets',
path_pattern=path_pattern, data=data,
)
parameters = request.get_parameters(spec)
assert parameters == {}
with pytest.raises(InvalidValueType):
request.get_body(spec)
def test_post_pets_raises_invalid_content_type(self, spec): def test_post_pets_raises_invalid_content_type(self, spec):
host_url = 'http://petstore.swagger.io/v1' host_url = 'http://petstore.swagger.io/v1'
path_pattern = '/v1/pets' path_pattern = '/v1/pets'