# Buggy code
def __call__(self, req):
    """
    The method is invoked on every request and shows the lifecycle of the request received from
    the middleware.

    Although some middleware may use parts of the API spec, it is safe to assume that if you're
    looking for the particular spec property handler, it's most likely a part of this method.

    At the time of writing, the only property being utilized by middleware was `x-log-result`.
    """
    LOG.debug("Received call with WebOb: %s", req)
    endpoint, path_vars = self.match(req)
    LOG.debug("Parsed endpoint: %s", endpoint)
    LOG.debug("Parsed path_vars: %s", path_vars)

    context = copy.copy(getattr(self, "mock_context", {}))
    cookie_token = None

    # Handle security
    if "security" in endpoint:
        security = endpoint.get("security")
    else:
        security = self.spec.get("security", [])

    if self.auth and security:
        try:
            security_definitions = self.spec.get("securityDefinitions", {})
            for statement in security:
                declaration, options = statement.copy().popitem()
                definition = security_definitions[declaration]

                if definition["type"] == "apiKey":
                    if definition["in"] == "header":
                        token = req.headers.get(definition["name"])
                    elif definition["in"] == "query":
                        token = req.GET.get(definition["name"])
                    elif definition["in"] == "cookie":
                        token = req.cookies.get(definition["name"])
                    else:
                        token = None

                    if token:
                        auth_func = op_resolver(definition["x-operationId"])
                        auth_resp = auth_func(token)

                        # Include information on how user authenticated inside the context
                        if "auth-token" in definition["name"].lower():
                            auth_method = "authentication token"
                        elif "api-key" in definition["name"].lower():
                            auth_method = "API key"

                        context["user"] = User.get_by_name(auth_resp.user)
                        context["auth_info"] = {
                            "method": auth_method,
                            "location": definition["in"],
                        }

                        # Also include token expiration time when authenticated via auth token
                        if "auth-token" in definition["name"].lower():
                            context["auth_info"]["token_expire"] = auth_resp.expiry

                        if "x-set-cookie" in definition:
                            max_age = (
                                auth_resp.expiry - date_utils.get_datetime_utc_now()
                            )
                            cookie_token = cookies.make_cookie(
                                definition["x-set-cookie"],
                                token,
                                max_age=max_age,
                                httponly=True,
                            )

                        break

            if "user" not in context:
                raise auth_exc.NoAuthSourceProvidedError(
                    "One of Token or API key required."
                )
        except (
            auth_exc.NoAuthSourceProvidedError,
            auth_exc.MultipleAuthSourcesError,
        ) as e:
            LOG.error(str(e))
            return abort_unauthorized(str(e))
        except auth_exc.TokenNotProvidedError as e:
            LOG.exception("Token is not provided.")
            return abort_unauthorized(str(e))
        except auth_exc.TokenNotFoundError as e:
            LOG.exception("Token is not found.")
            return abort_unauthorized(str(e))
        except auth_exc.TokenExpiredError as e:
            LOG.exception("Token has expired.")
            return abort_unauthorized(str(e))
        except auth_exc.ApiKeyNotProvidedError as e:
            LOG.exception("API key is not provided.")
            return abort_unauthorized(str(e))
        except auth_exc.ApiKeyNotFoundError as e:
            LOG.exception("API key is not found.")
            return abort_unauthorized(str(e))
        except auth_exc.ApiKeyDisabledError as e:
            LOG.exception("API key is disabled.")
            return abort_unauthorized(str(e))

        if cfg.CONF.rbac.enable:
            user_db = context["user"]

            permission_type = endpoint.get("x-permissions", None)
            if permission_type:
                resolver = resolvers.get_resolver_for_permission_type(permission_type)
                has_permission = resolver.user_has_permission(user_db, permission_type)

                if not has_permission:
                    raise rbac_exc.ResourceTypeAccessDeniedError(
                        user_db, permission_type
                    )

    # Collect parameters
    kw = {}
    for param in endpoint.get("parameters", []) + endpoint.get("x-parameters", []):
        name = param["name"]
        argument_name = param.get("x-as", None) or name
        source = param["in"]
        default = param.get("default", None)

        # Collecting params from different sources
        if source == "query":
            kw[argument_name] = req.GET.get(name, default)
        elif source == "path":
            kw[argument_name] = path_vars[name]
        elif source == "header":
            kw[argument_name] = req.headers.get(name, default)
        elif source == "formData":
            kw[argument_name] = req.POST.get(name, default)
        elif source == "environ":
            kw[argument_name] = req.environ.get(name.upper(), default)
        elif source == "context":
            kw[argument_name] = context.get(name, default)
        elif source == "request":
            kw[argument_name] = getattr(req, name)
        elif source == "body":
            content_type = req.headers.get("Content-Type", "application/json")
            content_type = parse_content_type_header(content_type=content_type)[0]
            schema = param["schema"]

            # Note: We also want to perform validation if no body is explicitly provided - in a
            # lot of POST, PUT scenarios, body is mandatory
            if not req.body and content_type == "application/json":
                req.body = b"{}"

            try:
                if content_type == "application/json":
                    data = req.json
                elif content_type == "text/plain":
                    data = req.body
                elif content_type in [
                    "application/x-www-form-urlencoded",
                    "multipart/form-data",
                ]:
                    data = urlparse.parse_qs(req.body)
                else:
                    raise ValueError('Unsupported Content-Type: "%s"' % (content_type))
            except Exception as e:
                detail = "Failed to parse request body: %s" % str(e)
                raise exc.HTTPBadRequest(detail=detail)

            # Special case for Python 3
            if (
                six.PY3
                and content_type == "text/plain"
                and isinstance(data, six.binary_type)
            ):
                # Convert bytes to text type (string / unicode)
                data = data.decode("utf-8")

            try:
                CustomValidator(schema, resolver=self.spec_resolver).validate(data)
            except (jsonschema.ValidationError, ValueError) as e:
                raise exc.HTTPBadRequest(
                    detail=e.message, comment=traceback.format_exc()
                )

            if content_type == "text/plain":
                kw[argument_name] = data
            else:

                class Body(object):
                    def __init__(self, **entries):
                        self.__dict__.update(entries)

                ref = schema.get("$ref", None)
                if ref:
                    with self.spec_resolver.resolving(ref) as resolved:
                        schema = resolved

                if "x-api-model" in schema:
                    input_type = schema.get("type", [])
                    Model = op_resolver(schema["x-api-model"])

                    if input_type and not isinstance(input_type, (list, tuple)):
                        input_type = [input_type]

                    # root attribute is not an object, we need to use wrapper attribute to
                    # make it work with **kwarg expansion
                    if input_type and "array" in input_type:
                        data = {"data": data}

                    instance = self._get_model_instance(model_cls=Model, data=data)

                    # Call validate on the API model - note we should eventually move all
                    # those model schema definitions into openapi.yaml
                    try:
                        instance = instance.validate()
                    except (jsonschema.ValidationError, ValueError) as e:
                        raise exc.HTTPBadRequest(
                            detail=e.message, comment=traceback.format_exc()
                        )
                else:
                    LOG.debug(
                        "Missing x-api-model definition for %s, using generic Body "
                        "model." % (endpoint["operationId"])
                    )
                    model = Body
                    instance = self._get_model_instance(model_cls=model, data=data)

                kw[argument_name] = instance

        # Making sure all required params are present
        required = param.get("required", False)
        if required and kw[argument_name] is None:
            detail = 'Required parameter "%s" is missing' % name
            raise exc.HTTPBadRequest(detail=detail)

        # Validating and casting param types
        param_type = param.get("type", None)
        if kw[argument_name] is not None:
            if param_type == "boolean":
                positive = ("true", "1", "yes", "y")
                negative = ("false", "0", "no", "n")

                if str(kw[argument_name]).lower() not in positive + negative:
                    detail = 'Parameter "%s" is not of type boolean' % argument_name
                    raise exc.HTTPBadRequest(detail=detail)

                kw[argument_name] = str(kw[argument_name]).lower() in positive
            elif param_type == "integer":
                regex = r"^-?[0-9]+$"

                if not re.search(regex, str(kw[argument_name])):
                    detail = 'Parameter "%s" is not of type integer' % argument_name
                    raise exc.HTTPBadRequest(detail=detail)

                kw[argument_name] = int(kw[argument_name])
            elif param_type == "number":
                regex = r"^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$"

                if not re.search(regex, str(kw[argument_name])):
                    detail = 'Parameter "%s" is not of type float' % argument_name
                    raise exc.HTTPBadRequest(detail=detail)

                kw[argument_name] = float(kw[argument_name])
            elif (
                param_type == "array"
                and param.get("items", {}).get("type", None) == "string"
            ):
                if kw[argument_name] is None:
                    kw[argument_name] = []
                elif isinstance(kw[argument_name], (list, tuple)):
                    # argument is already an array
                    pass
                else:
                    kw[argument_name] = kw[argument_name].split(",")

    # Call the controller
    try:
        func = op_resolver(endpoint["operationId"])
    except Exception as e:
        LOG.exception(
            'Failed to load controller for operation "%s": %s'
            % (endpoint["operationId"], str(e))
        )
        raise e

    try:
        resp = func(**kw)
    except Exception as e:
        LOG.exception(
            'Failed to call controller function "%s" for operation "%s": %s'
            % (func.__name__, endpoint["operationId"], str(e))
        )
        raise e

    # Handle response
    if resp is None:
        resp = Response()

    if not hasattr(resp, "__call__"):
        resp = Response(json=resp)

    responses = endpoint.get("responses", {})
    response_spec = responses.get(str(resp.status_code), None)
    default_response_spec = responses.get("default", None)

    if not response_spec and default_response_spec:
        LOG.debug(
            'No custom response spec found for endpoint "%s", using a default one'
            % (endpoint["operationId"])
        )
        response_spec_name = "default"
    else:
        response_spec_name = str(resp.status_code)

    response_spec = response_spec or default_response_spec

    if response_spec and "schema" in response_spec:
        LOG.debug(
            'Using response spec "%s" for endpoint %s and status code %s'
            % (response_spec_name, endpoint["operationId"], resp.status_code)
        )

        try:
            validator = CustomValidator(
                response_spec["schema"], resolver=self.spec_resolver
            )
            validator.validate(resp.json)
        except (jsonschema.ValidationError, ValueError):
            LOG.exception("Response validation failed.")
            resp.headers.add("Warning", '199 OpenAPI "Response validation failed"')
    else:
        LOG.debug(
            'No response spec found for endpoint "%s"' % (endpoint["operationId"])
        )

    if cookie_token:
        resp.headerlist.append(("Set-Cookie", cookie_token))

    return resp


