From cfdf3410d2340fcf3030d0346ad260361c9e50a7 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Sun, 20 Oct 2019 14:38:41 +0100 Subject: [PATCH] Narrow validation exceptions --- openapi_core/schema/media_types/models.py | 15 ++++++-- openapi_core/schema/parameters/exceptions.py | 9 ++++- openapi_core/schema/parameters/models.py | 10 +++-- openapi_core/schema/schemas/exceptions.py | 8 ++-- openapi_core/schema/schemas/models.py | 14 +++---- openapi_core/validation/request/validators.py | 38 ++++++++++++------- .../validation/response/validators.py | 23 +++++++---- tests/unit/schema/test_schemas.py | 16 ++++---- 8 files changed, 83 insertions(+), 50 deletions(-) diff --git a/openapi_core/schema/media_types/models.py b/openapi_core/schema/media_types/models.py index 61b2e3d..e593a1a 100644 --- a/openapi_core/schema/media_types/models.py +++ b/openapi_core/schema/media_types/models.py @@ -4,7 +4,9 @@ from collections import defaultdict from json import loads from openapi_core.schema.media_types.exceptions import InvalidMediaTypeValue -from openapi_core.schema.schemas.exceptions import OpenAPISchemaError +from openapi_core.schema.schemas.exceptions import ( + CastError, ValidateError, UnmarshalError, +) MEDIA_TYPE_DESERIALIZERS = { @@ -37,20 +39,25 @@ class MediaType(object): return value try: - return self.deserialize(value) + deserialized = self.deserialize(value) except ValueError as exc: raise InvalidMediaTypeValue(exc) + try: + return self.schema.cast(deserialized) + except CastError as exc: + raise InvalidMediaTypeValue(exc) + def unmarshal(self, value, custom_formatters=None, resolver=None): if not self.schema: return value try: self.schema.validate(value, resolver=resolver) - except OpenAPISchemaError as exc: + except ValidateError as exc: raise InvalidMediaTypeValue(exc) try: return self.schema.unmarshal(value, custom_formatters=custom_formatters) - except OpenAPISchemaError as exc: + except UnmarshalError as exc: raise InvalidMediaTypeValue(exc) diff --git a/openapi_core/schema/parameters/exceptions.py b/openapi_core/schema/parameters/exceptions.py index bcaf13f..79b61c4 100644 --- a/openapi_core/schema/parameters/exceptions.py +++ b/openapi_core/schema/parameters/exceptions.py @@ -7,8 +7,13 @@ class OpenAPIParameterError(OpenAPIMappingError): pass +class MissingParameterError(OpenAPIParameterError): + """Missing parameter error""" + pass + + @attr.s(hash=True) -class MissingParameter(OpenAPIParameterError): +class MissingParameter(MissingParameterError): name = attr.ib() def __str__(self): @@ -16,7 +21,7 @@ class MissingParameter(OpenAPIParameterError): @attr.s(hash=True) -class MissingRequiredParameter(OpenAPIParameterError): +class MissingRequiredParameter(MissingParameterError): name = attr.ib() def __str__(self): diff --git a/openapi_core/schema/parameters/models.py b/openapi_core/schema/parameters/models.py index 612b062..98af859 100644 --- a/openapi_core/schema/parameters/models.py +++ b/openapi_core/schema/parameters/models.py @@ -10,7 +10,9 @@ from openapi_core.schema.parameters.exceptions import ( EmptyParameterValue, ) from openapi_core.schema.schemas.enums import SchemaType -from openapi_core.schema.schemas.exceptions import OpenAPISchemaError +from openapi_core.schema.schemas.exceptions import ( + CastError, ValidateError, UnmarshalError, +) log = logging.getLogger(__name__) @@ -110,7 +112,7 @@ class Parameter(object): try: return self.schema.cast(deserialized) - except OpenAPISchemaError as exc: + except CastError as exc: raise InvalidParameterValue(self.name, exc) def unmarshal(self, value, custom_formatters=None, resolver=None): @@ -119,7 +121,7 @@ class Parameter(object): try: self.schema.validate(value, resolver=resolver) - except OpenAPISchemaError as exc: + except ValidateError as exc: raise InvalidParameterValue(self.name, exc) try: @@ -128,5 +130,5 @@ class Parameter(object): custom_formatters=custom_formatters, strict=True, ) - except OpenAPISchemaError as exc: + except UnmarshalError as exc: raise InvalidParameterValue(self.name, exc) diff --git a/openapi_core/schema/schemas/exceptions.py b/openapi_core/schema/schemas/exceptions.py index 9428190..2b87923 100644 --- a/openapi_core/schema/schemas/exceptions.py +++ b/openapi_core/schema/schemas/exceptions.py @@ -23,13 +23,13 @@ class ValidateError(OpenAPISchemaError): pass -class UnmarshallError(OpenAPISchemaError): - """Schema unmarshall operation error""" +class UnmarshalError(OpenAPISchemaError): + """Schema unmarshal operation error""" pass @attr.s(hash=True) -class UnmarshallValueError(UnmarshallError): +class UnmarshalValueError(UnmarshalError): """Failed to unmarshal value to type""" value = attr.ib() type = attr.ib() @@ -57,7 +57,7 @@ class InvalidSchemaValue(ValidateError): ).format(value=self.value, type=self.type, errors=errors) -class UnmarshallerError(UnmarshallError): +class UnmarshallerError(UnmarshalError): """Unmarshaller error""" pass diff --git a/openapi_core/schema/schemas/models.py b/openapi_core/schema/schemas/models.py index 7bc4ad8..534c7b7 100644 --- a/openapi_core/schema/schemas/models.py +++ b/openapi_core/schema/schemas/models.py @@ -16,7 +16,7 @@ from openapi_core.schema.schemas._format import oas30_format_checker from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType from openapi_core.schema.schemas.exceptions import ( CastError, InvalidSchemaValue, - UnmarshallerError, UnmarshallValueError, UnmarshallError, + UnmarshallerError, UnmarshalValueError, UnmarshalError, ) from openapi_core.schema.schemas.util import ( forcebool, format_date, format_datetime, format_byte, format_uuid, @@ -202,12 +202,12 @@ class Schema(object): warnings.warn("The schema is deprecated", DeprecationWarning) if value is None: if not self.nullable: - raise UnmarshallError( + raise UnmarshalError( "Null value for non-nullable schema", value, self.type) return self.default if self.enum and value not in self.enum: - raise UnmarshallError("Invalid value for enum: {0}".format(value)) + raise UnmarshalError("Invalid value for enum: {0}".format(value)) unmarshal_mapping = self.get_unmarshal_mapping( custom_formatters=custom_formatters, strict=strict) @@ -219,7 +219,7 @@ class Schema(object): try: unmarshalled = unmarshal_callable(value) except ValueError as exc: - raise UnmarshallValueError(value, self.type, exc) + raise UnmarshalValueError(value, self.type, exc) return unmarshalled @@ -254,7 +254,7 @@ class Schema(object): for subschema in self.one_of: try: unmarshalled = subschema.unmarshal(value, custom_formatters) - except UnmarshallError: + except UnmarshalError: continue else: if result is not None: @@ -271,7 +271,7 @@ class Schema(object): unmarshal_callable = unmarshal_mapping[schema_type] try: return unmarshal_callable(value) - except (UnmarshallError, ValueError): + except (UnmarshalError, ValueError): continue log.warning("failed to unmarshal any type") @@ -300,7 +300,7 @@ class Schema(object): try: unmarshalled = self._unmarshal_properties( value, one_of_schema, custom_formatters=custom_formatters) - except (UnmarshallError, ValueError): + except (UnmarshalError, ValueError): pass else: if properties is not None: diff --git a/openapi_core/validation/request/validators.py b/openapi_core/validation/request/validators.py index dd726d9..f41008e 100644 --- a/openapi_core/validation/request/validators.py +++ b/openapi_core/validation/request/validators.py @@ -2,8 +2,16 @@ from itertools import chain from six import iteritems -from openapi_core.schema.exceptions import OpenAPIMappingError -from openapi_core.schema.parameters.exceptions import MissingParameter +from openapi_core.schema.media_types.exceptions import ( + InvalidMediaTypeValue, InvalidContentType, +) +from openapi_core.schema.operations.exceptions import InvalidOperation +from openapi_core.schema.parameters.exceptions import ( + OpenAPIParameterError, MissingRequiredParameter, +) +from openapi_core.schema.paths.exceptions import InvalidPath +from openapi_core.schema.request_bodies.exceptions import MissingRequestBody +from openapi_core.schema.servers.exceptions import InvalidServer from openapi_core.validation.request.datatypes import ( RequestParameters, RequestValidationResult, ) @@ -20,7 +28,7 @@ class RequestValidator(object): try: server = self.spec.get_server(request.full_url_pattern) # don't process if server errors - except OpenAPIMappingError as exc: + except InvalidServer as exc: return RequestValidationResult([exc, ], None, None) operation_pattern = get_operation_pattern( @@ -29,10 +37,14 @@ class RequestValidator(object): try: path = self.spec[operation_pattern] + except InvalidPath as exc: + return RequestValidationResult([exc, ], None, None) + + try: operation = self.spec.get_operation( operation_pattern, request.method) # don't process if operation errors - except OpenAPIMappingError as exc: + except InvalidOperation as exc: return RequestValidationResult([exc, ], None, None) params, params_errors = self._get_parameters( @@ -59,15 +71,15 @@ class RequestValidator(object): seen.add((param_name, param.location.value)) try: raw_value = param.get_raw_value(request) - except MissingParameter: - continue - except OpenAPIMappingError as exc: + except MissingRequiredParameter as exc: errors.append(exc) continue + except OpenAPIParameterError: + continue try: casted = param.cast(raw_value) - except OpenAPIMappingError as exc: + except OpenAPIParameterError as exc: errors.append(exc) continue @@ -76,7 +88,7 @@ class RequestValidator(object): casted, self.custom_formatters, resolver=self.spec._resolver, ) - except OpenAPIMappingError as exc: + except OpenAPIParameterError as exc: errors.append(exc) else: locations.setdefault(param.location.value, {}) @@ -93,17 +105,17 @@ class RequestValidator(object): body = None try: media_type = operation.request_body[request.mimetype] - except OpenAPIMappingError as exc: + except InvalidContentType as exc: errors.append(exc) else: try: raw_body = operation.request_body.get_value(request) - except OpenAPIMappingError as exc: + except MissingRequestBody as exc: errors.append(exc) else: try: casted = media_type.cast(raw_body) - except OpenAPIMappingError as exc: + except InvalidMediaTypeValue as exc: errors.append(exc) else: try: @@ -111,7 +123,7 @@ class RequestValidator(object): casted, self.custom_formatters, resolver=self.spec._resolver, ) - except OpenAPIMappingError as exc: + except InvalidMediaTypeValue as exc: errors.append(exc) return body, errors diff --git a/openapi_core/validation/response/validators.py b/openapi_core/validation/response/validators.py index 341b294..4859f19 100644 --- a/openapi_core/validation/response/validators.py +++ b/openapi_core/validation/response/validators.py @@ -1,5 +1,12 @@ """OpenAPI core validation response validators module""" -from openapi_core.schema.exceptions import OpenAPIMappingError +from openapi_core.schema.operations.exceptions import InvalidOperation +from openapi_core.schema.media_types.exceptions import ( + InvalidMediaTypeValue, InvalidContentType, +) +from openapi_core.schema.responses.exceptions import ( + InvalidResponse, MissingResponseContent, +) +from openapi_core.schema.servers.exceptions import InvalidServer from openapi_core.validation.response.datatypes import ResponseValidationResult from openapi_core.validation.util import get_operation_pattern @@ -14,7 +21,7 @@ class ResponseValidator(object): try: server = self.spec.get_server(request.full_url_pattern) # don't process if server errors - except OpenAPIMappingError as exc: + except InvalidServer as exc: return ResponseValidationResult([exc, ], None, None) operation_pattern = get_operation_pattern( @@ -25,14 +32,14 @@ class ResponseValidator(object): operation = self.spec.get_operation( operation_pattern, request.method) # don't process if operation errors - except OpenAPIMappingError as exc: + except InvalidOperation as exc: return ResponseValidationResult([exc, ], None, None) try: operation_response = operation.get_response( str(response.status_code)) # don't process if operation response errors - except OpenAPIMappingError as exc: + except InvalidResponse as exc: return ResponseValidationResult([exc, ], None, None) data, data_errors = self._get_data(response, operation_response) @@ -52,17 +59,17 @@ class ResponseValidator(object): data = None try: media_type = operation_response[response.mimetype] - except OpenAPIMappingError as exc: + except InvalidContentType as exc: errors.append(exc) else: try: raw_data = operation_response.get_value(response) - except OpenAPIMappingError as exc: + except MissingResponseContent as exc: errors.append(exc) else: try: casted = media_type.cast(raw_data) - except OpenAPIMappingError as exc: + except InvalidMediaTypeValue as exc: errors.append(exc) else: try: @@ -70,7 +77,7 @@ class ResponseValidator(object): casted, self.custom_formatters, resolver=self.spec._resolver, ) - except OpenAPIMappingError as exc: + except InvalidMediaTypeValue as exc: errors.append(exc) return data, errors diff --git a/tests/unit/schema/test_schemas.py b/tests/unit/schema/test_schemas.py index 49c5019..74fe396 100644 --- a/tests/unit/schema/test_schemas.py +++ b/tests/unit/schema/test_schemas.py @@ -8,7 +8,7 @@ from openapi_core.extensions.models.models import Model from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType from openapi_core.schema.schemas.exceptions import ( InvalidSchemaValue, OpenAPISchemaError, UnmarshallerStrictTypeError, - UnmarshallValueError, UnmarshallError, InvalidCustomFormatSchemaValue, + UnmarshalValueError, UnmarshalError, InvalidCustomFormatSchemaValue, FormatterNotFoundError, ) from openapi_core.schema.schemas.models import Schema @@ -97,7 +97,7 @@ class TestSchemaUnmarshal(object): schema = Schema('string') value = None - with pytest.raises(UnmarshallError): + with pytest.raises(UnmarshalError): schema.unmarshal(value) def test_string_default(self): @@ -105,7 +105,7 @@ class TestSchemaUnmarshal(object): schema = Schema('string', default=default_value) value = None - with pytest.raises(UnmarshallError): + with pytest.raises(UnmarshalError): schema.unmarshal(value) def test_string_default_nullable(self): @@ -196,7 +196,7 @@ class TestSchemaUnmarshal(object): schema = Schema('integer', enum=[1, 2, 3]) value = '123' - with pytest.raises(UnmarshallError): + with pytest.raises(UnmarshalError): schema.unmarshal(value) def test_integer_enum(self): @@ -211,7 +211,7 @@ class TestSchemaUnmarshal(object): schema = Schema('integer', enum=[1, 2, 3]) value = '2' - with pytest.raises(UnmarshallError): + with pytest.raises(UnmarshalError): schema.unmarshal(value) def test_integer_default(self): @@ -219,7 +219,7 @@ class TestSchemaUnmarshal(object): schema = Schema('integer', default=default_value) value = None - with pytest.raises(UnmarshallError): + with pytest.raises(UnmarshalError): schema.unmarshal(value) def test_integer_default_nullable(self): @@ -250,14 +250,14 @@ class TestSchemaUnmarshal(object): schema = Schema('array', items=Schema('string')) value = '123' - with pytest.raises(UnmarshallValueError): + with pytest.raises(UnmarshalValueError): schema.unmarshal(value) def test_array_of_integer_string_invalid(self): schema = Schema('array', items=Schema('integer')) value = '123' - with pytest.raises(UnmarshallValueError): + with pytest.raises(UnmarshalValueError): schema.unmarshal(value) def test_boolean_valid(self):