mirror of
https://github.com/correl/openapi-core.git
synced 2024-11-25 19:19:55 +00:00
Unmarshal any schema type
This commit is contained in:
parent
05b8a30091
commit
0cbbdb0232
3 changed files with 138 additions and 44 deletions
|
@ -5,6 +5,14 @@ class OpenAPISchemaError(OpenAPIMappingError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NoValidSchema(OpenAPISchemaError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UndefinedItemsSchema(OpenAPISchemaError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidSchemaValue(OpenAPISchemaError):
|
class InvalidSchemaValue(OpenAPISchemaError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,8 @@ from openapi_core.extensions.models.factories import ModelFactory
|
||||||
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
|
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
|
||||||
from openapi_core.schema.schemas.exceptions import (
|
from openapi_core.schema.schemas.exceptions import (
|
||||||
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
|
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
|
||||||
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema,
|
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema, NoValidSchema,
|
||||||
|
UndefinedItemsSchema,
|
||||||
)
|
)
|
||||||
from openapi_core.schema.schemas.util import (
|
from openapi_core.schema.schemas.util import (
|
||||||
forcebool, format_date, format_datetime,
|
forcebool, format_date, format_datetime,
|
||||||
|
@ -26,7 +27,6 @@ class Schema(object):
|
||||||
"""Represents an OpenAPI Schema."""
|
"""Represents an OpenAPI Schema."""
|
||||||
|
|
||||||
DEFAULT_CAST_CALLABLE_GETTER = {
|
DEFAULT_CAST_CALLABLE_GETTER = {
|
||||||
SchemaType.ANY: lambda x: x,
|
|
||||||
SchemaType.INTEGER: int,
|
SchemaType.INTEGER: int,
|
||||||
SchemaType.NUMBER: float,
|
SchemaType.NUMBER: float,
|
||||||
SchemaType.BOOLEAN: forcebool,
|
SchemaType.BOOLEAN: forcebool,
|
||||||
|
@ -47,7 +47,7 @@ class Schema(object):
|
||||||
}
|
}
|
||||||
|
|
||||||
TYPE_VALIDATOR_CALLABLE_GETTER = {
|
TYPE_VALIDATOR_CALLABLE_GETTER = {
|
||||||
None: lambda x: True,
|
SchemaType.ANY: lambda x: x,
|
||||||
SchemaType.BOOLEAN: TypeValidator(bool),
|
SchemaType.BOOLEAN: TypeValidator(bool),
|
||||||
SchemaType.INTEGER: TypeValidator(integer_types, exclude=bool),
|
SchemaType.INTEGER: TypeValidator(integer_types, exclude=bool),
|
||||||
SchemaType.NUMBER: TypeValidator(integer_types, float, exclude=bool),
|
SchemaType.NUMBER: TypeValidator(integer_types, float, exclude=bool),
|
||||||
|
@ -108,7 +108,7 @@ class Schema(object):
|
||||||
|
|
||||||
return dict(
|
return dict(
|
||||||
(prop_name, val)
|
(prop_name, val)
|
||||||
for prop_name, val in all_properties.items()
|
for prop_name, val in iteritems(all_properties)
|
||||||
if prop_name in required
|
if prop_name in required
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -125,6 +125,7 @@ class Schema(object):
|
||||||
mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy()
|
mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy()
|
||||||
mapping.update({
|
mapping.update({
|
||||||
SchemaType.STRING: self._unmarshal_string,
|
SchemaType.STRING: self._unmarshal_string,
|
||||||
|
SchemaType.ANY: self._unmarshal_any,
|
||||||
SchemaType.ARRAY: self._unmarshal_collection,
|
SchemaType.ARRAY: self._unmarshal_collection,
|
||||||
SchemaType.OBJECT: self._unmarshal_object,
|
SchemaType.OBJECT: self._unmarshal_object,
|
||||||
})
|
})
|
||||||
|
@ -154,8 +155,8 @@ class Schema(object):
|
||||||
def unmarshal(self, value):
|
def unmarshal(self, value):
|
||||||
"""Unmarshal parameter from the value."""
|
"""Unmarshal parameter from the value."""
|
||||||
if self.deprecated:
|
if self.deprecated:
|
||||||
warnings.warn(
|
warnings.warn("The schema is deprecated", DeprecationWarning)
|
||||||
"The schema is deprecated", DeprecationWarning)
|
|
||||||
casted = self.cast(value)
|
casted = self.cast(value)
|
||||||
|
|
||||||
if casted is None and not self.required:
|
if casted is None and not self.required:
|
||||||
|
@ -188,7 +189,27 @@ class Schema(object):
|
||||||
value, self.format)
|
value, self.format)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _unmarshal_any(self, value):
|
||||||
|
types_resolve_order = [
|
||||||
|
SchemaType.OBJECT, SchemaType.ARRAY, SchemaType.BOOLEAN,
|
||||||
|
SchemaType.INTEGER, SchemaType.NUMBER, SchemaType.STRING,
|
||||||
|
]
|
||||||
|
cast_mapping = self.get_cast_mapping()
|
||||||
|
for schema_type in types_resolve_order:
|
||||||
|
cast_callable = cast_mapping[schema_type]
|
||||||
|
try:
|
||||||
|
return cast_callable(value)
|
||||||
|
# @todo: remove ValueError when validation separated
|
||||||
|
except (OpenAPISchemaError, TypeError, ValueError):
|
||||||
|
continue
|
||||||
|
|
||||||
|
raise NoValidSchema(
|
||||||
|
"No valid schema found for value {0}".format(value))
|
||||||
|
|
||||||
def _unmarshal_collection(self, value):
|
def _unmarshal_collection(self, value):
|
||||||
|
if self.items is None:
|
||||||
|
raise UndefinedItemsSchema("Undefined items' schema")
|
||||||
|
|
||||||
return list(map(self.items.unmarshal, value))
|
return list(map(self.items.unmarshal, value))
|
||||||
|
|
||||||
def _unmarshal_object(self, value, model_factory=None):
|
def _unmarshal_object(self, value, model_factory=None):
|
||||||
|
|
|
@ -17,6 +17,7 @@ from openapi_core.schema.request_bodies.models import RequestBody
|
||||||
from openapi_core.schema.responses.models import Response
|
from openapi_core.schema.responses.models import Response
|
||||||
from openapi_core.schema.schemas.exceptions import (
|
from openapi_core.schema.schemas.exceptions import (
|
||||||
UndefinedSchemaProperty, MissingSchemaProperty, NoOneOfSchema,
|
UndefinedSchemaProperty, MissingSchemaProperty, NoOneOfSchema,
|
||||||
|
NoValidSchema,
|
||||||
)
|
)
|
||||||
from openapi_core.schema.schemas.models import Schema
|
from openapi_core.schema.schemas.models import Schema
|
||||||
from openapi_core.schema.servers.exceptions import InvalidServer
|
from openapi_core.schema.servers.exceptions import InvalidServer
|
||||||
|
@ -1037,9 +1038,10 @@ class TestPetstore(object):
|
||||||
self, spec, response_validator):
|
self, spec, response_validator):
|
||||||
host_url = 'http://petstore.swagger.io/v1'
|
host_url = 'http://petstore.swagger.io/v1'
|
||||||
path_pattern = '/v1/tags'
|
path_pattern = '/v1/tags'
|
||||||
|
created = 'now'
|
||||||
pet_name = 'Dog'
|
pet_name = 'Dog'
|
||||||
data_json = {
|
data_json = {
|
||||||
'created': 'now',
|
'created': created,
|
||||||
'name': pet_name,
|
'name': pet_name,
|
||||||
}
|
}
|
||||||
data = json.dumps(data_json)
|
data = json.dumps(data_json)
|
||||||
|
@ -1053,44 +1055,14 @@ class TestPetstore(object):
|
||||||
body = request.get_body(spec)
|
body = request.get_body(spec)
|
||||||
|
|
||||||
assert parameters == {}
|
assert parameters == {}
|
||||||
assert body == data_json
|
assert isinstance(body, BaseModel)
|
||||||
|
assert body.created == created
|
||||||
data_json = {
|
assert body.name == pet_name
|
||||||
'code': 400,
|
|
||||||
'message': 'Bad request',
|
|
||||||
'rootCause': 'Tag already exist',
|
|
||||||
'additionalinfo': 'Tag Dog already exist',
|
|
||||||
}
|
|
||||||
data = json.dumps(data_json)
|
|
||||||
response = MockResponse(data, status_code=404)
|
|
||||||
|
|
||||||
response_result = response_validator.validate(request, response)
|
|
||||||
|
|
||||||
assert response_result.errors == []
|
|
||||||
assert response_result.data == data_json
|
|
||||||
|
|
||||||
def test_post_tags_created_datetime(
|
|
||||||
self, spec, response_validator):
|
|
||||||
host_url = 'http://petstore.swagger.io/v1'
|
|
||||||
path_pattern = '/v1/tags'
|
|
||||||
pet_name = 'Dog'
|
|
||||||
data_json = {
|
|
||||||
'created': '2016-04-16T16:06:05Z',
|
|
||||||
'name': pet_name,
|
|
||||||
}
|
|
||||||
data = json.dumps(data_json)
|
|
||||||
|
|
||||||
request = MockRequest(
|
|
||||||
host_url, 'POST', '/tags',
|
|
||||||
path_pattern=path_pattern, data=data,
|
|
||||||
)
|
|
||||||
|
|
||||||
parameters = request.get_parameters(spec)
|
|
||||||
body = request.get_body(spec)
|
|
||||||
|
|
||||||
assert parameters == {}
|
|
||||||
assert body == data_json
|
|
||||||
|
|
||||||
|
code = 400
|
||||||
|
message = 'Bad request'
|
||||||
|
rootCause = 'Tag already exist'
|
||||||
|
additionalinfo = 'Tag Dog already exist'
|
||||||
data_json = {
|
data_json = {
|
||||||
'code': 400,
|
'code': 400,
|
||||||
'message': 'Bad request',
|
'message': 'Bad request',
|
||||||
|
@ -1108,3 +1080,96 @@ class TestPetstore(object):
|
||||||
assert response_result.data.message == message
|
assert response_result.data.message == message
|
||||||
assert response_result.data.rootCause == rootCause
|
assert response_result.data.rootCause == rootCause
|
||||||
assert response_result.data.additionalinfo == additionalinfo
|
assert response_result.data.additionalinfo == additionalinfo
|
||||||
|
|
||||||
|
def test_post_tags_created_datetime(
|
||||||
|
self, spec, response_validator):
|
||||||
|
host_url = 'http://petstore.swagger.io/v1'
|
||||||
|
path_pattern = '/v1/tags'
|
||||||
|
created = '2016-04-16T16:06:05Z'
|
||||||
|
pet_name = 'Dog'
|
||||||
|
data_json = {
|
||||||
|
'created': created,
|
||||||
|
'name': pet_name,
|
||||||
|
}
|
||||||
|
data = json.dumps(data_json)
|
||||||
|
|
||||||
|
request = MockRequest(
|
||||||
|
host_url, 'POST', '/tags',
|
||||||
|
path_pattern=path_pattern, data=data,
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = request.get_parameters(spec)
|
||||||
|
body = request.get_body(spec)
|
||||||
|
|
||||||
|
assert parameters == {}
|
||||||
|
assert isinstance(body, BaseModel)
|
||||||
|
assert body.created == created
|
||||||
|
assert body.name == pet_name
|
||||||
|
|
||||||
|
code = 400
|
||||||
|
message = 'Bad request'
|
||||||
|
rootCause = 'Tag already exist'
|
||||||
|
additionalinfo = 'Tag Dog already exist'
|
||||||
|
data_json = {
|
||||||
|
'code': code,
|
||||||
|
'message': message,
|
||||||
|
'rootCause': rootCause,
|
||||||
|
'additionalinfo': additionalinfo,
|
||||||
|
}
|
||||||
|
data = json.dumps(data_json)
|
||||||
|
response = MockResponse(data, status_code=404)
|
||||||
|
|
||||||
|
response_result = response_validator.validate(request, response)
|
||||||
|
|
||||||
|
assert response_result.errors == []
|
||||||
|
assert isinstance(response_result.data, BaseModel)
|
||||||
|
assert response_result.data.code == code
|
||||||
|
assert response_result.data.message == message
|
||||||
|
assert response_result.data.rootCause == rootCause
|
||||||
|
assert response_result.data.additionalinfo == additionalinfo
|
||||||
|
|
||||||
|
@pytest.mark.xfail(reason='OneOf for string not supported atm')
|
||||||
|
def test_post_tags_created_invalid_type(
|
||||||
|
self, spec, response_validator):
|
||||||
|
host_url = 'http://petstore.swagger.io/v1'
|
||||||
|
path_pattern = '/v1/tags'
|
||||||
|
created = 'long time ago'
|
||||||
|
pet_name = 'Dog'
|
||||||
|
data_json = {
|
||||||
|
'created': created,
|
||||||
|
'name': pet_name,
|
||||||
|
}
|
||||||
|
data = json.dumps(data_json)
|
||||||
|
|
||||||
|
request = MockRequest(
|
||||||
|
host_url, 'POST', '/tags',
|
||||||
|
path_pattern=path_pattern, data=data,
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = request.get_parameters(spec)
|
||||||
|
with pytest.raises(NoValidSchema):
|
||||||
|
request.get_body(spec)
|
||||||
|
|
||||||
|
assert parameters == {}
|
||||||
|
|
||||||
|
code = 400
|
||||||
|
message = 'Bad request'
|
||||||
|
rootCause = 'Tag already exist'
|
||||||
|
additionalinfo = 'Tag Dog already exist'
|
||||||
|
data_json = {
|
||||||
|
'code': code,
|
||||||
|
'message': message,
|
||||||
|
'rootCause': rootCause,
|
||||||
|
'additionalinfo': additionalinfo,
|
||||||
|
}
|
||||||
|
data = json.dumps(data_json)
|
||||||
|
response = MockResponse(data, status_code=404)
|
||||||
|
|
||||||
|
response_result = response_validator.validate(request, response)
|
||||||
|
|
||||||
|
assert response_result.errors == []
|
||||||
|
assert isinstance(response_result.data, BaseModel)
|
||||||
|
assert response_result.data.code == code
|
||||||
|
assert response_result.data.message == message
|
||||||
|
assert response_result.data.rootCause == rootCause
|
||||||
|
assert response_result.data.additionalinfo == additionalinfo
|
||||||
|
|
Loading…
Reference in a new issue