diff --git a/openapi_core/__init__.py b/openapi_core/__init__.py index 56d0a145..6927a707 100644 --- a/openapi_core/__init__.py +++ b/openapi_core/__init__.py @@ -1,18 +1,18 @@ """OpenAPI core module""" from openapi_core.spec import OpenAPIv30Spec -from openapi_core.validation.request.shortcuts import validate_request from openapi_core.validation.request.validators import RequestBodyValidator from openapi_core.validation.request.validators import ( RequestParametersValidator, ) from openapi_core.validation.request.validators import RequestSecurityValidator from openapi_core.validation.request.validators import RequestValidator -from openapi_core.validation.response.shortcuts import validate_response from openapi_core.validation.response.validators import ResponseDataValidator from openapi_core.validation.response.validators import ( ResponseHeadersValidator, ) from openapi_core.validation.response.validators import ResponseValidator +from openapi_core.validation.shortcuts import validate_request +from openapi_core.validation.shortcuts import validate_response __author__ = "Artur Maciag" __email__ = "maciag.artur@gmail.com" diff --git a/openapi_core/contrib/django/handlers.py b/openapi_core/contrib/django/handlers.py index 9dd808e5..6d20c340 100644 --- a/openapi_core/contrib/django/handlers.py +++ b/openapi_core/contrib/django/handlers.py @@ -1,12 +1,12 @@ """OpenAPI core contrib django handlers module""" from django.http import JsonResponse -from openapi_core.exceptions import MissingRequiredParameter from openapi_core.templating.media_types.exceptions import MediaTypeNotFound from openapi_core.templating.paths.exceptions import OperationNotFound from openapi_core.templating.paths.exceptions import PathNotFound from openapi_core.templating.paths.exceptions import ServerNotFound from openapi_core.validation.exceptions import InvalidSecurity +from openapi_core.validation.exceptions import MissingRequiredParameter class DjangoOpenAPIErrorsHandler: diff --git a/openapi_core/contrib/django/middlewares.py b/openapi_core/contrib/django/middlewares.py index 7226cfe9..08de5f71 100644 --- a/openapi_core/contrib/django/middlewares.py +++ b/openapi_core/contrib/django/middlewares.py @@ -6,8 +6,8 @@ from openapi_core.contrib.django.requests import DjangoOpenAPIRequest from openapi_core.contrib.django.responses import DjangoOpenAPIResponse from openapi_core.validation.processors import OpenAPIProcessor -from openapi_core.validation.request.validators import RequestValidator -from openapi_core.validation.response.validators import ResponseValidator +from openapi_core.validation.request import openapi_request_validator +from openapi_core.validation.response import openapi_response_validator class DjangoOpenAPIMiddleware: @@ -22,15 +22,15 @@ def __init__(self, get_response): if not hasattr(settings, "OPENAPI_SPEC"): raise ImproperlyConfigured("OPENAPI_SPEC not defined in settings") - request_validator = RequestValidator(settings.OPENAPI_SPEC) - response_validator = ResponseValidator(settings.OPENAPI_SPEC) self.validation_processor = OpenAPIProcessor( - request_validator, response_validator + openapi_request_validator, openapi_response_validator ) def __call__(self, request): openapi_request = self._get_openapi_request(request) - req_result = self.validation_processor.process_request(openapi_request) + req_result = self.validation_processor.process_request( + settings.OPENAPI_SPEC, openapi_request + ) if req_result.errors: response = self._handle_request_errors(req_result, request) else: @@ -39,7 +39,7 @@ def __call__(self, request): openapi_response = self._get_openapi_response(response) resp_result = self.validation_processor.process_response( - openapi_request, openapi_response + settings.OPENAPI_SPEC, openapi_request, openapi_response ) if resp_result.errors: return self._handle_response_errors(resp_result, request, response) diff --git a/openapi_core/contrib/falcon/handlers.py b/openapi_core/contrib/falcon/handlers.py index a01e70dc..77d2e63f 100644 --- a/openapi_core/contrib/falcon/handlers.py +++ b/openapi_core/contrib/falcon/handlers.py @@ -4,12 +4,12 @@ from falcon import status_codes from falcon.constants import MEDIA_JSON -from openapi_core.exceptions import MissingRequiredParameter from openapi_core.templating.media_types.exceptions import MediaTypeNotFound from openapi_core.templating.paths.exceptions import OperationNotFound from openapi_core.templating.paths.exceptions import PathNotFound from openapi_core.templating.paths.exceptions import ServerNotFound from openapi_core.validation.exceptions import InvalidSecurity +from openapi_core.validation.exceptions import MissingRequiredParameter class FalconOpenAPIErrorsHandler: diff --git a/openapi_core/contrib/falcon/middlewares.py b/openapi_core/contrib/falcon/middlewares.py index 0311aee0..eac38a24 100644 --- a/openapi_core/contrib/falcon/middlewares.py +++ b/openapi_core/contrib/falcon/middlewares.py @@ -4,8 +4,8 @@ from openapi_core.contrib.falcon.requests import FalconOpenAPIRequest from openapi_core.contrib.falcon.responses import FalconOpenAPIResponse from openapi_core.validation.processors import OpenAPIProcessor -from openapi_core.validation.request.validators import RequestValidator -from openapi_core.validation.response.validators import ResponseValidator +from openapi_core.validation.request import openapi_request_validator +from openapi_core.validation.response import openapi_response_validator class FalconOpenAPIMiddleware: @@ -16,11 +16,13 @@ class FalconOpenAPIMiddleware: def __init__( self, + spec, validation_processor, request_class=None, response_class=None, errors_handler=None, ): + self.spec = spec self.validation_processor = validation_processor self.request_class = request_class or self.request_class self.response_class = response_class or self.response_class @@ -34,12 +36,11 @@ def from_spec( response_class=None, errors_handler=None, ): - request_validator = RequestValidator(spec) - response_validator = ResponseValidator(spec) validation_processor = OpenAPIProcessor( - request_validator, response_validator + openapi_request_validator, openapi_response_validator ) return cls( + spec, validation_processor, request_class=request_class, response_class=response_class, @@ -76,9 +77,11 @@ def _get_openapi_response(self, response): return self.response_class(response) def _process_openapi_request(self, openapi_request): - return self.validation_processor.process_request(openapi_request) + return self.validation_processor.process_request( + self.spec, openapi_request + ) def _process_openapi_response(self, opneapi_request, openapi_response): return self.validation_processor.process_response( - opneapi_request, openapi_response + self.spec, opneapi_request, openapi_response ) diff --git a/openapi_core/contrib/flask/decorators.py b/openapi_core/contrib/flask/decorators.py index 621a5bf5..45025808 100644 --- a/openapi_core/contrib/flask/decorators.py +++ b/openapi_core/contrib/flask/decorators.py @@ -4,13 +4,14 @@ from openapi_core.contrib.flask.requests import FlaskOpenAPIRequest from openapi_core.contrib.flask.responses import FlaskOpenAPIResponse from openapi_core.validation.decorators import OpenAPIDecorator -from openapi_core.validation.request.validators import RequestValidator -from openapi_core.validation.response.validators import ResponseValidator +from openapi_core.validation.request import openapi_request_validator +from openapi_core.validation.response import openapi_response_validator class FlaskOpenAPIViewDecorator(OpenAPIDecorator): def __init__( self, + spec, request_validator, response_validator, request_class=FlaskOpenAPIRequest, @@ -19,6 +20,7 @@ def __init__( openapi_errors_handler=FlaskOpenAPIErrorsHandler, ): super().__init__( + spec, request_validator, response_validator, request_class, @@ -43,11 +45,10 @@ def from_spec( request_provider=FlaskRequestProvider, openapi_errors_handler=FlaskOpenAPIErrorsHandler, ): - request_validator = RequestValidator(spec) - response_validator = ResponseValidator(spec) return cls( - request_validator=request_validator, - response_validator=response_validator, + spec, + request_validator=openapi_request_validator, + response_validator=openapi_response_validator, request_class=request_class, response_class=response_class, request_provider=request_provider, diff --git a/openapi_core/contrib/flask/views.py b/openapi_core/contrib/flask/views.py index 74535695..5bb58778 100644 --- a/openapi_core/contrib/flask/views.py +++ b/openapi_core/contrib/flask/views.py @@ -3,8 +3,8 @@ from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator from openapi_core.contrib.flask.handlers import FlaskOpenAPIErrorsHandler -from openapi_core.validation.request.validators import RequestValidator -from openapi_core.validation.response.validators import ResponseValidator +from openapi_core.validation.request import openapi_request_validator +from openapi_core.validation.response import openapi_response_validator class FlaskOpenAPIView(MethodView): @@ -14,13 +14,13 @@ class FlaskOpenAPIView(MethodView): def __init__(self, spec): super().__init__() - self.request_validator = RequestValidator(spec) - self.response_validator = ResponseValidator(spec) + self.spec = spec def dispatch_request(self, *args, **kwargs): decorator = FlaskOpenAPIViewDecorator( - request_validator=self.request_validator, - response_validator=self.response_validator, + self.spec, + request_validator=openapi_request_validator, + response_validator=openapi_response_validator, openapi_errors_handler=self.openapi_errors_handler, ) return decorator(super().dispatch_request)(*args, **kwargs) diff --git a/openapi_core/exceptions.py b/openapi_core/exceptions.py index e1755749..504173c5 100644 --- a/openapi_core/exceptions.py +++ b/openapi_core/exceptions.py @@ -1,93 +1,5 @@ """OpenAPI core exceptions module""" -from dataclasses import dataclass - -from openapi_core.validation.request.protocols import Request -from openapi_core.validation.response.protocols import Response class OpenAPIError(Exception): pass - - -class OpenAPIHeaderError(OpenAPIError): - pass - - -class MissingHeaderError(OpenAPIHeaderError): - """Missing header error""" - - -@dataclass -class MissingHeader(MissingHeaderError): - name: str - - def __str__(self): - return f"Missing header (without default value): {self.name}" - - -@dataclass -class MissingRequiredHeader(MissingHeaderError): - name: str - - def __str__(self): - return f"Missing required header: {self.name}" - - -class OpenAPIParameterError(OpenAPIError): - pass - - -class MissingParameterError(OpenAPIParameterError): - """Missing parameter error""" - - -@dataclass -class MissingParameter(MissingParameterError): - name: str - - def __str__(self): - return f"Missing parameter (without default value): {self.name}" - - -@dataclass -class MissingRequiredParameter(MissingParameterError): - name: str - - def __str__(self): - return f"Missing required parameter: {self.name}" - - -class OpenAPIRequestBodyError(OpenAPIError): - pass - - -class MissingRequestBodyError(OpenAPIRequestBodyError): - """Missing request body error""" - - -@dataclass -class MissingRequestBody(MissingRequestBodyError): - request: Request - - def __str__(self): - return "Missing request body" - - -@dataclass -class MissingRequiredRequestBody(MissingRequestBodyError): - request: Request - - def __str__(self): - return "Missing required request body" - - -class OpenAPIResponseError(OpenAPIError): - pass - - -@dataclass -class MissingResponseContent(OpenAPIResponseError): - response: Response - - def __str__(self): - return "Missing response content" diff --git a/openapi_core/validation/__init__.py b/openapi_core/validation/__init__.py index e69de29b..52d41ee2 100644 --- a/openapi_core/validation/__init__.py +++ b/openapi_core/validation/__init__.py @@ -0,0 +1,20 @@ +"""OpenAPI core validation module""" +from openapi_core.validation.request import openapi_request_body_validator +from openapi_core.validation.request import ( + openapi_request_parameters_validator, +) +from openapi_core.validation.request import openapi_request_security_validator +from openapi_core.validation.request import openapi_request_validator +from openapi_core.validation.response import openapi_response_data_validator +from openapi_core.validation.response import openapi_response_headers_validator +from openapi_core.validation.response import openapi_response_validator + +__all__ = [ + "openapi_request_body_validator", + "openapi_request_parameters_validator", + "openapi_request_security_validator", + "openapi_request_validator", + "openapi_response_data_validator", + "openapi_response_headers_validator", + "openapi_response_validator", +] diff --git a/openapi_core/validation/decorators.py b/openapi_core/validation/decorators.py index 2b0899cd..9d8ce93c 100644 --- a/openapi_core/validation/decorators.py +++ b/openapi_core/validation/decorators.py @@ -7,6 +7,7 @@ class OpenAPIDecorator(OpenAPIProcessor): def __init__( self, + spec, request_validator, response_validator, request_class, @@ -15,6 +16,7 @@ def __init__( openapi_errors_handler, ): super().__init__(request_validator, response_validator) + self.spec = spec self.request_class = request_class self.response_class = response_class self.request_provider = request_provider @@ -25,7 +27,7 @@ def __call__(self, view): def decorated(*args, **kwargs): request = self._get_request(*args, **kwargs) openapi_request = self._get_openapi_request(request) - request_result = self.process_request(openapi_request) + request_result = self.process_request(self.spec, openapi_request) if request_result.errors: return self._handle_request_errors(request_result) response = self._handle_request_view( @@ -33,7 +35,7 @@ def decorated(*args, **kwargs): ) openapi_response = self._get_openapi_response(response) response_result = self.process_response( - openapi_request, openapi_response + self.spec, openapi_request, openapi_response ) if response_result.errors: return self._handle_response_errors(response_result) diff --git a/openapi_core/validation/exceptions.py b/openapi_core/validation/exceptions.py index dab7aa24..2cc2b191 100644 --- a/openapi_core/validation/exceptions.py +++ b/openapi_core/validation/exceptions.py @@ -12,3 +12,51 @@ class ValidationError(OpenAPIError): class InvalidSecurity(ValidationError): def __str__(self): return "Security not valid for any requirement" + + +class OpenAPIParameterError(OpenAPIError): + pass + + +class MissingParameterError(OpenAPIParameterError): + """Missing parameter error""" + + +@dataclass +class MissingParameter(MissingParameterError): + name: str + + def __str__(self): + return f"Missing parameter (without default value): {self.name}" + + +@dataclass +class MissingRequiredParameter(MissingParameterError): + name: str + + def __str__(self): + return f"Missing required parameter: {self.name}" + + +class OpenAPIHeaderError(OpenAPIError): + pass + + +class MissingHeaderError(OpenAPIHeaderError): + """Missing header error""" + + +@dataclass +class MissingHeader(MissingHeaderError): + name: str + + def __str__(self): + return f"Missing header (without default value): {self.name}" + + +@dataclass +class MissingRequiredHeader(MissingHeaderError): + name: str + + def __str__(self): + return f"Missing required header: {self.name}" diff --git a/openapi_core/validation/processors.py b/openapi_core/validation/processors.py index 587ebde8..abaf4974 100644 --- a/openapi_core/validation/processors.py +++ b/openapi_core/validation/processors.py @@ -6,8 +6,8 @@ def __init__(self, request_validator, response_validator): self.request_validator = request_validator self.response_validator = response_validator - def process_request(self, request): - return self.request_validator.validate(request) + def process_request(self, spec, request): + return self.request_validator.validate(spec, request) - def process_response(self, request, response): - return self.response_validator.validate(request, response) + def process_response(self, spec, request, response): + return self.response_validator.validate(spec, request, response) diff --git a/openapi_core/validation/request/__init__.py b/openapi_core/validation/request/__init__.py index e69de29b..54a69a34 100644 --- a/openapi_core/validation/request/__init__.py +++ b/openapi_core/validation/request/__init__.py @@ -0,0 +1,19 @@ +"""OpenAPI core validation request module""" +from openapi_core.validation.request.validators import RequestBodyValidator +from openapi_core.validation.request.validators import ( + RequestParametersValidator, +) +from openapi_core.validation.request.validators import RequestSecurityValidator +from openapi_core.validation.request.validators import RequestValidator + +__all__ = [ + "openapi_request_body_validator", + "openapi_request_parameters_validator", + "openapi_request_security_validator", + "openapi_request_validator", +] + +openapi_request_body_validator = RequestBodyValidator() +openapi_request_parameters_validator = RequestParametersValidator() +openapi_request_security_validator = RequestSecurityValidator() +openapi_request_validator = RequestValidator() diff --git a/openapi_core/validation/request/exceptions.py b/openapi_core/validation/request/exceptions.py index 356c3b8c..18d9b37f 100644 --- a/openapi_core/validation/request/exceptions.py +++ b/openapi_core/validation/request/exceptions.py @@ -1,10 +1,36 @@ from dataclasses import dataclass from typing import List +from openapi_core.exceptions import OpenAPIError from openapi_core.validation.request.datatypes import Parameters +from openapi_core.validation.request.protocols import Request @dataclass class ParametersError(Exception): parameters: Parameters context: List[Exception] + + +class OpenAPIRequestBodyError(OpenAPIError): + pass + + +class MissingRequestBodyError(OpenAPIRequestBodyError): + """Missing request body error""" + + +@dataclass +class MissingRequestBody(MissingRequestBodyError): + request: Request + + def __str__(self): + return "Missing request body" + + +@dataclass +class MissingRequiredRequestBody(MissingRequestBodyError): + request: Request + + def __str__(self): + return "Missing required request body" diff --git a/openapi_core/validation/request/shortcuts.py b/openapi_core/validation/request/shortcuts.py deleted file mode 100644 index e810938f..00000000 --- a/openapi_core/validation/request/shortcuts.py +++ /dev/null @@ -1,48 +0,0 @@ -"""OpenAPI core validation request shortcuts module""" -from openapi_core.validation.request.validators import RequestBodyValidator -from openapi_core.validation.request.validators import ( - RequestParametersValidator, -) -from openapi_core.validation.request.validators import RequestSecurityValidator -from openapi_core.validation.request.validators import RequestValidator - - -def validate_request(validator, request): - result = validator.validate(request) - result.raise_for_errors() - return result - - -def spec_validate_request(spec, request, base_url=None): - validator = RequestValidator( - spec, - base_url=base_url, - ) - return validate_request(validator, request) - - -def spec_validate_body(spec, request, base_url=None): - validator = RequestBodyValidator( - spec, - base_url=base_url, - ) - result = validate_request(validator, request) - return result.body - - -def spec_validate_parameters(spec, request, base_url=None): - validator = RequestParametersValidator( - spec, - base_url=base_url, - ) - result = validate_request(validator, request) - return result.parameters - - -def spec_validate_security(spec, request, base_url=None): - validator = RequestSecurityValidator( - spec, - base_url=base_url, - ) - result = validate_request(validator, request) - return result.security diff --git a/openapi_core/validation/request/validators.py b/openapi_core/validation/request/validators.py index ca62b4d8..7af369c6 100644 --- a/openapi_core/validation/request/validators.py +++ b/openapi_core/validation/request/validators.py @@ -3,10 +3,6 @@ from openapi_core.casting.schemas.exceptions import CastError from openapi_core.deserializing.exceptions import DeserializeError -from openapi_core.exceptions import MissingParameter -from openapi_core.exceptions import MissingRequestBody -from openapi_core.exceptions import MissingRequiredParameter -from openapi_core.exceptions import MissingRequiredRequestBody from openapi_core.schema.parameters import iter_params from openapi_core.security.exceptions import SecurityError from openapi_core.security.factories import SecurityProviderFactory @@ -19,13 +15,27 @@ SchemaUnmarshallersFactory, ) from openapi_core.validation.exceptions import InvalidSecurity +from openapi_core.validation.exceptions import MissingParameter +from openapi_core.validation.exceptions import MissingRequiredParameter from openapi_core.validation.request.datatypes import Parameters from openapi_core.validation.request.datatypes import RequestValidationResult +from openapi_core.validation.request.exceptions import MissingRequestBody +from openapi_core.validation.request.exceptions import ( + MissingRequiredRequestBody, +) from openapi_core.validation.request.exceptions import ParametersError from openapi_core.validation.validators import BaseValidator class BaseRequestValidator(BaseValidator): + def validate( + self, + spec, + request, + base_url=None, + ): + raise NotImplementedError + @property def schema_unmarshallers_factory(self): spec_resolver = ( @@ -158,7 +168,14 @@ def _get_body_value(self, request_body, request): class RequestParametersValidator(BaseRequestValidator): - def validate(self, request): + def validate( + self, + spec, + request, + base_url=None, + ): + self.spec = spec + self.base_url = base_url try: path, operation, _, path_result, _ = self._find_path(request) except PathError as exc: @@ -183,7 +200,14 @@ def validate(self, request): class RequestBodyValidator(BaseRequestValidator): - def validate(self, request): + def validate( + self, + spec, + request, + base_url=None, + ): + self.spec = spec + self.base_url = base_url try: _, operation, _, _, _ = self._find_path(request) except PathError as exc: @@ -214,7 +238,14 @@ def validate(self, request): class RequestSecurityValidator(BaseRequestValidator): - def validate(self, request): + def validate( + self, + spec, + request, + base_url=None, + ): + self.spec = spec + self.base_url = base_url try: _, operation, _, _, _ = self._find_path(request) except PathError as exc: @@ -232,7 +263,14 @@ def validate(self, request): class RequestValidator(BaseRequestValidator): - def validate(self, request): + def validate( + self, + spec, + request, + base_url=None, + ): + self.spec = spec + self.base_url = base_url try: path, operation, _, path_result, _ = self._find_path(request) # don't process if operation errors diff --git a/openapi_core/validation/response/__init__.py b/openapi_core/validation/response/__init__.py index e69de29b..5c0fed0c 100644 --- a/openapi_core/validation/response/__init__.py +++ b/openapi_core/validation/response/__init__.py @@ -0,0 +1,16 @@ +"""OpenAPI core validation response module""" +from openapi_core.validation.response.validators import ResponseDataValidator +from openapi_core.validation.response.validators import ( + ResponseHeadersValidator, +) +from openapi_core.validation.response.validators import ResponseValidator + +__all__ = [ + "openapi_response_data_validator", + "openapi_response_headers_validator", + "openapi_response_validator", +] + +openapi_response_data_validator = ResponseDataValidator() +openapi_response_headers_validator = ResponseHeadersValidator() +openapi_response_validator = ResponseValidator() diff --git a/openapi_core/validation/response/exceptions.py b/openapi_core/validation/response/exceptions.py index 8466de83..5808f23b 100644 --- a/openapi_core/validation/response/exceptions.py +++ b/openapi_core/validation/response/exceptions.py @@ -3,8 +3,23 @@ from typing import Dict from typing import List +from openapi_core.exceptions import OpenAPIError +from openapi_core.validation.response.protocols import Response + @dataclass class HeadersError(Exception): headers: Dict[str, Any] context: List[Exception] + + +class OpenAPIResponseError(OpenAPIError): + pass + + +@dataclass +class MissingResponseContent(OpenAPIResponseError): + response: Response + + def __str__(self): + return "Missing response content" diff --git a/openapi_core/validation/response/shortcuts.py b/openapi_core/validation/response/shortcuts.py deleted file mode 100644 index f5ae443b..00000000 --- a/openapi_core/validation/response/shortcuts.py +++ /dev/null @@ -1,40 +0,0 @@ -"""OpenAPI core validation response shortcuts module""" -from openapi_core.validation.response.validators import ResponseDataValidator -from openapi_core.validation.response.validators import ( - ResponseHeadersValidator, -) -from openapi_core.validation.response.validators import ResponseValidator - - -def validate_response(validator, request, response): - result = validator.validate(request, response) - result.raise_for_errors() - return result - - -def spec_validate_response(spec, request, response, base_url=None): - validator = ResponseValidator( - spec, - base_url=base_url, - custom_formatters=None, - custom_media_type_deserializers=None, - ) - return validate_response(validator, request, response) - - -def spec_validate_data(spec, request, response, base_url=None): - validator = ResponseDataValidator( - spec, - base_url=base_url, - ) - result = validate_response(validator, request, response) - return result.data - - -def spec_validate_headers(spec, request, response, base_url=None): - validator = ResponseHeadersValidator( - spec, - base_url=base_url, - ) - result = validate_response(validator, request, response) - return result.headers diff --git a/openapi_core/validation/response/validators.py b/openapi_core/validation/response/validators.py index 49ecb598..8823798a 100644 --- a/openapi_core/validation/response/validators.py +++ b/openapi_core/validation/response/validators.py @@ -3,9 +3,6 @@ from openapi_core.casting.schemas.exceptions import CastError from openapi_core.deserializing.exceptions import DeserializeError -from openapi_core.exceptions import MissingHeader -from openapi_core.exceptions import MissingRequiredHeader -from openapi_core.exceptions import MissingResponseContent from openapi_core.templating.media_types.exceptions import MediaTypeFinderError from openapi_core.templating.paths.exceptions import PathError from openapi_core.templating.responses.exceptions import ResponseFinderError @@ -15,12 +12,24 @@ from openapi_core.unmarshalling.schemas.factories import ( SchemaUnmarshallersFactory, ) +from openapi_core.validation.exceptions import MissingHeader +from openapi_core.validation.exceptions import MissingRequiredHeader from openapi_core.validation.response.datatypes import ResponseValidationResult from openapi_core.validation.response.exceptions import HeadersError +from openapi_core.validation.response.exceptions import MissingResponseContent from openapi_core.validation.validators import BaseValidator class BaseResponseValidator(BaseValidator): + def validate( + self, + spec, + request, + response, + base_url=None, + ): + raise NotImplementedError + @property def schema_unmarshallers_factory(self): spec_resolver = ( @@ -121,7 +130,15 @@ def _get_header(self, name, header, response): class ResponseDataValidator(BaseResponseValidator): - def validate(self, request, response): + def validate( + self, + spec, + request, + response, + base_url=None, + ): + self.spec = spec + self.base_url = base_url try: operation_response = self._find_operation_response( request, response @@ -152,7 +169,15 @@ def validate(self, request, response): class ResponseHeadersValidator(BaseResponseValidator): - def validate(self, request, response): + def validate( + self, + spec, + request, + response, + base_url=None, + ): + self.spec = spec + self.base_url = base_url try: operation_response = self._find_operation_response( request, response @@ -176,7 +201,15 @@ def validate(self, request, response): class ResponseValidator(BaseResponseValidator): - def validate(self, request, response): + def validate( + self, + spec, + request, + response, + base_url=None, + ): + self.spec = spec + self.base_url = base_url try: operation_response = self._find_operation_response( request, response diff --git a/openapi_core/validation/shortcuts.py b/openapi_core/validation/shortcuts.py new file mode 100644 index 00000000..5818d38f --- /dev/null +++ b/openapi_core/validation/shortcuts.py @@ -0,0 +1,23 @@ +"""OpenAPI core validation shortcuts module""" +from openapi_core.validation.request import openapi_request_validator +from openapi_core.validation.response import openapi_response_validator + + +def validate_request( + spec, request, base_url=None, validator=openapi_request_validator +): + result = validator.validate(spec, request, base_url=base_url) + result.raise_for_errors() + return result + + +def validate_response( + spec, + request, + response, + base_url=None, + validator=openapi_response_validator, +): + result = validator.validate(spec, request, response, base_url=base_url) + result.raise_for_errors() + return result diff --git a/openapi_core/validation/validators.py b/openapi_core/validation/validators.py index d22af9a1..445856d1 100644 --- a/openapi_core/validation/validators.py +++ b/openapi_core/validation/validators.py @@ -17,13 +17,9 @@ class BaseValidator: def __init__( self, - spec, - base_url=None, custom_formatters=None, custom_media_type_deserializers=None, ): - self.spec = spec - self.base_url = base_url self.custom_formatters = custom_formatters or {} self.custom_media_type_deserializers = custom_media_type_deserializers diff --git a/tests/integration/contrib/django/test_django_project.py b/tests/integration/contrib/django/test_django_project.py index 1c1de12c..0170bdc2 100644 --- a/tests/integration/contrib/django/test_django_project.py +++ b/tests/integration/contrib/django/test_django_project.py @@ -55,7 +55,7 @@ def test_get_no_required_param(self, client): "errors": [ { "class": ( - "" ), "status": 400, @@ -149,7 +149,7 @@ def test_post_required_header_param_missing(self, client): "errors": [ { "class": ( - "" ), "status": 400, @@ -214,7 +214,7 @@ def test_post_required_cookie_param_missing(self, client): "errors": [ { "class": ( - "" ), "status": 400, diff --git a/tests/integration/contrib/falcon/test_falcon_project.py b/tests/integration/contrib/falcon/test_falcon_project.py index c67b5b7b..921de4e0 100644 --- a/tests/integration/contrib/falcon/test_falcon_project.py +++ b/tests/integration/contrib/falcon/test_falcon_project.py @@ -120,7 +120,7 @@ def test_post_required_header_param_missing(self, client): "errors": [ { "class": ( - "" ), "status": 400, @@ -199,7 +199,7 @@ def test_post_required_cookie_param_missing(self, client): "errors": [ { "class": ( - "" ), "status": 400, diff --git a/tests/integration/contrib/flask/test_flask_views.py b/tests/integration/contrib/flask/test_flask_views.py index 34551bb6..b53e586d 100644 --- a/tests/integration/contrib/flask/test_flask_views.py +++ b/tests/integration/contrib/flask/test_flask_views.py @@ -172,7 +172,7 @@ def test_missing_required_header(self, client): "errors": [ { "class": ( - "" ), "status": 400, diff --git a/tests/integration/contrib/requests/test_requests_validation.py b/tests/integration/contrib/requests/test_requests_validation.py index 7c053576..63a983bd 100644 --- a/tests/integration/contrib/requests/test_requests_validation.py +++ b/tests/integration/contrib/requests/test_requests_validation.py @@ -5,8 +5,8 @@ from openapi_core.contrib.requests import RequestsOpenAPIRequest from openapi_core.contrib.requests import RequestsOpenAPIResponse from openapi_core.spec import OpenAPIv30Spec as Spec -from openapi_core.validation.request.validators import RequestValidator -from openapi_core.validation.response.validators import ResponseValidator +from openapi_core.validation.request import openapi_request_validator +from openapi_core.validation.response import openapi_response_validator class TestRequestsOpenAPIValidation: @@ -25,7 +25,6 @@ def test_response_validator_path_pattern(self, spec): match_querystring=True, headers={"X-Rate-Limit": "12"}, ) - validator = ResponseValidator(spec) request = requests.Request( "POST", "http://localhost/browse/12/", @@ -38,11 +37,12 @@ def test_response_validator_path_pattern(self, spec): response = session.send(request_prepared) openapi_request = RequestsOpenAPIRequest(request) openapi_response = RequestsOpenAPIResponse(response) - result = validator.validate(openapi_request, openapi_response) + result = openapi_response_validator.validate( + spec, openapi_request, openapi_response + ) assert not result.errors def test_request_validator_path_pattern(self, spec): - validator = RequestValidator(spec) request = requests.Request( "POST", "http://localhost/browse/12/", @@ -51,11 +51,10 @@ def test_request_validator_path_pattern(self, spec): json={"param1": 1}, ) openapi_request = RequestsOpenAPIRequest(request) - result = validator.validate(openapi_request) + result = openapi_request_validator.validate(spec, openapi_request) assert not result.errors def test_request_validator_prepared_request(self, spec): - validator = RequestValidator(spec) request = requests.Request( "POST", "http://localhost/browse/12/", @@ -65,5 +64,5 @@ def test_request_validator_prepared_request(self, spec): ) request_prepared = request.prepare() openapi_request = RequestsOpenAPIRequest(request_prepared) - result = validator.validate(openapi_request) + result = openapi_request_validator.validate(spec, openapi_request) assert not result.errors diff --git a/tests/integration/validation/test_minimal.py b/tests/integration/validation/test_minimal.py index a974d813..61722af4 100644 --- a/tests/integration/validation/test_minimal.py +++ b/tests/integration/validation/test_minimal.py @@ -4,8 +4,8 @@ from openapi_core.templating.paths.exceptions import OperationNotFound from openapi_core.templating.paths.exceptions import PathNotFound from openapi_core.testing import MockRequest +from openapi_core.validation.request import openapi_request_validator from openapi_core.validation.request.datatypes import Parameters -from openapi_core.validation.request.validators import RequestValidator class TestMinimal: @@ -28,10 +28,9 @@ class TestMinimal: def test_hosts(self, factory, server, spec_path): spec_dict = factory.spec_from_file(spec_path) spec = Spec.create(spec_dict) - validator = RequestValidator(spec) request = MockRequest(server, "get", "/status") - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert not result.errors @@ -40,10 +39,9 @@ def test_hosts(self, factory, server, spec_path): def test_invalid_operation(self, factory, server, spec_path): spec_dict = factory.spec_from_file(spec_path) spec = Spec.create(spec_dict) - validator = RequestValidator(spec) request = MockRequest(server, "post", "/status") - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert len(result.errors) == 1 assert isinstance(result.errors[0], OperationNotFound) @@ -55,10 +53,9 @@ def test_invalid_operation(self, factory, server, spec_path): def test_invalid_path(self, factory, server, spec_path): spec_dict = factory.spec_from_file(spec_path) spec = Spec.create(spec_dict) - validator = RequestValidator(spec) request = MockRequest(server, "get", "/nonexistent") - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert len(result.errors) == 1 assert isinstance(result.errors[0], PathNotFound) diff --git a/tests/integration/validation/test_petstore.py b/tests/integration/validation/test_petstore.py index 9b4ae582..69f032a4 100644 --- a/tests/integration/validation/test_petstore.py +++ b/tests/integration/validation/test_petstore.py @@ -11,8 +11,6 @@ from openapi_core.deserializing.parameters.exceptions import ( EmptyQueryParameterValue, ) -from openapi_core.exceptions import MissingRequiredHeader -from openapi_core.exceptions import MissingRequiredParameter from openapi_core.extensions.models.models import BaseModel from openapi_core.spec import OpenAPIv30Spec as Spec from openapi_core.templating.media_types.exceptions import MediaTypeNotFound @@ -20,14 +18,19 @@ from openapi_core.testing import MockRequest from openapi_core.testing import MockResponse from openapi_core.unmarshalling.schemas.exceptions import InvalidSchemaValue +from openapi_core.validation.exceptions import MissingRequiredHeader +from openapi_core.validation.exceptions import MissingRequiredParameter +from openapi_core.validation.request import openapi_request_body_validator +from openapi_core.validation.request import ( + openapi_request_parameters_validator, +) +from openapi_core.validation.request import openapi_request_security_validator from openapi_core.validation.request.datatypes import Parameters -from openapi_core.validation.request.shortcuts import spec_validate_body -from openapi_core.validation.request.shortcuts import spec_validate_parameters -from openapi_core.validation.request.shortcuts import spec_validate_security -from openapi_core.validation.request.validators import RequestValidator -from openapi_core.validation.response.shortcuts import spec_validate_data -from openapi_core.validation.response.shortcuts import spec_validate_headers -from openapi_core.validation.response.validators import ResponseValidator +from openapi_core.validation.response import openapi_response_data_validator +from openapi_core.validation.response import openapi_response_headers_validator +from openapi_core.validation.response import openapi_response_validator +from openapi_core.validation.shortcuts import validate_request +from openapi_core.validation.shortcuts import validate_response class TestPetstore: @@ -52,15 +55,7 @@ def spec_dict(self, factory): def spec(self, spec_dict, spec_uri): return Spec.create(spec_dict, url=spec_uri) - @pytest.fixture(scope="module") - def request_validator(self, spec): - return RequestValidator(spec) - - @pytest.fixture(scope="module") - def response_validator(self, spec): - return ResponseValidator(spec) - - def test_get_pets(self, spec, response_validator): + def test_get_pets(self, spec): host_url = "http://petstore.swagger.io/v1" path_pattern = "/v1/pets" query_params = { @@ -76,17 +71,23 @@ def test_get_pets(self, spec, response_validator): ) with pytest.warns(DeprecationWarning): - parameters = spec_validate_parameters(spec, request) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( query={ "limit": 20, "page": 1, "search": "", } ) - assert body is None + + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) + + assert result.body is None data_json = { "data": [], @@ -98,7 +99,7 @@ def test_get_pets(self, spec, response_validator): } response = MockResponse(data, headers=headers) - response_result = response_validator.validate(request, response) + response_result = validate_response(spec, request, response) assert response_result.errors == [] assert isinstance(response_result.data, BaseModel) @@ -107,7 +108,7 @@ def test_get_pets(self, spec, response_validator): "x-next": "next-url", } - def test_get_pets_response(self, spec, response_validator): + def test_get_pets_response(self, spec): host_url = "http://petstore.swagger.io/v1" path_pattern = "/v1/pets" query_params = { @@ -123,17 +124,23 @@ def test_get_pets_response(self, spec, response_validator): ) with pytest.warns(DeprecationWarning): - parameters = spec_validate_parameters(spec, request) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( query={ "limit": 20, "page": 1, "search": "", } ) - assert body is None + + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) + + assert result.body is None data_json = { "data": [ @@ -149,7 +156,7 @@ def test_get_pets_response(self, spec, response_validator): data = json.dumps(data_json) response = MockResponse(data) - response_result = response_validator.validate(request, response) + response_result = validate_response(spec, request, response) assert response_result.errors == [] assert isinstance(response_result.data, BaseModel) @@ -157,7 +164,7 @@ def test_get_pets_response(self, spec, response_validator): assert response_result.data.data[0].id == 1 assert response_result.data.data[0].name == "Cat" - def test_get_pets_response_no_schema(self, spec, response_validator): + def test_get_pets_response_no_schema(self, spec): host_url = "http://petstore.swagger.io/v1" path_pattern = "/v1/pets" query_params = { @@ -173,28 +180,34 @@ def test_get_pets_response_no_schema(self, spec, response_validator): ) with pytest.warns(DeprecationWarning): - parameters = spec_validate_parameters(spec, request) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( query={ "limit": 20, "page": 1, "search": "", } ) - assert body is None + + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) + + assert result.body is None data = "" response = MockResponse(data, status_code=404, mimetype="text/html") with pytest.warns(UserWarning): - response_result = response_validator.validate(request, response) + response_result = validate_response(spec, request, response) assert response_result.errors == [] assert response_result.data == data - def test_get_pets_invalid_response(self, spec, response_validator): + def test_get_pets_invalid_response(self, spec): host_url = "http://petstore.swagger.io/v1" path_pattern = "/v1/pets" query_params = { @@ -210,17 +223,23 @@ def test_get_pets_invalid_response(self, spec, response_validator): ) with pytest.warns(DeprecationWarning): - parameters = spec_validate_parameters(spec, request) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( query={ "limit": 20, "page": 1, "search": "", } ) - assert body is None + + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) + + assert result.body is None response_data_json = { "data": [ @@ -236,9 +255,16 @@ def test_get_pets_invalid_response(self, spec, response_validator): response = MockResponse(response_data) with pytest.raises(InvalidSchemaValue): - spec_validate_data(spec, request, response) - - response_result = response_validator.validate(request, response) + validate_response( + spec, + request, + response, + validator=openapi_response_data_validator, + ) + + response_result = openapi_response_validator.validate( + spec, request, response + ) schema_errors = response_result.errors[0].schema_errors assert response_result.errors == [ @@ -250,7 +276,7 @@ def test_get_pets_invalid_response(self, spec, response_validator): ] assert response_result.data is None - def test_get_pets_ids_param(self, spec, response_validator): + def test_get_pets_ids_param(self, spec): host_url = "http://petstore.swagger.io/v1" path_pattern = "/v1/pets" query_params = { @@ -267,10 +293,11 @@ def test_get_pets_ids_param(self, spec, response_validator): ) with pytest.warns(DeprecationWarning): - parameters = spec_validate_parameters(spec, request) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( query={ "limit": 20, "page": 1, @@ -278,7 +305,12 @@ def test_get_pets_ids_param(self, spec, response_validator): "ids": [12, 13], } ) - assert body is None + + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) + + assert result.body is None data_json = { "data": [], @@ -286,13 +318,13 @@ def test_get_pets_ids_param(self, spec, response_validator): data = json.dumps(data_json) response = MockResponse(data) - response_result = response_validator.validate(request, response) + response_result = validate_response(spec, request, response) assert response_result.errors == [] assert isinstance(response_result.data, BaseModel) assert response_result.data.data == [] - def test_get_pets_tags_param(self, spec, response_validator): + def test_get_pets_tags_param(self, spec): host_url = "http://petstore.swagger.io/v1" path_pattern = "/v1/pets" query_params = [ @@ -309,10 +341,11 @@ def test_get_pets_tags_param(self, spec, response_validator): ) with pytest.warns(DeprecationWarning): - parameters = spec_validate_parameters(spec, request) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( query={ "limit": 20, "page": 1, @@ -320,7 +353,12 @@ def test_get_pets_tags_param(self, spec, response_validator): "tags": ["cats", "dogs"], } ) - assert body is None + + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) + + assert result.body is None data_json = { "data": [], @@ -328,7 +366,7 @@ def test_get_pets_tags_param(self, spec, response_validator): data = json.dumps(data_json) response = MockResponse(data) - response_result = response_validator.validate(request, response) + response_result = validate_response(spec, request, response) assert response_result.errors == [] assert isinstance(response_result.data, BaseModel) @@ -352,11 +390,17 @@ def test_get_pets_parameter_deserialization_error(self, spec): with pytest.warns(DeprecationWarning): with pytest.raises(DeserializeError): - spec_validate_parameters(spec, request) - - body = spec_validate_body(spec, request) + validate_request( + spec, + request, + validator=openapi_request_parameters_validator, + ) + + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) - assert body is None + assert result.body is None def test_get_pets_wrong_parameter_type(self, spec): host_url = "http://petstore.swagger.io/v1" @@ -375,11 +419,17 @@ def test_get_pets_wrong_parameter_type(self, spec): with pytest.warns(DeprecationWarning): with pytest.raises(CastError): - spec_validate_parameters(spec, request) - - body = spec_validate_body(spec, request) + validate_request( + spec, + request, + validator=openapi_request_parameters_validator, + ) + + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) - assert body is None + assert result.body is None def test_get_pets_raises_missing_required_param(self, spec): host_url = "http://petstore.swagger.io/v1" @@ -393,11 +443,17 @@ def test_get_pets_raises_missing_required_param(self, spec): with pytest.warns(DeprecationWarning): with pytest.raises(MissingRequiredParameter): - spec_validate_parameters(spec, request) - - body = spec_validate_body(spec, request) + validate_request( + spec, + request, + validator=openapi_request_parameters_validator, + ) + + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) - assert body is None + assert result.body is None def test_get_pets_empty_value(self, spec): host_url = "http://petstore.swagger.io/v1" @@ -416,10 +472,16 @@ def test_get_pets_empty_value(self, spec): with pytest.warns(DeprecationWarning): with pytest.raises(EmptyQueryParameterValue): - spec_validate_parameters(spec, request) - body = spec_validate_body(spec, request) + validate_request( + spec, + request, + validator=openapi_request_parameters_validator, + ) + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) - assert body is None + assert result.body is None def test_get_pets_allow_empty_value(self, spec): host_url = "http://petstore.swagger.io/v1" @@ -438,9 +500,11 @@ def test_get_pets_allow_empty_value(self, spec): ) with pytest.warns(DeprecationWarning): - parameters = spec_validate_parameters(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( query={ "page": 1, "limit": 20, @@ -448,9 +512,11 @@ def test_get_pets_allow_empty_value(self, spec): } ) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) - assert body is None + assert result.body is None def test_get_pets_none_value(self, spec): host_url = "http://petstore.swagger.io/v1" @@ -468,9 +534,11 @@ def test_get_pets_none_value(self, spec): ) with pytest.warns(DeprecationWarning): - parameters = spec_validate_parameters(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( query={ "limit": None, "page": 1, @@ -478,9 +546,11 @@ def test_get_pets_none_value(self, spec): } ) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) - assert body is None + assert result.body is None def test_get_pets_param_order(self, spec): host_url = "http://petstore.swagger.io/v1" @@ -499,9 +569,11 @@ def test_get_pets_param_order(self, spec): ) with pytest.warns(DeprecationWarning): - parameters = spec_validate_parameters(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( query={ "limit": None, "order": "desc", @@ -510,9 +582,11 @@ def test_get_pets_param_order(self, spec): } ) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) - assert body is None + assert result.body is None def test_get_pets_param_coordinates(self, spec): host_url = "http://petstore.swagger.io/v1" @@ -535,9 +609,11 @@ def test_get_pets_param_coordinates(self, spec): ) with pytest.warns(DeprecationWarning): - parameters = spec_validate_parameters(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( query={ "limit": None, "page": 1, @@ -546,9 +622,11 @@ def test_get_pets_param_coordinates(self, spec): } ) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) - assert body is None + assert result.body is None def test_post_birds(self, spec, spec_dict): host_url = "https://staging.gigantic-server.com/v1" @@ -594,9 +672,11 @@ def test_post_birds(self, spec, spec_dict): cookies=cookies, ) - parameters = spec_validate_parameters(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( header={ "api-key": self.api_key, }, @@ -608,23 +688,27 @@ def test_post_birds(self, spec, spec_dict): }, ) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) schemas = spec_dict["components"]["schemas"] pet_model = schemas["PetCreate"]["x-model"] address_model = schemas["Address"]["x-model"] - assert body.__class__.__name__ == pet_model - assert body.name == pet_name - assert body.tag == pet_tag - assert body.position == 2 - assert body.address.__class__.__name__ == address_model - assert body.address.street == pet_street - assert body.address.city == pet_city - assert body.healthy == pet_healthy - - security = spec_validate_security(spec, request) + assert result.body.__class__.__name__ == pet_model + assert result.body.name == pet_name + assert result.body.tag == pet_tag + assert result.body.position == 2 + assert result.body.address.__class__.__name__ == address_model + assert result.body.address.street == pet_street + assert result.body.address.city == pet_city + assert result.body.healthy == pet_healthy + + result = validate_request( + spec, request, validator=openapi_request_security_validator + ) - assert security == {} + assert result.security == {} def test_post_cats(self, spec, spec_dict): host_url = "https://staging.gigantic-server.com/v1" @@ -665,9 +749,11 @@ def test_post_cats(self, spec, spec_dict): cookies=cookies, ) - parameters = spec_validate_parameters(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( header={ "api-key": self.api_key, }, @@ -676,19 +762,21 @@ def test_post_cats(self, spec, spec_dict): }, ) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) schemas = spec_dict["components"]["schemas"] pet_model = schemas["PetCreate"]["x-model"] address_model = schemas["Address"]["x-model"] - assert body.__class__.__name__ == pet_model - assert body.name == pet_name - assert body.tag == pet_tag - assert body.position == 2 - assert body.address.__class__.__name__ == address_model - assert body.address.street == pet_street - assert body.address.city == pet_city - assert body.healthy == pet_healthy + assert result.body.__class__.__name__ == pet_model + assert result.body.name == pet_name + assert result.body.tag == pet_tag + assert result.body.position == 2 + assert result.body.address.__class__.__name__ == address_model + assert result.body.address.street == pet_street + assert result.body.address.city == pet_city + assert result.body.healthy == pet_healthy def test_post_cats_boolean_string(self, spec, spec_dict): host_url = "https://staging.gigantic-server.com/v1" @@ -729,9 +817,11 @@ def test_post_cats_boolean_string(self, spec, spec_dict): cookies=cookies, ) - parameters = spec_validate_parameters(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( header={ "api-key": self.api_key, }, @@ -740,19 +830,21 @@ def test_post_cats_boolean_string(self, spec, spec_dict): }, ) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) schemas = spec_dict["components"]["schemas"] pet_model = schemas["PetCreate"]["x-model"] address_model = schemas["Address"]["x-model"] - assert body.__class__.__name__ == pet_model - assert body.name == pet_name - assert body.tag == pet_tag - assert body.position == 2 - assert body.address.__class__.__name__ == address_model - assert body.address.street == pet_street - assert body.address.city == pet_city - assert body.healthy is False + assert result.body.__class__.__name__ == pet_model + assert result.body.name == pet_name + assert result.body.tag == pet_tag + assert result.body.position == 2 + assert result.body.address.__class__.__name__ == address_model + assert result.body.address.street == pet_street + assert result.body.address.city == pet_city + assert result.body.healthy is False def test_post_no_one_of_schema(self, spec, spec_dict): host_url = "https://staging.gigantic-server.com/v1" @@ -781,9 +873,11 @@ def test_post_no_one_of_schema(self, spec, spec_dict): cookies=cookies, ) - parameters = spec_validate_parameters(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( header={ "api-key": self.api_key, }, @@ -793,7 +887,9 @@ def test_post_no_one_of_schema(self, spec, spec_dict): ) with pytest.raises(InvalidSchemaValue): - spec_validate_body(spec, request) + validate_request( + spec, request, validator=openapi_request_body_validator + ) def test_post_cats_only_required_body(self, spec, spec_dict): host_url = "https://staging.gigantic-server.com/v1" @@ -824,9 +920,11 @@ def test_post_cats_only_required_body(self, spec, spec_dict): cookies=cookies, ) - parameters = spec_validate_parameters(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( header={ "api-key": self.api_key, }, @@ -835,14 +933,16 @@ def test_post_cats_only_required_body(self, spec, spec_dict): }, ) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) schemas = spec_dict["components"]["schemas"] pet_model = schemas["PetCreate"]["x-model"] - assert body.__class__.__name__ == pet_model - assert body.name == pet_name - assert not hasattr(body, "tag") - assert not hasattr(body, "address") + assert result.body.__class__.__name__ == pet_model + assert result.body.name == pet_name + assert not hasattr(result.body, "tag") + assert not hasattr(result.body, "address") def test_post_pets_raises_invalid_mimetype(self, spec): host_url = "https://staging.gigantic-server.com/v1" @@ -870,9 +970,11 @@ def test_post_pets_raises_invalid_mimetype(self, spec): cookies=cookies, ) - parameters = spec_validate_parameters(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( header={ "api-key": self.api_key, }, @@ -882,7 +984,9 @@ def test_post_pets_raises_invalid_mimetype(self, spec): ) with pytest.raises(MediaTypeNotFound): - spec_validate_body(spec, request) + validate_request( + spec, request, validator=openapi_request_body_validator + ) def test_post_pets_missing_cookie(self, spec, spec_dict): host_url = "https://staging.gigantic-server.com/v1" @@ -910,16 +1014,20 @@ def test_post_pets_missing_cookie(self, spec, spec_dict): ) with pytest.raises(MissingRequiredParameter): - spec_validate_parameters(spec, request) + validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) schemas = spec_dict["components"]["schemas"] pet_model = schemas["PetCreate"]["x-model"] - assert body.__class__.__name__ == pet_model - assert body.name == pet_name - assert not hasattr(body, "tag") - assert not hasattr(body, "address") + assert result.body.__class__.__name__ == pet_model + assert result.body.name == pet_name + assert not hasattr(result.body, "tag") + assert not hasattr(result.body, "address") def test_post_pets_missing_header(self, spec, spec_dict): host_url = "https://staging.gigantic-server.com/v1" @@ -947,16 +1055,20 @@ def test_post_pets_missing_header(self, spec, spec_dict): ) with pytest.raises(MissingRequiredParameter): - spec_validate_parameters(spec, request) + validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) schemas = spec_dict["components"]["schemas"] pet_model = schemas["PetCreate"]["x-model"] - assert body.__class__.__name__ == pet_model - assert body.name == pet_name - assert not hasattr(body, "tag") - assert not hasattr(body, "address") + assert result.body.__class__.__name__ == pet_model + assert result.body.name == pet_name + assert not hasattr(result.body, "tag") + assert not hasattr(result.body, "address") def test_post_pets_raises_invalid_server_error(self, spec): host_url = "http://flowerstore.swagger.io/v1" @@ -985,10 +1097,14 @@ def test_post_pets_raises_invalid_server_error(self, spec): ) with pytest.raises(ServerNotFound): - spec_validate_parameters(spec, request) + validate_request( + spec, request, validator=openapi_request_parameters_validator + ) with pytest.raises(ServerNotFound): - spec_validate_body(spec, request) + validate_request( + spec, request, validator=openapi_request_body_validator + ) data_id = 1 data_name = "test" @@ -1005,9 +1121,14 @@ def test_post_pets_raises_invalid_server_error(self, spec): response = MockResponse(data) with pytest.raises(ServerNotFound): - spec_validate_data(spec, request, response) - - def test_get_pet(self, spec, response_validator): + validate_response( + spec, + request, + response, + validator=openapi_response_data_validator, + ) + + def test_get_pet(self, spec): host_url = "http://petstore.swagger.io/v1" path_pattern = "/v1/pets/{petId}" view_args = { @@ -1026,21 +1147,27 @@ def test_get_pet(self, spec, response_validator): headers=headers, ) - parameters = spec_validate_parameters(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( path={ "petId": 1, } ) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) - assert body is None + assert result.body is None - security = spec_validate_security(spec, request) + result = validate_request( + spec, request, validator=openapi_request_security_validator + ) - assert security == { + assert result.security == { "petstore_auth": auth, } @@ -1058,7 +1185,7 @@ def test_get_pet(self, spec, response_validator): data = json.dumps(data_json) response = MockResponse(data) - response_result = response_validator.validate(request, response) + response_result = validate_response(spec, request, response) assert response_result.errors == [] assert isinstance(response_result.data, BaseModel) @@ -1066,7 +1193,7 @@ def test_get_pet(self, spec, response_validator): assert response_result.data.data.id == data_id assert response_result.data.data.name == data_name - def test_get_pet_not_found(self, spec, response_validator): + def test_get_pet_not_found(self, spec): host_url = "http://petstore.swagger.io/v1" path_pattern = "/v1/pets/{petId}" view_args = { @@ -1080,17 +1207,21 @@ def test_get_pet_not_found(self, spec, response_validator): view_args=view_args, ) - parameters = spec_validate_parameters(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( path={ "petId": 1, } ) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) - assert body is None + assert result.body is None code = 404 message = "Not found" @@ -1103,7 +1234,7 @@ def test_get_pet_not_found(self, spec, response_validator): data = json.dumps(data_json) response = MockResponse(data, status_code=404) - response_result = response_validator.validate(request, response) + response_result = validate_response(spec, request, response) assert response_result.errors == [] assert isinstance(response_result.data, BaseModel) @@ -1111,7 +1242,7 @@ def test_get_pet_not_found(self, spec, response_validator): assert response_result.data.message == message assert response_result.data.rootCause == rootCause - def test_get_pet_wildcard(self, spec, response_validator): + def test_get_pet_wildcard(self, spec): host_url = "http://petstore.swagger.io/v1" path_pattern = "/v1/pets/{petId}" view_args = { @@ -1125,28 +1256,32 @@ def test_get_pet_wildcard(self, spec, response_validator): view_args=view_args, ) - parameters = spec_validate_parameters(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters( + assert result.parameters == Parameters( path={ "petId": 1, } ) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) - assert body is None + assert result.body is None data = b"imagedata" response = MockResponse(data, mimetype="image/png") with pytest.warns(UserWarning): - response_result = response_validator.validate(request, response) + response_result = validate_response(spec, request, response) assert response_result.errors == [] assert response_result.data == data - def test_get_tags(self, spec, response_validator): + def test_get_tags(self, spec): host_url = "http://petstore.swagger.io/v1" path_pattern = "/v1/tags" @@ -1157,17 +1292,23 @@ def test_get_tags(self, spec, response_validator): path_pattern=path_pattern, ) - parameters = spec_validate_parameters(spec, request) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) + + assert result.parameters == Parameters() - assert parameters == Parameters() - assert body is None + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) + + assert result.body is None data_json = ["cats", "birds"] data = json.dumps(data_json) response = MockResponse(data) - response_result = response_validator.validate(request, response) + response_result = validate_response(spec, request, response) assert response_result.errors == [] assert response_result.data == data_json @@ -1191,12 +1332,16 @@ def test_post_tags_extra_body_properties(self, spec, spec_dict): data=data, ) - parameters = spec_validate_parameters(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters() + assert result.parameters == Parameters() with pytest.raises(InvalidSchemaValue): - spec_validate_body(spec, request) + validate_request( + spec, request, validator=openapi_request_body_validator + ) def test_post_tags_empty_body(self, spec, spec_dict): host_url = "http://petstore.swagger.io/v1" @@ -1212,12 +1357,16 @@ def test_post_tags_empty_body(self, spec, spec_dict): data=data, ) - parameters = spec_validate_parameters(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters() + assert result.parameters == Parameters() with pytest.raises(InvalidSchemaValue): - spec_validate_body(spec, request) + validate_request( + spec, request, validator=openapi_request_body_validator + ) def test_post_tags_wrong_property_type(self, spec): host_url = "http://petstore.swagger.io/v1" @@ -1233,14 +1382,18 @@ def test_post_tags_wrong_property_type(self, spec): data=data, ) - parameters = spec_validate_parameters(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters() + assert result.parameters == Parameters() with pytest.raises(InvalidSchemaValue): - spec_validate_body(spec, request) + validate_request( + spec, request, validator=openapi_request_body_validator + ) - def test_post_tags_additional_properties(self, spec, response_validator): + def test_post_tags_additional_properties(self, spec): host_url = "http://petstore.swagger.io/v1" path_pattern = "/v1/tags" pet_name = "Dog" @@ -1257,12 +1410,18 @@ def test_post_tags_additional_properties(self, spec, response_validator): data=data, ) - parameters = spec_validate_parameters(spec, request) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) + + assert result.parameters == Parameters() + + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) - assert parameters == Parameters() - assert isinstance(body, BaseModel) - assert body.name == pet_name + assert isinstance(result.body, BaseModel) + assert result.body.name == pet_name code = 400 message = "Bad request" @@ -1277,7 +1436,7 @@ def test_post_tags_additional_properties(self, spec, response_validator): data = json.dumps(data_json) response = MockResponse(data, status_code=404) - response_result = response_validator.validate(request, response) + response_result = validate_response(spec, request, response) assert response_result.errors == [] assert isinstance(response_result.data, BaseModel) @@ -1286,7 +1445,7 @@ def test_post_tags_additional_properties(self, spec, response_validator): assert response_result.data.rootCause == rootCause assert response_result.data.additionalinfo == additionalinfo - def test_post_tags_created_now(self, spec, response_validator): + def test_post_tags_created_now(self, spec): host_url = "http://petstore.swagger.io/v1" path_pattern = "/v1/tags" created = "now" @@ -1305,13 +1464,19 @@ def test_post_tags_created_now(self, spec, response_validator): data=data, ) - parameters = spec_validate_parameters(spec, request) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) + + assert result.parameters == Parameters() + + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) - assert parameters == Parameters() - assert isinstance(body, BaseModel) - assert body.created == created - assert body.name == pet_name + assert isinstance(result.body, BaseModel) + assert result.body.created == created + assert result.body.name == pet_name code = 400 message = "Bad request" @@ -1326,7 +1491,7 @@ def test_post_tags_created_now(self, spec, response_validator): data = json.dumps(data_json) response = MockResponse(data, status_code=404) - response_result = response_validator.validate(request, response) + response_result = validate_response(spec, request, response) assert response_result.errors == [] assert isinstance(response_result.data, BaseModel) @@ -1335,7 +1500,7 @@ def test_post_tags_created_now(self, spec, response_validator): assert response_result.data.rootCause == rootCause assert response_result.data.additionalinfo == additionalinfo - def test_post_tags_created_datetime(self, spec, response_validator): + def test_post_tags_created_datetime(self, spec): host_url = "http://petstore.swagger.io/v1" path_pattern = "/v1/tags" created = "2016-04-16T16:06:05Z" @@ -1354,13 +1519,21 @@ def test_post_tags_created_datetime(self, spec, response_validator): data=data, ) - parameters = spec_validate_parameters(spec, request) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) + + assert result.parameters == Parameters() - assert parameters == Parameters() - assert isinstance(body, BaseModel) - assert body.created == datetime(2016, 4, 16, 16, 6, 5, tzinfo=UTC) - assert body.name == pet_name + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) + + assert isinstance(result.body, BaseModel) + assert result.body.created == datetime( + 2016, 4, 16, 16, 6, 5, tzinfo=UTC + ) + assert result.body.name == pet_name code = 400 message = "Bad request" @@ -1375,15 +1548,17 @@ def test_post_tags_created_datetime(self, spec, response_validator): response_data = json.dumps(response_data_json) response = MockResponse(response_data, status_code=404) - data = spec_validate_data(spec, request, response) + result = validate_response( + spec, request, response, validator=openapi_response_data_validator + ) - assert isinstance(data, BaseModel) - assert data.code == code - assert data.message == message - assert data.rootCause == rootCause - assert data.additionalinfo == additionalinfo + assert isinstance(result.data, BaseModel) + assert result.data.code == code + assert result.data.message == message + assert result.data.rootCause == rootCause + assert result.data.additionalinfo == additionalinfo - response_result = response_validator.validate(request, response) + response_result = validate_response(spec, request, response) assert response_result.errors == [] assert isinstance(response_result.data, BaseModel) @@ -1392,7 +1567,7 @@ def test_post_tags_created_datetime(self, spec, response_validator): assert response_result.data.rootCause == rootCause assert response_result.data.additionalinfo == additionalinfo - def test_post_tags_created_invalid_type(self, spec, response_validator): + def test_post_tags_created_invalid_type(self, spec): host_url = "http://petstore.swagger.io/v1" path_pattern = "/v1/tags" created = "long time ago" @@ -1411,11 +1586,16 @@ def test_post_tags_created_invalid_type(self, spec, response_validator): data=data, ) - parameters = spec_validate_parameters(spec, request) - with pytest.raises(InvalidSchemaValue): - spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) - assert parameters == Parameters() + assert result.parameters == Parameters() + + with pytest.raises(InvalidSchemaValue): + validate_request( + spec, request, validator=openapi_request_body_validator + ) code = 400 message = "Bad request" @@ -1431,7 +1611,7 @@ def test_post_tags_created_invalid_type(self, spec, response_validator): data = json.dumps(data_json) response = MockResponse(data, status_code=404) - response_result = response_validator.validate(request, response) + response_result = validate_response(spec, request, response) assert response_result.errors == [] assert isinstance(response_result.data, BaseModel) @@ -1441,7 +1621,7 @@ def test_post_tags_created_invalid_type(self, spec, response_validator): assert response_result.data.rootCause == rootCause assert response_result.data.additionalinfo == additionalinfo - def test_delete_tags_with_requestbody(self, spec, response_validator): + def test_delete_tags_with_requestbody(self, spec): host_url = "http://petstore.swagger.io/v1" path_pattern = "/v1/tags" ids = [1, 2, 3] @@ -1457,12 +1637,18 @@ def test_delete_tags_with_requestbody(self, spec, response_validator): data=data, ) - parameters = spec_validate_parameters(spec, request) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) + + assert result.parameters == Parameters() + + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) - assert parameters == Parameters() - assert isinstance(body, BaseModel) - assert body.ids == ids + assert isinstance(result.body, BaseModel) + assert result.body.ids == ids data = None headers = { @@ -1471,18 +1657,23 @@ def test_delete_tags_with_requestbody(self, spec, response_validator): response = MockResponse(data, status_code=200, headers=headers) with pytest.warns(DeprecationWarning): - response_result = response_validator.validate(request, response) + response_result = validate_response(spec, request, response) assert response_result.errors == [] assert response_result.data is None with pytest.warns(DeprecationWarning): - response_headers = spec_validate_headers(spec, request, response) - - assert response_headers == { + result = validate_response( + spec, + request, + response, + validator=openapi_response_headers_validator, + ) + + assert result.headers == { "x-delete-confirm": True, } - def test_delete_tags_no_requestbody(self, spec, response_validator): + def test_delete_tags_no_requestbody(self, spec): host_url = "http://petstore.swagger.io/v1" path_pattern = "/v1/tags" request = MockRequest( @@ -1492,15 +1683,19 @@ def test_delete_tags_no_requestbody(self, spec, response_validator): path_pattern=path_pattern, ) - parameters = spec_validate_parameters(spec, request) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) + + assert result.parameters == Parameters() - assert parameters == Parameters() - assert body is None + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) + + assert result.body is None - def test_delete_tags_raises_missing_required_response_header( - self, spec, response_validator - ): + def test_delete_tags_raises_missing_required_response_header(self, spec): host_url = "http://petstore.swagger.io/v1" path_pattern = "/v1/tags" request = MockRequest( @@ -1510,17 +1705,25 @@ def test_delete_tags_raises_missing_required_response_header( path_pattern=path_pattern, ) - parameters = spec_validate_parameters(spec, request) - body = spec_validate_body(spec, request) + result = validate_request( + spec, request, validator=openapi_request_parameters_validator + ) + + assert result.parameters == Parameters() + + result = validate_request( + spec, request, validator=openapi_request_body_validator + ) - assert parameters == Parameters() - assert body is None + assert result.body is None data = None response = MockResponse(data, status_code=200) with pytest.warns(DeprecationWarning): - response_result = response_validator.validate(request, response) + response_result = openapi_response_validator.validate( + spec, request, response + ) assert response_result.errors == [ MissingRequiredHeader(name="x-delete-confirm"), diff --git a/tests/integration/validation/test_read_only_write_only.py b/tests/integration/validation/test_read_only_write_only.py index 9b2e7c7a..0509f793 100644 --- a/tests/integration/validation/test_read_only_write_only.py +++ b/tests/integration/validation/test_read_only_write_only.py @@ -6,18 +6,8 @@ from openapi_core.testing import MockRequest from openapi_core.testing import MockResponse from openapi_core.unmarshalling.schemas.exceptions import InvalidSchemaValue -from openapi_core.validation.request.validators import RequestValidator -from openapi_core.validation.response.validators import ResponseValidator - - -@pytest.fixture -def response_validator(spec): - return ResponseValidator(spec) - - -@pytest.fixture -def request_validator(spec): - return RequestValidator(spec) +from openapi_core.validation.request import openapi_request_validator +from openapi_core.validation.response import openapi_response_validator @pytest.fixture(scope="class") @@ -27,7 +17,7 @@ def spec(factory): class TestReadOnly: - def test_write_a_read_only_property(self, request_validator): + def test_write_a_read_only_property(self, spec): data = json.dumps( { "id": 10, @@ -39,12 +29,12 @@ def test_write_a_read_only_property(self, request_validator): host_url="", method="POST", path="/users", data=data ) - result = request_validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert type(result.errors[0]) == InvalidSchemaValue assert result.body is None - def test_read_only_property_response(self, response_validator): + def test_read_only_property_response(self, spec): data = json.dumps( { "id": 10, @@ -56,7 +46,7 @@ def test_read_only_property_response(self, response_validator): response = MockResponse(data) - result = response_validator.validate(request, response) + result = openapi_response_validator.validate(spec, request, response) assert not result.errors assert result.data == { @@ -66,7 +56,7 @@ def test_read_only_property_response(self, response_validator): class TestWriteOnly: - def test_write_only_property(self, request_validator): + def test_write_only_property(self, spec): data = json.dumps( { "name": "Pedro", @@ -78,7 +68,7 @@ def test_write_only_property(self, request_validator): host_url="", method="POST", path="/users", data=data ) - result = request_validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert not result.errors assert result.body == { @@ -86,7 +76,7 @@ def test_write_only_property(self, request_validator): "hidden": False, } - def test_read_a_write_only_property(self, response_validator): + def test_read_a_write_only_property(self, spec): data = json.dumps( { "id": 10, @@ -98,7 +88,7 @@ def test_read_a_write_only_property(self, response_validator): request = MockRequest(host_url="", method="POST", path="/users") response = MockResponse(data) - result = response_validator.validate(request, response) + result = openapi_response_validator.validate(spec, request, response) assert type(result.errors[0]) == InvalidSchemaValue assert result.data is None diff --git a/tests/integration/validation/test_security_override.py b/tests/integration/validation/test_security_override.py index aef5b629..c5f5f6e2 100644 --- a/tests/integration/validation/test_security_override.py +++ b/tests/integration/validation/test_security_override.py @@ -5,12 +5,7 @@ from openapi_core.spec import OpenAPIv30Spec as Spec from openapi_core.testing import MockRequest from openapi_core.validation.exceptions import InvalidSecurity -from openapi_core.validation.request.validators import RequestValidator - - -@pytest.fixture -def request_validator(spec): - return RequestValidator(spec) +from openapi_core.validation.request import openapi_request_validator @pytest.fixture(scope="class") @@ -31,26 +26,26 @@ def api_key_encoded(self): api_key_bytes_enc = b64encode(api_key_bytes) return str(api_key_bytes_enc, "utf8") - def test_default(self, request_validator): + def test_default(self, spec): args = {"api_key": self.api_key} request = MockRequest(self.host_url, "get", "/resource/one", args=args) - result = request_validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert not result.errors assert result.security == { "api_key": self.api_key, } - def test_default_invalid(self, request_validator): + def test_default_invalid(self, spec): request = MockRequest(self.host_url, "get", "/resource/one") - result = request_validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert type(result.errors[0]) == InvalidSecurity assert result.security is None - def test_override(self, request_validator): + def test_override(self, spec): authorization = "Basic " + self.api_key_encoded headers = { "Authorization": authorization, @@ -59,25 +54,25 @@ def test_override(self, request_validator): self.host_url, "post", "/resource/one", headers=headers ) - result = request_validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert not result.errors assert result.security == { "petstore_auth": self.api_key_encoded, } - def test_override_invalid(self, request_validator): + def test_override_invalid(self, spec): request = MockRequest(self.host_url, "post", "/resource/one") - result = request_validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert type(result.errors[0]) == InvalidSecurity assert result.security is None - def test_remove(self, request_validator): + def test_remove(self, spec): request = MockRequest(self.host_url, "put", "/resource/one") - result = request_validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert not result.errors assert result.security == {} diff --git a/tests/integration/validation/test_validators.py b/tests/integration/validation/test_validators.py index f3c52339..05e42d00 100644 --- a/tests/integration/validation/test_validators.py +++ b/tests/integration/validation/test_validators.py @@ -7,9 +7,6 @@ from openapi_core.deserializing.media_types.exceptions import ( MediaTypeDeserializeError, ) -from openapi_core.exceptions import MissingRequiredParameter -from openapi_core.exceptions import MissingRequiredRequestBody -from openapi_core.exceptions import MissingResponseContent from openapi_core.extensions.models.models import BaseModel from openapi_core.spec import OpenAPIv30Spec as Spec from openapi_core.templating.media_types.exceptions import MediaTypeNotFound @@ -19,10 +16,15 @@ from openapi_core.testing import MockRequest from openapi_core.testing import MockResponse from openapi_core.unmarshalling.schemas.exceptions import InvalidSchemaValue +from openapi_core.validation import openapi_request_validator +from openapi_core.validation import openapi_response_validator from openapi_core.validation.exceptions import InvalidSecurity +from openapi_core.validation.exceptions import MissingRequiredParameter from openapi_core.validation.request.datatypes import Parameters -from openapi_core.validation.request.validators import RequestValidator -from openapi_core.validation.response.validators import ResponseValidator +from openapi_core.validation.request.exceptions import ( + MissingRequiredRequestBody, +) +from openapi_core.validation.response.exceptions import MissingResponseContent class TestRequestValidator: @@ -45,45 +47,41 @@ def spec_dict(self, factory): def spec(self, spec_dict): return Spec.create(spec_dict) - @pytest.fixture(scope="session") - def validator(self, spec): - return RequestValidator(spec, base_url=self.host_url) - - def test_request_server_error(self, validator): + def test_request_server_error(self, spec): request = MockRequest("http://petstore.invalid.net/v1", "get", "/") - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert len(result.errors) == 1 assert type(result.errors[0]) == PathNotFound assert result.body is None assert result.parameters == Parameters() - def test_invalid_path(self, validator): + def test_invalid_path(self, spec): request = MockRequest(self.host_url, "get", "/v1") - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert len(result.errors) == 1 assert type(result.errors[0]) == PathNotFound assert result.body is None assert result.parameters == Parameters() - def test_invalid_operation(self, validator): + def test_invalid_operation(self, spec): request = MockRequest(self.host_url, "patch", "/v1/pets") - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert len(result.errors) == 1 assert type(result.errors[0]) == OperationNotFound assert result.body is None assert result.parameters == Parameters() - def test_missing_parameter(self, validator): + def test_missing_parameter(self, spec): request = MockRequest(self.host_url, "get", "/v1/pets") with pytest.warns(DeprecationWarning): - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert type(result.errors[0]) == MissingRequiredParameter assert result.body is None @@ -94,7 +92,7 @@ def test_missing_parameter(self, validator): }, ) - def test_get_pets(self, validator): + def test_get_pets(self, spec): args = {"limit": "10", "ids": ["1", "2"], "api_key": self.api_key} request = MockRequest( self.host_url, @@ -105,7 +103,7 @@ def test_get_pets(self, validator): ) with pytest.warns(DeprecationWarning): - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert result.errors == [] assert result.body is None @@ -121,7 +119,7 @@ def test_get_pets(self, validator): "api_key": self.api_key, } - def test_get_pets_webob(self, validator): + def test_get_pets_webob(self, spec): from webob.multidict import GetDict request = MockRequest( @@ -135,7 +133,7 @@ def test_get_pets_webob(self, validator): ) with pytest.warns(DeprecationWarning): - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert result.errors == [] assert result.body is None @@ -148,7 +146,7 @@ def test_get_pets_webob(self, validator): }, ) - def test_missing_body(self, validator): + def test_missing_body(self, spec): headers = { "api-key": self.api_key_encoded, } @@ -164,7 +162,7 @@ def test_missing_body(self, validator): cookies=cookies, ) - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert len(result.errors) == 1 assert type(result.errors[0]) == MissingRequiredRequestBody @@ -178,7 +176,7 @@ def test_missing_body(self, validator): }, ) - def test_invalid_content_type(self, validator): + def test_invalid_content_type(self, spec): data = "csv,data" headers = { "api-key": self.api_key_encoded, @@ -197,7 +195,7 @@ def test_invalid_content_type(self, validator): cookies=cookies, ) - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert len(result.errors) == 1 assert type(result.errors[0]) == MediaTypeNotFound @@ -211,7 +209,7 @@ def test_invalid_content_type(self, validator): }, ) - def test_invalid_complex_parameter(self, validator, spec_dict): + def test_invalid_complex_parameter(self, spec, spec_dict): pet_name = "Cat" pet_tag = "cats" pet_street = "Piekna" @@ -250,7 +248,7 @@ def test_invalid_complex_parameter(self, validator, spec_dict): cookies=cookies, ) - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert len(result.errors) == 1 assert type(result.errors[0]) == InvalidSchemaValue @@ -275,7 +273,7 @@ def test_invalid_complex_parameter(self, validator, spec_dict): assert result.body.address.street == pet_street assert result.body.address.city == pet_city - def test_post_pets(self, validator, spec_dict): + def test_post_pets(self, spec, spec_dict): pet_name = "Cat" pet_tag = "cats" pet_street = "Piekna" @@ -309,7 +307,7 @@ def test_post_pets(self, validator, spec_dict): cookies=cookies, ) - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert result.errors == [] assert result.parameters == Parameters( @@ -333,7 +331,7 @@ def test_post_pets(self, validator, spec_dict): assert result.body.address.street == pet_street assert result.body.address.city == pet_city - def test_post_pets_plain_no_schema(self, validator, spec_dict): + def test_post_pets_plain_no_schema(self, spec, spec_dict): data = "plain text" headers = { "api-key": self.api_key_encoded, @@ -353,7 +351,7 @@ def test_post_pets_plain_no_schema(self, validator, spec_dict): ) with pytest.warns(UserWarning): - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert result.errors == [] assert result.parameters == Parameters( @@ -367,7 +365,7 @@ def test_post_pets_plain_no_schema(self, validator, spec_dict): assert result.security == {} assert result.body == data - def test_get_pet_unauthorized(self, validator): + def test_get_pet_unauthorized(self, spec): request = MockRequest( self.host_url, "get", @@ -376,7 +374,7 @@ def test_get_pet_unauthorized(self, validator): view_args={"petId": "1"}, ) - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert result.errors == [ InvalidSecurity(), @@ -385,7 +383,7 @@ def test_get_pet_unauthorized(self, validator): assert result.parameters == Parameters() assert result.security is None - def test_get_pet(self, validator): + def test_get_pet(self, spec): authorization = "Basic " + self.api_key_encoded headers = { "Authorization": authorization, @@ -399,7 +397,7 @@ def test_get_pet(self, validator): headers=headers, ) - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert result.errors == [] assert result.body is None @@ -447,47 +445,43 @@ def spec_dict(self): def spec(self, spec_dict): return Spec.create(spec_dict) - @pytest.fixture(scope="session") - def validator(self, spec): - return RequestValidator(spec, base_url="http://example.com") - - def test_request_missing_param(self, validator): + def test_request_missing_param(self, spec): request = MockRequest("http://example.com", "get", "/resource") - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert len(result.errors) == 1 assert type(result.errors[0]) == MissingRequiredParameter assert result.body is None assert result.parameters == Parameters() - def test_request_invalid_param(self, validator): + def test_request_invalid_param(self, spec): request = MockRequest( "http://example.com", "get", "/resource", args={"resId": "invalid"}, ) - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert len(result.errors) == 1 assert type(result.errors[0]) == CastError assert result.body is None assert result.parameters == Parameters() - def test_request_valid_param(self, validator): + def test_request_valid_param(self, spec): request = MockRequest( "http://example.com", "get", "/resource", args={"resId": "10"}, ) - result = validator.validate(request) + result = openapi_request_validator.validate(spec, request) assert len(result.errors) == 0 assert result.body is None assert result.parameters == Parameters(query={"resId": 10}) - def test_request_override_param(self, spec_dict): + def test_request_override_param(self, spec, spec_dict): # override path parameter on operation spec_dict["paths"]["/resource"]["get"]["parameters"] = [ { @@ -500,17 +494,16 @@ def test_request_override_param(self, spec_dict): }, } ] - validator = RequestValidator( - Spec.create(spec_dict), base_url="http://example.com" - ) request = MockRequest("http://example.com", "get", "/resource") - result = validator.validate(request) + result = openapi_request_validator.validate( + spec, request, base_url="http://example.com" + ) assert len(result.errors) == 0 assert result.body is None assert result.parameters == Parameters() - def test_request_override_param_uniqueness(self, spec_dict): + def test_request_override_param_uniqueness(self, spec, spec_dict): # add parameter on operation with same name as on path but # different location spec_dict["paths"]["/resource"]["get"]["parameters"] = [ @@ -524,11 +517,10 @@ def test_request_override_param_uniqueness(self, spec_dict): }, } ] - validator = RequestValidator( - Spec.create(spec_dict), base_url="http://example.com" - ) request = MockRequest("http://example.com", "get", "/resource") - result = validator.validate(request) + result = openapi_request_validator.validate( + spec, request, base_url="http://example.com" + ) assert len(result.errors) == 1 assert type(result.errors[0]) == MissingRequiredParameter @@ -548,88 +540,84 @@ def spec_dict(self, factory): def spec(self, spec_dict): return Spec.create(spec_dict) - @pytest.fixture - def validator(self, spec): - return ResponseValidator(spec, base_url=self.host_url) - - def test_invalid_server(self, validator): + def test_invalid_server(self, spec): request = MockRequest("http://petstore.invalid.net/v1", "get", "/") response = MockResponse("Not Found", status_code=404) - result = validator.validate(request, response) + result = openapi_response_validator.validate(spec, request, response) assert len(result.errors) == 1 assert type(result.errors[0]) == PathNotFound assert result.data is None assert result.headers == {} - def test_invalid_operation(self, validator): + def test_invalid_operation(self, spec): request = MockRequest(self.host_url, "patch", "/v1/pets") response = MockResponse("Not Found", status_code=404) - result = validator.validate(request, response) + result = openapi_response_validator.validate(spec, request, response) assert len(result.errors) == 1 assert type(result.errors[0]) == OperationNotFound assert result.data is None assert result.headers == {} - def test_invalid_response(self, validator): + def test_invalid_response(self, spec): request = MockRequest(self.host_url, "get", "/v1/pets") response = MockResponse("Not Found", status_code=409) - result = validator.validate(request, response) + result = openapi_response_validator.validate(spec, request, response) assert len(result.errors) == 1 assert type(result.errors[0]) == ResponseNotFound assert result.data is None assert result.headers == {} - def test_invalid_content_type(self, validator): + def test_invalid_content_type(self, spec): request = MockRequest(self.host_url, "get", "/v1/pets") response = MockResponse("Not Found", mimetype="text/csv") - result = validator.validate(request, response) + result = openapi_response_validator.validate(spec, request, response) assert len(result.errors) == 1 assert type(result.errors[0]) == MediaTypeNotFound assert result.data is None assert result.headers == {} - def test_missing_body(self, validator): + def test_missing_body(self, spec): request = MockRequest(self.host_url, "get", "/v1/pets") response = MockResponse(None) - result = validator.validate(request, response) + result = openapi_response_validator.validate(spec, request, response) assert len(result.errors) == 1 assert type(result.errors[0]) == MissingResponseContent assert result.data is None assert result.headers == {} - def test_invalid_media_type(self, validator): + def test_invalid_media_type(self, spec): request = MockRequest(self.host_url, "get", "/v1/pets") response = MockResponse("abcde") - result = validator.validate(request, response) + result = openapi_response_validator.validate(spec, request, response) assert len(result.errors) == 1 assert type(result.errors[0]) == MediaTypeDeserializeError assert result.data is None assert result.headers == {} - def test_invalid_media_type_value(self, validator): + def test_invalid_media_type_value(self, spec): request = MockRequest(self.host_url, "get", "/v1/pets") response = MockResponse("{}") - result = validator.validate(request, response) + result = openapi_response_validator.validate(spec, request, response) assert len(result.errors) == 1 assert type(result.errors[0]) == InvalidSchemaValue assert result.data is None assert result.headers == {} - def test_invalid_value(self, validator): + def test_invalid_value(self, spec): request = MockRequest(self.host_url, "get", "/v1/tags") response_json = { "data": [ @@ -639,14 +627,14 @@ def test_invalid_value(self, validator): response_data = json.dumps(response_json) response = MockResponse(response_data) - result = validator.validate(request, response) + result = openapi_response_validator.validate(spec, request, response) assert len(result.errors) == 1 assert type(result.errors[0]) == InvalidSchemaValue assert result.data is None assert result.headers == {} - def test_get_pets(self, validator): + def test_get_pets(self, spec): request = MockRequest(self.host_url, "get", "/v1/pets") response_json = { "data": [ @@ -662,7 +650,7 @@ def test_get_pets(self, validator): response_data = json.dumps(response_json) response = MockResponse(response_data) - result = validator.validate(request, response) + result = openapi_response_validator.validate(spec, request, response) assert result.errors == [] assert isinstance(result.data, BaseModel) diff --git a/tests/unit/validation/test_request_shortcuts.py b/tests/unit/validation/test_request_shortcuts.py index 26c5f8af..f9ab9cde 100644 --- a/tests/unit/validation/test_request_shortcuts.py +++ b/tests/unit/validation/test_request_shortcuts.py @@ -3,12 +3,12 @@ import pytest from openapi_core.testing.datatypes import ResultMock -from openapi_core.validation.request.shortcuts import spec_validate_request +from openapi_core.validation.shortcuts import validate_request class TestValidateRequest: @mock.patch( - "openapi_core.validation.request.shortcuts.RequestValidator.validate" + "openapi_core.validation.shortcuts.openapi_request_validator.validate" ) def test_validator_valid(self, mock_validate): spec = mock.sentinel.spec @@ -17,13 +17,13 @@ def test_validator_valid(self, mock_validate): validation_result = ResultMock(parameters=parameters) mock_validate.return_value = validation_result - result = spec_validate_request(spec, request) + result = validate_request(spec, request) assert result == validation_result mock_validate.aasert_called_once_with(request) @mock.patch( - "openapi_core.validation.request.shortcuts.RequestValidator.validate" + "openapi_core.validation.shortcuts.openapi_request_validator.validate" ) def test_validator_error(self, mock_validate): spec = mock.sentinel.spec @@ -31,6 +31,6 @@ def test_validator_error(self, mock_validate): mock_validate.return_value = ResultMock(error_to_raise=ValueError) with pytest.raises(ValueError): - spec_validate_request(spec, request) + validate_request(spec, request) mock_validate.aasert_called_once_with(request) diff --git a/tests/unit/validation/test_response_shortcuts.py b/tests/unit/validation/test_response_shortcuts.py index d422dfce..fbdcfeec 100644 --- a/tests/unit/validation/test_response_shortcuts.py +++ b/tests/unit/validation/test_response_shortcuts.py @@ -3,12 +3,12 @@ import pytest from openapi_core.testing.datatypes import ResultMock -from openapi_core.validation.response.shortcuts import spec_validate_response +from openapi_core.validation.shortcuts import validate_response class TestSpecValidateData: @mock.patch( - "openapi_core.validation.response.shortcuts.ResponseValidator.validate" + "openapi_core.validation.shortcuts.openapi_response_validator.validate" ) def test_validator_valid(self, mock_validate): spec = mock.sentinel.spec @@ -18,13 +18,13 @@ def test_validator_valid(self, mock_validate): validation_result = ResultMock(data=data) mock_validate.return_value = validation_result - result = spec_validate_response(spec, request, response) + result = validate_response(spec, request, response) assert result == validation_result mock_validate.aasert_called_once_with(request, response) @mock.patch( - "openapi_core.validation.response.shortcuts.ResponseValidator.validate" + "openapi_core.validation.shortcuts.openapi_response_validator.validate" ) def test_validator_error(self, mock_validate): spec = mock.sentinel.spec @@ -33,6 +33,6 @@ def test_validator_error(self, mock_validate): mock_validate.return_value = ResultMock(error_to_raise=ValueError) with pytest.raises(ValueError): - spec_validate_response(spec, request, response) + validate_response(spec, request, response) mock_validate.aasert_called_once_with(request, response)