From 0cbbdb0232425936c40a88ce943f620297c340e7 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Sun, 5 Aug 2018 13:40:34 +0100 Subject: [PATCH] Unmarshal any schema type --- openapi_core/schema/schemas/exceptions.py | 8 ++ openapi_core/schema/schemas/models.py | 33 ++++- tests/integration/test_petstore.py | 141 ++++++++++++++++------ 3 files changed, 138 insertions(+), 44 deletions(-) diff --git a/openapi_core/schema/schemas/exceptions.py b/openapi_core/schema/schemas/exceptions.py index 3222a39..ef51d21 100644 --- a/openapi_core/schema/schemas/exceptions.py +++ b/openapi_core/schema/schemas/exceptions.py @@ -5,6 +5,14 @@ class OpenAPISchemaError(OpenAPIMappingError): pass +class NoValidSchema(OpenAPISchemaError): + pass + + +class UndefinedItemsSchema(OpenAPISchemaError): + pass + + class InvalidSchemaValue(OpenAPISchemaError): pass diff --git a/openapi_core/schema/schemas/models.py b/openapi_core/schema/schemas/models.py index 31a80c5..2aabf45 100644 --- a/openapi_core/schema/schemas/models.py +++ b/openapi_core/schema/schemas/models.py @@ -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.exceptions import ( InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty, - OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema, + OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema, NoValidSchema, + UndefinedItemsSchema, ) from openapi_core.schema.schemas.util import ( forcebool, format_date, format_datetime, @@ -26,7 +27,6 @@ class Schema(object): """Represents an OpenAPI Schema.""" DEFAULT_CAST_CALLABLE_GETTER = { - SchemaType.ANY: lambda x: x, SchemaType.INTEGER: int, SchemaType.NUMBER: float, SchemaType.BOOLEAN: forcebool, @@ -47,7 +47,7 @@ class Schema(object): } TYPE_VALIDATOR_CALLABLE_GETTER = { - None: lambda x: True, + SchemaType.ANY: lambda x: x, SchemaType.BOOLEAN: TypeValidator(bool), SchemaType.INTEGER: TypeValidator(integer_types, exclude=bool), SchemaType.NUMBER: TypeValidator(integer_types, float, exclude=bool), @@ -108,7 +108,7 @@ class Schema(object): return dict( (prop_name, val) - for prop_name, val in all_properties.items() + for prop_name, val in iteritems(all_properties) if prop_name in required ) @@ -125,6 +125,7 @@ class Schema(object): mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy() mapping.update({ SchemaType.STRING: self._unmarshal_string, + SchemaType.ANY: self._unmarshal_any, SchemaType.ARRAY: self._unmarshal_collection, SchemaType.OBJECT: self._unmarshal_object, }) @@ -154,8 +155,8 @@ class Schema(object): def unmarshal(self, value): """Unmarshal parameter from the value.""" if self.deprecated: - warnings.warn( - "The schema is deprecated", DeprecationWarning) + warnings.warn("The schema is deprecated", DeprecationWarning) + casted = self.cast(value) if casted is None and not self.required: @@ -188,7 +189,27 @@ class Schema(object): 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): + if self.items is None: + raise UndefinedItemsSchema("Undefined items' schema") + return list(map(self.items.unmarshal, value)) def _unmarshal_object(self, value, model_factory=None): diff --git a/tests/integration/test_petstore.py b/tests/integration/test_petstore.py index 43ad5c2..3e3ed2a 100644 --- a/tests/integration/test_petstore.py +++ b/tests/integration/test_petstore.py @@ -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.schemas.exceptions import ( UndefinedSchemaProperty, MissingSchemaProperty, NoOneOfSchema, + NoValidSchema, ) from openapi_core.schema.schemas.models import Schema from openapi_core.schema.servers.exceptions import InvalidServer @@ -1037,9 +1038,10 @@ class TestPetstore(object): self, spec, response_validator): host_url = 'http://petstore.swagger.io/v1' path_pattern = '/v1/tags' + created = 'now' pet_name = 'Dog' data_json = { - 'created': 'now', + 'created': created, 'name': pet_name, } data = json.dumps(data_json) @@ -1053,44 +1055,14 @@ class TestPetstore(object): body = request.get_body(spec) assert parameters == {} - assert body == data_json - - data_json = { - '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 + 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': 400, 'message': 'Bad request', @@ -1108,3 +1080,96 @@ class TestPetstore(object): assert response_result.data.message == message assert response_result.data.rootCause == rootCause 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