# Traceback (ValidationError)
2018-07-19 18:31:46,668 INFO [-] f590ed84-3e54-4634-89f2-120eb3f956e5 - POST /tokens with query={} (remote_addr='127.0.0.1',method='POST',request_id='f590ed84-3e54-4634-89f2-120eb3f956e5',query={},path='/tokens')
2018-07-19 18:31:46,669 DEBUG [-] Recieved call with WebOb: POST /tokens HTTP/1.0
Accept: */*
Authorization: Basic c3QyYWRtaW46Q2hAbmdlTWU=
Content-Type: text/plain
Host: 127.0.0.1:9100
User-Agent: curl/7.47.0
X-Request-Id: f590ed84-3e54-4634-89f2-120eb3f956e5
2018-07-19 18:31:46,669 DEBUG [-] Match path: /tokens
2018-07-19 18:31:46,670 DEBUG [-] Parsed endpoint: {'x-parameters': [{'in': 'environ', 'type': 'string', 'description': 'source of the request', 'name': 'remote_addr'}, {'in': 'environ', 'type': 'string', 'description': 'set externally to indicate user identity in case of proxy auth', 'name': 'remote_user'}], 'responses': {'201': {'headers': {'x-api-url': {'type': 'string'}}, 'schema': {'$ref': '#/definitions/Token'}, 'examples': {'application/json': {'user': 'st2admin', 'token': '5e86421776f946e98faea36c29e5a7c7', 'expiry': '2016-05-28T12:39:28.650231Z', 'id': '574840001878c10d0b6e8fbf', 'metadata': {}}}, 'description': 'New token has been created'}, 'default': {'description': 'Unexpected error', 'schema': {'$ref': '#/definitions/Error'}}, '401': {'schema': {'$ref': '#/definitions/Error'}, 'examples': {'application/json': {'faultstring': 'Invalid or missing credentials'}}, 'description': 'Invalid or missing credentials has been provided'}}, 'parameters': [{'in': 'header', 'type': 'string', 'description': 'base64 encoded string containing login and password', 'name': 'authorization'}, {'in': 'header', 'type': 'string', 'description': 'set externally to indicate real source of the request', 'name': 'x-forwarded-for'}, {'in': 'body', 'description': 'Lifespan of the token', 'name': 'request', 'schema': {'$ref': '#/definitions/TokenRequest'}}], 'x-log-result': False, 'security': [], 'description': 'Authenticates a user with `Authorization` header and returns a `token`\nobject.\n', 'operationId': 'st2auth.controllers.v1.auth:token_controller.post'}
2018-07-19 18:31:46,670 DEBUG [-] Parsed path_vars: {}
2018-07-19 18:31:46,700 DEBUG [-] API call failed: Traceback (most recent call last):
File "/opt/stackstorm/st2/local/lib/python2.7/site-packages/st2common/router.py", line 372, in __call__
CustomValidator(schema, resolver=self.spec_resolver).validate(data)
File "/opt/stackstorm/st2/local/lib/python2.7/site-packages/jsonschema/validators.py", line 130, in validate
raise error
ValidationError: '{}' is not of type 'object'

Failed validating 'type' in schema:
{'properties': {'ttl': {'minimum': 1, 'type': ['integer', 'null']}},
'type': 'object'}

On instance:
'{}'
(exception_message="'{}' is not of type 'object'",exception_data={'comment': 'Traceback (most recent call last):\n  File "/opt/stackstorm/st2/local/lib/python2.7/site-packages/st2common/router.py", line 372, in __call__\n    CustomValidator(schema, resolver=self.spec_resolver).validate(data)\n  File "/opt/stackstorm/st2/local/lib/python2.7/site-packages/jsonschema/validators.py", line 130, in validate\n    raise error\nValidationError: \'{}\' is not of type \'object\'\n\nFailed validating \'type\' in schema:\n    {\'properties\': {\'ttl\': {\'minimum\': 1, \'type\': [\'integer\', \'null\']}},\n     \'type\': \'object\'}\n\nOn instance:\n    \'{}\'\n', '_headers': None, 'conditional_response': False, 'detail': "'{}' is not of type 'object'", '_app_iter': [''], '_status': '400 Bad Request', '_headerlist': [('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')]},exception_class='HTTPBadRequest')
2018-07-19 18:31:46,701 DEBUG [-] Match path: /tokens
2018-07-19 18:31:46,701 INFO [-] f590ed84-3e54-4634-89f2-120eb3f956e5 - 400 53 33.332ms (content_length=53,request_id='f590ed84-3e54-4634-89f2-120eb3f956e5',runtime=33.332,remote_addr='127.0.0.1',status=400,method='POST',path='/tokens')

# Fixed code
def __call__(self, req):
    """
    The method is invoked on every request and shows the lifecycle of the request received from
    the middleware.

    Although some middleware may use parts of the API spec, it is safe to assume that if you're
    looking for the particular spec property handler, it's most likely a part of this method.

    At the time of writing, the only property being utilized by middleware was `x-log-result`.
    """
    LOG.debug("Received call with WebOb: %s", req)
    endpoint, path_vars = self.match(req)
    LOG.debug("Parsed endpoint: %s", endpoint)
    LOG.debug("Parsed path_vars: %s", path_vars)

    context = copy.copy(getattr(self, "mock_context", {}))
    cookie_token = None

    # Handle security
    if "security" in endpoint:
        security = endpoint.get("security")
    else:
        security = self.spec.get("security", [])

    if self.auth and security:
        try:
            security_definitions = self.spec.get("securityDefinitions", {})
            for statement in security:
                declaration, options = statement.copy().popitem()
                definition = security_definitions[declaration]

                if definition["type"] == "apiKey":
                    if definition["in"] == "header":
                        token = req.headers.get(definition["name"])
                    elif definition["in"] == "query":
                        token = req.GET.get(definition["name"])
                    elif definition["in"] == "cookie":
                        token = req.cookies.get(definition["name"])
                    else:
                        token = None

                    if token:
                        auth_func = op_resolver(definition["x-operationId"])
                        auth_resp = auth_func(token)

                        # Include information on how user authenticated inside the context
                        if "auth-token" in definition["name"].lower():
                            auth_method = "authentication token"
                        elif "api-key" in definition["name"].lower():
                            auth_method = "API key"

                        context["user"] = User.get_by_name(auth_resp.user)
                        context["auth_info"] = {
                            "method": auth_method,
                            "location": definition["in"],
                        }

                        # Also include token expiration time when authenticated via auth token
                        if "auth-token" in definition["name"].lower():
                            context["auth_info"]["token_expire"] = auth_resp.expiry

                        if "x-set-cookie" in definition:
                            max_age = (
                                auth_resp.expiry - date_utils.get_datetime_utc_now()
                            )
                            cookie_token = cookies.make_cookie(
                                definition["x-set-cookie"],
                                token,
                                max_age=max_age,
                                httponly=True,
                            )

                        break

            if "user" not in context:
                raise auth_exc.NoAuthSourceProvidedError(
                    "One of Token or API key required."
                )
        except (
            auth_exc.NoAuthSourceProvidedError,
            auth_exc.MultipleAuthSourcesError,
        ) as e:
            LOG.error(str(e))
            return abort_unauthorized(str(e))
        except auth_exc.TokenNotProvidedError as e:
            LOG.exception("Token is not provided.")
            return abort_unauthorized(str(e))
        except auth_exc.TokenNotFoundError as e:
            LOG.exception("Token is not found.")
            return abort_unauthorized(str(e))
        except auth_exc.TokenExpiredError as e:
            LOG.exception("Token has expired.")
            return abort_unauthorized(str(e))
        except auth_exc.ApiKeyNotProvidedError as e:
            LOG.exception("API key is not provided.")
            return abort_unauthorized(str(e))
        except auth_exc.ApiKeyNotFoundError as e:
            LOG.exception("API key is not found.")
            return abort_unauthorized(str(e))
        except auth_exc.ApiKeyDisabledError as e:
            LOG.exception("API key is disabled.")
            return abort_unauthorized(str(e))

        if cfg.CONF.rbac.enable:
            user_db = context["user"]

            permission_type = endpoint.get("x-permissions", None)
            if permission_type:
                resolver = resolvers.get_resolver_for_permission_type(permission_type)
                has_permission = resolver.user_has_permission(user_db, permission_type)

                if not has_permission:
                    raise rbac_exc.ResourceTypeAccessDeniedError(
                        user_db, permission_type
                    )

    # Collect parameters
    kw = {}
    for param in endpoint.get("parameters", []) + endpoint.get("x-parameters", []):
        name = param["name"]
        argument_name = param.get("x-as", None) or name
        source = param["in"]
        default = param.get("default", None)

        # Collecting params from different sources
        if source == "query":
            kw[argument_name] = req.GET.get(name, default)
        elif source == "path":
            kw[argument_name] = path_vars[name]
        elif source == "header":
            kw[argument_name] = req.headers.get(name, default)
        elif source == "formData":
            kw[argument_name] = req.POST.get(name, default)
        elif source == "environ":
            kw[argument_name] = req.environ.get(name.upper(), default)
        elif source == "context":
            kw[argument_name] = context.get(name, default)
        elif source == "request":
            kw[argument_name] = getattr(req, name)
        elif source == "body":
            content_type = req.headers.get("Content-Type", "application/json")
            content_type = parse_content_type_header(content_type=content_type)[0]
            schema = param["schema"]

            # NOTE: HACK: Workaround for eventlet wsgi server which sets Content-Type to
            # text/plain if Content-Type is not provided in the request.
            # All ouf our API endpoints except /exp/validation/mistral expect application/json
            # so we explicitly set it to that if not provided (set to text/plain by the base
            # http server) and if it's not /exp/validation/mistral API endpoint
            if not self.is_gunicorn and content_type == "text/plain":
                operation_id = endpoint["operationId"]

                if "mistral_validation_controller" not in operation_id:
                    content_type = "application/json"

            # Note: We also want to perform validation if no body is explicitly provided - in a
            # lot of POST, PUT scenarios, body is mandatory
            if not req.body and content_type == "application/json":
                req.body = b"{}"

            try:
                if content_type == "application/json":
                    data = req.json
                elif content_type == "text/plain":
                    data = req.body
                elif content_type in [
                    "application/x-www-form-urlencoded",
                    "multipart/form-data",
                ]:
                    data = urlparse.parse_qs(req.body)
                else:
                    raise ValueError('Unsupported Content-Type: "%s"' % (content_type))
            except Exception as e:
                detail = "Failed to parse request body: %s" % str(e)
                raise exc.HTTPBadRequest(detail=detail)

            # Special case for Python 3
            if (
                six.PY3
                and content_type == "text/plain"
                and isinstance(data, six.binary_type)
            ):
                # Convert bytes to text type (string / unicode)
                data = data.decode("utf-8")

            try:
                CustomValidator(schema, resolver=self.spec_resolver).validate(data)
            except (jsonschema.ValidationError, ValueError) as e:
                raise exc.HTTPBadRequest(
                    detail=e.message, comment=traceback.format_exc()
                )

            if content_type == "text/plain":
                kw[argument_name] = data
            else:

                class Body(object):
                    def __init__(self, **entries):
                        self.__dict__.update(entries)

                ref = schema.get("$ref", None)
                if ref:
                    with self.spec_resolver.resolving(ref) as resolved:
                        schema = resolved

                if "x-api-model" in schema:
                    input_type = schema.get("type", [])
                    Model = op_resolver(schema["x-api-model"])

                    if input_type and not isinstance(input_type, (list, tuple)):
                        input_type = [input_type]

                    # root attribute is not an object, we need to use wrapper attribute to
                    # make it work with **kwarg expansion
                    if input_type and "array" in input_type:
                        data = {"data": data}

                    instance = self._get_model_instance(model_cls=Model, data=data)

                    # Call validate on the API model - note we should eventually move all
                    # those model schema definitions into openapi.yaml
                    try:
                        instance = instance.validate()
                    except (jsonschema.ValidationError, ValueError) as e:
                        raise exc.HTTPBadRequest(
                            detail=e.message, comment=traceback.format_exc()
                        )
                else:
                    LOG.debug(
                        "Missing x-api-model definition for %s, using generic Body "
                        "model." % (endpoint["operationId"])
                    )
                    model = Body
                    instance = self._get_model_instance(model_cls=model, data=data)

                kw[argument_name] = instance

        # Making sure all required params are present
        required = param.get("required", False)
        if required and kw[argument_name] is None:
            detail = 'Required parameter "%s" is missing' % name
            raise exc.HTTPBadRequest(detail=detail)

        # Validating and casting param types
        param_type = param.get("type", None)
        if kw[argument_name] is not None:
            if param_type == "boolean":
                positive = ("true", "1", "yes", "y")
                negative = ("false", "0", "no", "n")

                if str(kw[argument_name]).lower() not in positive + negative:
                    detail = 'Parameter "%s" is not of type boolean' % argument_name
                    raise exc.HTTPBadRequest(detail=detail)

                kw[argument_name] = str(kw[argument_name]).lower() in positive
            elif param_type == "integer":
                regex = r"^-?[0-9]+$"

                if not re.search(regex, str(kw[argument_name])):
                    detail = 'Parameter "%s" is not of type integer' % argument_name
                    raise exc.HTTPBadRequest(detail=detail)

                kw[argument_name] = int(kw[argument_name])
            elif param_type == "number":
                regex = r"^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$"

                if not re.search(regex, str(kw[argument_name])):
                    detail = 'Parameter "%s" is not of type float' % argument_name
                    raise exc.HTTPBadRequest(detail=detail)

                kw[argument_name] = float(kw[argument_name])
            elif (
                param_type == "array"
                and param.get("items", {}).get("type", None) == "string"
            ):
                if kw[argument_name] is None:
                    kw[argument_name] = []
                elif isinstance(kw[argument_name], (list, tuple)):
                    # argument is already an array
                    pass
                else:
                    kw[argument_name] = kw[argument_name].split(",")

    # Call the controller
    try:
        func = op_resolver(endpoint["operationId"])
    except Exception as e:
        LOG.exception(
            'Failed to load controller for operation "%s": %s'
            % (endpoint["operationId"], str(e))
        )
        raise e

    try:
        resp = func(**kw)
    except Exception as e:
        LOG.exception(
            'Failed to call controller function "%s" for operation "%s": %s'
            % (func.__name__, endpoint["operationId"], str(e))
        )
        raise e

    # Handle response
    if resp is None:
        resp = Response()

    if not hasattr(resp, "__call__"):
        resp = Response(json=resp)

    responses = endpoint.get("responses", {})
    response_spec = responses.get(str(resp.status_code), None)
    default_response_spec = responses.get("default", None)

    if not response_spec and default_response_spec:
        LOG.debug(
            'No custom response spec found for endpoint "%s", using a default one'
            % (endpoint["operationId"])
        )
        response_spec_name = "default"
    else:
        response_spec_name = str(resp.status_code)

    response_spec = response_spec or default_response_spec

    if response_spec and "schema" in response_spec:
        LOG.debug(
            'Using response spec "%s" for endpoint %s and status code %s'
            % (response_spec_name, endpoint["operationId"], resp.status_code)
        )

        try:
            validator = CustomValidator(
                response_spec["schema"], resolver=self.spec_resolver
            )
            validator.validate(resp.json)
        except (jsonschema.ValidationError, ValueError):
            LOG.exception("Response validation failed.")
            resp.headers.add("Warning", '199 OpenAPI "Response validation failed"')
    else:
        LOG.debug(
            'No response spec found for endpoint "%s"' % (endpoint["operationId"])
        )

    if cookie_token:
        resp.headerlist.append(("Set-Cookie", cookie_token))

    return resp