mirror of
https://github.com/correl/openapi-core.git
synced 2024-11-25 19:19:55 +00:00
schema strict validation
This commit is contained in:
parent
aaab71c94e
commit
be7f55d967
3 changed files with 147 additions and 26 deletions
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
Loading…
Reference in a new issue