aws / chalice

Python Serverless Microframework for AWS
Apache License 2.0
10.67k stars 1.01k forks source link

Missing custom API Gateway Responses #1633

Open reayd-chetwood opened 3 years ago

reayd-chetwood commented 3 years ago

Hi there,

We've got almost all the error responses customized accept for the ones in this function: https://github.com/aws/chalice/blob/8139001a7ebe27ae3d9f39f34db06b82ebb4d1e4/chalice/app.py#L1613-L1670

Is there a way to override these error responses? Or include them as Classes like the ChaliceViewErrors?

jamesls commented 3 years ago

FWIW, I don't think those exceptions are actually possible anymore. They might be raised in chalice local but shouldn't ever happen in an actual deployed app. Unsupported http methods will be rejected by API Gateway and there should always be a resource path in the request context (otherwise API gateway sent us an invalid event payload). Are you seeing these errors from your app? We might be able to just remove this...

reayd-chetwood commented 3 years ago

Yes we see these errors from our app. Are you saying they are raised somewhere else?

From what I read API gateway lambda proxies rely on the deployed app to return the response. We tried changing the error response mapping to the format we required in API gateway, but this was ignored.

jamesls commented 3 years ago

Are you saying they are raised somewhere else?

I'm saying that shouldn't be raising at all. I think that was an artifact from early versions of Chalice that don't really apply anymore. Although, I'm now realizing that the code snippet you linked is actually the whole method. I was just looking at the InternalServerError and MethodNotAllowedError which should never be raised from chalice. The errors after that (everything after here) are still relevant and can still be raised.

How are you customizing the error responses now? With custom middleware?

reayd-chetwood commented 3 years ago

We import all the Chalice errors along with some custom ones, and wrap the functions with a decorator:

from chalice import (
    Response,
    BadRequestError,
    UnauthorizedError,
    ForbiddenError,
    NotFoundError,
    ConflictError,
    TooManyRequestsError,
    ChaliceViewError,
)

def exception_handler(f):
    # type: (Callable[..., Any]) -> Callable[..., Any]
    """
    A function wrapper for catching all exceptions
    """

    @wraps(f)
    def inner(*args, **kwargs):
        # type: (*Any, **Any) -> Any
        try:
            return f(*args, **kwargs)
        except Exception as exc:
            if isinstance(exc, BadRequestError):
                error = BadRequestError("bad request")
                status_code = HTTPStatus.BAD_REQUEST
            elif isinstance(exc, UnauthorizedError):
                error = AuthorisationError("unauthorised")
                status_code = HTTPStatus.BAD_REQUEST
            elif isinstance(exc, ForbiddenError):
                error = AuthorisationError("forbidden")
                status_code = HTTPStatus.BAD_REQUEST
            elif isinstance(exc, NotFoundError):
                error = NotFoundError("not found")
                status_code = HTTPStatus.BAD_REQUEST
            elif isinstance(exc, ConflictError):
                error = ConflictError("bad request")
                status_code = HTTPStatus.BAD_REQUEST
            elif isinstance(exc, TooManyRequestsError):
                error = TooManyRequestsError("too many requests")
                status_code = HTTPStatus.BAD_REQUEST
            elif isinstance(exc, ChaliceViewError):
                error = ChaliceViewError("internal server error")
                status_code = HTTPStatus.INTERNAL_SERVER_ERROR
            elif isinstance(exc, AuthorisationError):
                error = AuthorisationError("unauthorised")
                status_code = HTTPStatus.BAD_REQUEST
            else:
                error = CustomViewError(exc)
                status_code = HTTPStatus.INTERNAL_SERVER_ERROR

            validated_error = CustomErrorResponse(**error.dto())

            return Response(
                body=validated_error.json(),
                status_code=status_code,
            )

    return inner