Skip to content

Commit 456e518

Browse files
author
Ran Isenberg
committed
a
1 parent 689e8b3 commit 456e518

File tree

10 files changed

+152
-31
lines changed

10 files changed

+152
-31
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ integration:
5252
e2e:
5353
poetry run pytest tests/e2e --cov-config=.coveragerc --cov=service --cov-report xml
5454

55-
pr: deps pre-commit complex lint lint-docs unit deploy coverage-tests e2e
55+
pr: deps format pre-commit complex lint lint-docs unit deploy coverage-tests e2e
5656

5757
coverage-tests:
5858
poetry run pytest tests/unit tests/integration --cov-config=.coveragerc --cov=service --cov-report xml

docs/swagger/openapi.json

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
{
2+
"openapi": "3.0.0",
3+
"info": {
4+
"title": "AWS Lambda Handler Cookbook",
5+
"version": "1.0.0"
6+
},
7+
"servers": [
8+
{
9+
"url": "/prod"
10+
}
11+
],
12+
"paths": {
13+
"/api/orders": {
14+
"post": {
15+
"tags": [
16+
"CRUD"
17+
],
18+
"summary": "Create an order",
19+
"description": "Create an order identified by the body payload`",
20+
"operationId": "handle_create_order_api_orders_post",
21+
"requestBody": {
22+
"content": {
23+
"application/json": {
24+
"schema": {
25+
"$ref": "#/components/schemas/CreateOrderRequest"
26+
}
27+
}
28+
},
29+
"required": true
30+
},
31+
"responses": {
32+
"200": {
33+
"description": "The created order",
34+
"content": {
35+
"application/json": {
36+
"schema": {
37+
"$ref": "#/components/schemas/CreateOrderOutput"
38+
}
39+
}
40+
}
41+
},
42+
"501": {
43+
"description": "internal server error",
44+
"content": {
45+
"application/json": {
46+
"schema": {
47+
"properties": {
48+
"error": {
49+
"type": "string",
50+
"title": "Error",
51+
"default": "internal server error"
52+
}
53+
},
54+
"type": "object",
55+
"title": "InternalServerErrorOutput"
56+
}
57+
}
58+
}
59+
}
60+
}
61+
}
62+
}
63+
},
64+
"components": {
65+
"schemas": {
66+
"CreateOrderOutput": {
67+
"properties": {
68+
"name": {
69+
"type": "string",
70+
"maxLength": 20,
71+
"minLength": 1,
72+
"title": "Name"
73+
},
74+
"item_count": {
75+
"type": "integer",
76+
"exclusiveMinimum": 0,
77+
"title": "Item Count"
78+
},
79+
"id": {
80+
"type": "string",
81+
"maxLength": 36,
82+
"minLength": 36,
83+
"title": "Id"
84+
}
85+
},
86+
"type": "object",
87+
"required": [
88+
"name",
89+
"item_count",
90+
"id"
91+
],
92+
"title": "CreateOrderOutput"
93+
},
94+
"CreateOrderRequest": {
95+
"properties": {
96+
"customer_name": {
97+
"type": "string",
98+
"maxLength": 20,
99+
"minLength": 1,
100+
"title": "Customer Name"
101+
},
102+
"order_item_count": {
103+
"type": "integer",
104+
"exclusiveMinimum": 0,
105+
"title": "Order Item Count"
106+
}
107+
},
108+
"type": "object",
109+
"required": [
110+
"customer_name",
111+
"order_item_count"
112+
],
113+
"title": "CreateOrderRequest"
114+
}
115+
}
116+
}
117+
}

docs/swagger/swagger.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
title: Swagger
3+
description: Swagger Documentation
4+
---
5+
6+
!!swagger openapi.json!!

mkdocs.yml

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ nav:
99
- Getting Started: getting_started.md
1010
- CDK: cdk.md
1111
- Pipeline: pipeline.md
12+
- Swagger: swagger/swagger.md
1213
- Best Practices:
1314
- best_practices/logger.md
1415
- best_practices/tracer.md
@@ -80,19 +81,7 @@ copyright: Copyright © 2023 Ran Isenberg
8081
plugins:
8182
- git-revision-date
8283
- search
83-
- glightbox:
84-
touchNavigation: true
85-
loop: false
86-
effect: zoom
87-
slide_effect: slide
88-
width: 100%
89-
height: auto
90-
zoomable: true
91-
draggable: true
92-
skip_classes:
93-
- custom-skip-class-name
94-
auto_caption: false
95-
caption_position: bottom
84+
- render_swagger
9685
extra_css:
9786
- stylesheets/extra.css
9887

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ mypy = "*"
5555
types-requests = "*"
5656
toml = "*"
5757
poetry-plugin-export = "*"
58+
mkdocs-render-swagger-plugin = "*"
5859

