Skip to content

Commit 7482311

Browse files
kazu728anafalcaoleandrodamascena
authored
feat(event_handler): add extras HTTP Error Code Exceptions (#6454)
* feat(event_handler): add error status * update doc with new errors * fix typo * change class description --------- Co-authored-by: Ana Falcão <[email protected]> Co-authored-by: Ana Falcao <[email protected]> Co-authored-by: Leandro Damascena <[email protected]>
1 parent 1822c9f commit 7482311

File tree

4 files changed

+122
-7
lines changed

4 files changed

+122
-7
lines changed

aws_lambda_powertools/event_handler/exceptions.py

+33-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33

44
class ServiceError(Exception):
5-
"""API Gateway and ALB HTTP Service Error"""
5+
"""Powertools class HTTP Service Error"""
66

77
def __init__(self, status_code: int, msg: str):
88
"""
@@ -18,28 +18,56 @@ def __init__(self, status_code: int, msg: str):
1818

1919

2020
class BadRequestError(ServiceError):
21-
"""API Gateway and ALB Bad Request Error (400)"""
21+
"""Powertools class Bad Request Error (400)"""
2222

2323
def __init__(self, msg: str):
2424
super().__init__(HTTPStatus.BAD_REQUEST, msg)
2525

2626

2727
class UnauthorizedError(ServiceError):
28-
"""API Gateway and ALB Unauthorized Error (401)"""
28+
"""Powertools class Unauthorized Error (401)"""
2929

3030
def __init__(self, msg: str):
3131
super().__init__(HTTPStatus.UNAUTHORIZED, msg)
3232

3333

34+
class ForbiddenError(ServiceError):
35+
"""Powertools class Forbidden Error (403)"""
36+
37+
def __init__(self, msg: str):
38+
super().__init__(HTTPStatus.FORBIDDEN, msg)
39+
40+
3441
class NotFoundError(ServiceError):
35-
"""API Gateway and ALB Not Found Error (404)"""
42+
"""Powertools class Not Found Error (404)"""
3643

3744
def __init__(self, msg: str = "Not found"):
3845
super().__init__(HTTPStatus.NOT_FOUND, msg)
3946

4047

48+
class RequestTimeoutError(ServiceError):
49+
"""Powertools class Request Timeout Error (408)"""
50+
51+
def __init__(self, msg: str):
52+
super().__init__(HTTPStatus.REQUEST_TIMEOUT, msg)
53+
54+
55+
class RequestEntityTooLargeError(ServiceError):
56+
"""Powertools class Request Entity Too Large Error (413)"""
57+
58+
def __init__(self, msg: str):
59+
super().__init__(HTTPStatus.REQUEST_ENTITY_TOO_LARGE, msg)
60+
61+
4162
class InternalServerError(ServiceError):
42-
"""API Gateway and ALB Internal Server Error (500)"""
63+
"""Powertools class Internal Server Error (500)"""
4364

4465
def __init__(self, message: str):
4566
super().__init__(HTTPStatus.INTERNAL_SERVER_ERROR, message)
67+
68+
69+
class ServiceUnavailableError(ServiceError):
70+
"""Powertools class Service Unavailable Error (503)"""
71+
72+
def __init__(self, msg: str):
73+
super().__init__(HTTPStatus.SERVICE_UNAVAILABLE, msg)

docs/core/event_handler/api_gateway.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -595,9 +595,9 @@ You can easily raise any HTTP Error back to the client using `ServiceError` exce
595595
???+ info
596596
If you need to send custom headers, use [Response](#fine-grained-responses) class instead.
597597

598-
We provide pre-defined errors for the most popular ones such as HTTP 400, 401, 404, 500.
598+
We provide pre-defined errors for the most popular ones based on [AWS Lambda API Reference Common Erros](https://docs.aws.amazon.com/lambda/latest/api/CommonErrors.html).
599599

600-
```python hl_lines="6-11 23 28 33 38 43" title="Raising common HTTP Status errors (4xx, 5xx)"
600+
```python hl_lines="7-15 27 32 37 42 47 52 57 62 67" title="Raising common HTTP Status errors (4xx, 5xx)"
601601
--8<-- "examples/event_handler_rest/src/raising_http_errors.py"
602602
```
603603

examples/event_handler_rest/src/raising_http_errors.py

+23
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
66
from aws_lambda_powertools.event_handler.exceptions import (
77
BadRequestError,
8+
ForbiddenError,
89
InternalServerError,
910
NotFoundError,
11+
RequestEntityTooLargeError,
12+
RequestTimeoutError,
1013
ServiceError,
14+
ServiceUnavailableError,
1115
UnauthorizedError,
1216
)
1317
from aws_lambda_powertools.logging import correlation_paths
@@ -28,21 +32,40 @@ def unauthorized_error():
2832
raise UnauthorizedError("Unauthorized") # HTTP 401
2933

3034

35+
@app.get(rule="/forbidden-error")
36+
def forbidden_error():
37+
raise ForbiddenError("Access denied") # HTTP 403
38+
39+
3140
@app.get(rule="/not-found-error")
3241
def not_found_error():
3342
raise NotFoundError # HTTP 404
3443

3544

45+
@app.get(rule="/request-timeout-error")
46+
def request_timeout_error():
47+
raise RequestTimeoutError("Request timed out") # HTTP 408
48+
49+
3650
@app.get(rule="/internal-server-error")
3751
def internal_server_error():
3852
raise InternalServerError("Internal server error") # HTTP 500
3953

4054

55+
@app.get(rule="/request-entity-too-large-error")
56+
def request_entity_too_large_error():
57+
raise RequestEntityTooLargeError("Request payload too large") # HTTP 413
58+
59+
4160
@app.get(rule="/service-error", cors=True)
4261
def service_error():
4362
raise ServiceError(502, "Something went wrong!")
4463

4564

65+
@app.get(rule="/service-unavailable-error")
66+
def service_unavailable_error():
67+
raise ServiceUnavailableError("Service is temporarily unavailable") # HTTP 503
68+
4669
@app.get("/todos")
4770
@tracer.capture_method
4871
def get_todos():

tests/functional/event_handler/required_dependencies/test_api_gateway.py

+64
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,13 @@
2626
)
2727
from aws_lambda_powertools.event_handler.exceptions import (
2828
BadRequestError,
29+
ForbiddenError,
2930
InternalServerError,
3031
NotFoundError,
32+
RequestEntityTooLargeError,
33+
RequestTimeoutError,
3134
ServiceError,
35+
ServiceUnavailableError,
3236
UnauthorizedError,
3337
)
3438
from aws_lambda_powertools.shared import constants
@@ -873,6 +877,21 @@ def unauthorized_error():
873877
expected = {"statusCode": 401, "message": "Unauthorized"}
874878
assert result["body"] == json_dump(expected)
875879

880+
# GIVEN a ForbiddenError
881+
@app.get(rule="/forbidden-error", cors=False)
882+
def forbidden_error():
883+
raise ForbiddenError("Access denied")
884+
885+
# WHEN calling the handler
886+
# AND path is /forbidden-error
887+
result = app({"path": "/forbidden-error", "httpMethod": "GET"}, None)
888+
# THEN return the forbidden error response
889+
# AND status code equals 403
890+
assert result["statusCode"] == 403
891+
assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON]
892+
expected = {"statusCode": 403, "message": "Access denied"}
893+
assert result["body"] == json_dump(expected)
894+
876895
# GIVEN an NotFoundError
877896
@app.get(rule="/not-found-error", cors=False)
878897
def not_found_error():
@@ -888,6 +907,36 @@ def not_found_error():
888907
expected = {"statusCode": 404, "message": "Not found"}
889908
assert result["body"] == json_dump(expected)
890909

910+
# GIVEN a RequestTimeoutError
911+
@app.get(rule="/request-timeout-error", cors=False)
912+
def request_timeout_error():
913+
raise RequestTimeoutError("Request timed out")
914+
915+
# WHEN calling the handler
916+
# AND path is /request-timeout-error
917+
result = app({"path": "/request-timeout-error", "httpMethod": "GET"}, None)
918+
# THEN return the request timeout error response
919+
# AND status code equals 408
920+
assert result["statusCode"] == 408
921+
assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON]
922+
expected = {"statusCode": 408, "message": "Request timed out"}
923+
assert result["body"] == json_dump(expected)
924+
925+
# GIVEN a RequestEntityTooLargeError
926+
@app.get(rule="/request-entity-too-large-error", cors=False)
927+
def request_entity_too_large_error():
928+
raise RequestEntityTooLargeError("Request payload too large")
929+
930+
# WHEN calling the handler
931+
# AND path is /request-entity-too-large-error
932+
result = app({"path": "/request-entity-too-large-error", "httpMethod": "GET"}, None)
933+
# THEN return the request entity too large error response
934+
# AND status code equals 413
935+
assert result["statusCode"] == 413
936+
assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON]
937+
expected = {"statusCode": 413, "message": "Request payload too large"}
938+
assert result["body"] == json_dump(expected)
939+
891940
# GIVEN an InternalServerError
892941
@app.get(rule="/internal-server-error", cors=False)
893942
def internal_server_error():
@@ -903,6 +952,21 @@ def internal_server_error():
903952
expected = {"statusCode": 500, "message": "Internal server error"}
904953
assert result["body"] == json_dump(expected)
905954

955+
# GIVEN a ServiceUnavailableError
956+
@app.get(rule="/service-unavailable-error", cors=False)
957+
def service_unavailable_error():
958+
raise ServiceUnavailableError("Service is temporarily unavailable")
959+
960+
# WHEN calling the handler
961+
# AND path is /service-unavailable-error
962+
result = app({"path": "/service-unavailable-error", "httpMethod": "GET"}, None)
963+
# THEN return the service unavailable error response
964+
# AND status code equals 503
965+
assert result["statusCode"] == 503
966+
assert result["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON]
967+
expected = {"statusCode": 503, "message": "Service is temporarily unavailable"}
968+
assert result["body"] == json_dump(expected)
969+
906970
# GIVEN an ServiceError with a custom status code
907971
@app.get(rule="/service-error")
908972
def service_error():

0 commit comments

Comments
 (0)