5960
[tool.pytest.ini_options]
6061
testpaths = "tests"

service/handlers/handle_create_order.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from service.handlers.utils.rest_api_resolver import ORDERS_PATH, app
1515
from service.logic.create_order import create_order
1616
from service.models.input import CreateOrderRequest
17-
from service.models.output import CreateOrderOutput
17+
from service.models.output import CreateOrderOutput, InternalServerErrorOutput
1818

1919

2020
@app.post(
@@ -23,12 +23,18 @@
2323
description='Create an order identified by the body payload`',
2424
response_description='The created order',
2525
responses={
26-
200: CreateOrderOutput.model_json_schema(),
27-
501: {'error': 'internal server error'},
26+
200: {
27+
'description': 'The created order',
28+
'content': {'application/json': {'schema': {'$ref': '#/components/schemas/CreateOrderOutput'}}},
29+
},
30+
501: {
31+
'description': 'Internal server error',
32+
'content': {'application/json': {'schema': InternalServerErrorOutput.model_json_schema()}},
33+
},
2834
},
29-
tags=['CRUD'],
35+
tags=['CRUD', 'Orders'],
3036
)
31-
def handle_create_order(create_input: Annotated[CreateOrderRequest, Body(embed=False, media_type='application/json')]) -> dict[str, Any]:
37+
def handle_create_order(create_input: Annotated[CreateOrderRequest, Body(embed=False, media_type='application/json')]) -> CreateOrderOutput:
3238
env_vars: MyHandlerEnvVars = get_environment_variables(model=MyHandlerEnvVars)
3339
logger.debug('environment variables', env_vars=env_vars.model_dump())
3440
logger.info('got create order request', order=create_input.model_dump())
@@ -44,7 +50,7 @@ def handle_create_order(create_input: Annotated[CreateOrderRequest, Body(embed=F
4450
)
4551

4652
logger.info('finished handling create order request')
47-
return response.model_dump()
53+
return response
4854

4955

5056
@init_environment_variables(model=MyHandlerEnvVars)
Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import json
21
from http import HTTPStatus
32

43
from aws_lambda_powertools.event_handler import APIGatewayRestResolver, Response, content_types
54

65
from service.handlers.utils.observability import logger
76
from service.models.exceptions import DynamicConfigurationException, InternalServerException
7+
from service.models.output import InternalServerErrorOutput
88

99
ORDERS_PATH = '/api/orders/'
1010

@@ -16,17 +16,13 @@
1616
def handle_dynamic_config_error(ex: DynamicConfigurationException): # receives exception raised
1717
logger.exception('failed to load dynamic configuration from AppConfig')
1818
return Response(
19-
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
20-
content_type=content_types.APPLICATION_JSON,
21-
body=json.dumps({'error': 'internal server error'}),
19+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, content_type=content_types.APPLICATION_JSON, body=InternalServerErrorOutput().model_dump()
2220
)
2321

2422

2523
@app.exception_handler(InternalServerException)
2624
def handle_internal_server_error(ex: InternalServerException): # receives exception raised
2725
logger.exception('finished handling request with internal error')
2826
return Response(
29-
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
30-
content_type=content_types.APPLICATION_JSON,
31-
body=json.dumps({'error': 'internal server error'}),
27+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, content_type=content_types.APPLICATION_JSON, body=InternalServerErrorOutput().model_dump()
3228
)

service/models/output.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from pydantic import BaseModel
2+
13
from service.models.order import Order
24

35

@@ -6,3 +8,7 @@
68
# The output can be a subject of what order contains, i.e just the id
79
class CreateOrderOutput(Order):
810
pass
11+
12+
13+
class InternalServerErrorOutput(BaseModel):
14+
error: str = 'internal server error'

tests/e2e/test_create_order.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,5 @@ def test_handler_bad_request(api_gw_url):
4949
# When: Making a POST request to the API Gateway URL
5050
response = requests.post(api_gw_url, data=body_str)
5151

52-
# Then: Validate the response status is 400 BAD REQUEST and body is empty
53-
assert response.status_code == HTTPStatus.BAD_REQUEST
54-
body_dict = json.loads(response.text)
55-
assert body_dict == {'error': 'invalid input'}
52+
# Then: Validate the response status is 422 UNPROCESSABLE_ENTITY
53+
assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY

tests/integration/test_create_order.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ def test_internal_server_error(mocker, table_name: str):
8989

9090
# Then: Ensure the response reflects an internal server error
9191
assert response['statusCode'] == HTTPStatus.INTERNAL_SERVER_ERROR
92+
body_dict = json.loads(response['body'])
93+
assert body_dict == {'error': 'internal server error'}
9294

9395

9496
def test_handler_bad_request(mocker):

0 commit comments

Comments
 (0)