Skip to content

Commit d6d9462

Browse files
committed
Refactor get by ID journey
1 parent c366cc5 commit d6d9462

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+471
-315
lines changed

backend/src/controller/__init__.py

Whitespace-only changes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""Utility module for interacting with the AWS API Gateway event provided to controllers"""
2+
from typing import Optional
3+
4+
from aws_lambda_typing.events import APIGatewayProxyEventV1
5+
6+
from controller.constants import SUPPLIER_SYSTEM_HEADER_NAME
7+
from models.errors import UnauthorizedError
8+
from utils import dict_utils
9+
10+
11+
def get_path_parameter(event: APIGatewayProxyEventV1, param_name: str) -> str:
12+
return dict_utils.get_field(
13+
dict(event),
14+
"pathParameters",
15+
param_name,
16+
default=""
17+
)
18+
19+
20+
def get_supplier_system_header(event: APIGatewayProxyEventV1) -> str:
21+
"""Retrieves the supplier system header from the API Gateway event"""
22+
supplier_system: Optional[str] = dict_utils.get_field(dict(event), "headers", SUPPLIER_SYSTEM_HEADER_NAME)
23+
24+
if supplier_system is None:
25+
# SupplierSystem header must be provided for looking up permissions
26+
raise UnauthorizedError()
27+
28+
return supplier_system
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Utility module providing helper functions for dealing with response formats for AWS API Gateway"""
2+
import json
3+
from typing import Optional
4+
5+
6+
def create_response(
7+
status_code: int,
8+
body: Optional[dict | str] = None,
9+
headers: Optional[dict] = None
10+
):
11+
"""Creates response body as per Lambda -> API Gateway proxy integration"""
12+
if body is not None:
13+
if isinstance(body, dict):
14+
body = json.dumps(body)
15+
if headers:
16+
headers["Content-Type"] = "application/fhir+json"
17+
else:
18+
headers = {"Content-Type": "application/fhir+json"}
19+
20+
return {
21+
"statusCode": status_code,
22+
"headers": headers if headers else {},
23+
**({"body": body} if body else {}),
24+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""FHIR Controller constants"""
2+
3+
4+
SUPPLIER_SYSTEM_HEADER_NAME = "SupplierSystem"
5+
E_TAG_HEADER_NAME = "E-Tag"
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""Module for the global FHIR API exception handler"""
2+
import functools
3+
import uuid
4+
from typing import Callable, Type
5+
6+
from clients import logger
7+
from constants import GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE
8+
from controller.aws_apig_response_utils import create_response
9+
from models.errors import UnauthorizedVaxError, UnauthorizedError, ResourceNotFoundError, create_operation_outcome, \
10+
Severity, Code
11+
12+
13+
_CUSTOM_EXCEPTION_TO_STATUS_MAP: dict[Type[Exception], int] = {
14+
UnauthorizedError: 403,
15+
UnauthorizedVaxError: 403,
16+
ResourceNotFoundError: 404
17+
}
18+
19+
20+
def fhir_api_exception_handler(function: Callable) -> Callable:
21+
"""Decorator to handle any expected FHIR API exceptions or unexpected exception and provide a valid response to
22+
the client"""
23+
24+
@functools.wraps(function)
25+
def wrapper(*args, **kwargs):
26+
try:
27+
return function(*args, **kwargs)
28+
except tuple(_CUSTOM_EXCEPTION_TO_STATUS_MAP) as exc:
29+
status_code = _CUSTOM_EXCEPTION_TO_STATUS_MAP[type(exc)]
30+
return create_response(status_code=status_code, body=exc.to_operation_outcome())
31+
except Exception: # pylint: disable = broad-exception-caught
32+
logger.exception("Unhandled exception")
33+
server_error = create_operation_outcome(
34+
resource_id=str(uuid.uuid4()),
35+
severity=Severity.error,
36+
code=Code.server_error,
37+
diagnostics=GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE,
38+
)
39+
return create_response(500, server_error)
40+
41+
return wrapper
42+
File renamed without changes.
Lines changed: 55 additions & 90 deletions
Large diffs are not rendered by default.

backend/src/create_imms_handler.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import pprint
44
import uuid
55

6-
7-
from fhir_controller import FhirController, make_controller
6+
from controller.aws_apig_response_utils import create_response
7+
from controller.fhir_controller import FhirController, make_controller
88
from local_lambda import load_string
99
from models.errors import Severity, Code, create_operation_outcome
1010
from log_structure import function_info
@@ -29,7 +29,7 @@ def create_immunization(event, controller: FhirController):
2929
code=Code.server_error,
3030
diagnostics=GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE,
3131
)
32-
return FhirController.create_response(500, exp_error)
32+
return create_response(500, exp_error)
3333

3434

3535
if __name__ == "__main__":

backend/src/delete_imms_handler.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import pprint
44
import uuid
55

6-
7-
from fhir_controller import FhirController, make_controller
6+
from controller.aws_apig_response_utils import create_response
7+
from controller.fhir_controller import FhirController, make_controller
88
from models.errors import Severity, Code, create_operation_outcome
99
from log_structure import function_info
1010
from constants import GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE
@@ -28,7 +28,7 @@ def delete_immunization(event, controller: FhirController):
2828
code=Code.server_error,
2929
diagnostics=GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE,
3030
)
31-
return FhirController.create_response(500, exp_error)
31+
return create_response(500, exp_error)
3232

3333

3434
if __name__ == "__main__":

backend/src/fhir_repository.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,19 +102,17 @@ def get_immunization_by_identifier(self, identifier_pk: str) -> tuple[Optional[d
102102
else:
103103
return None, None
104104

105-
def get_immunization_by_id(self, imms_id: str) -> Optional[dict]:
105+
def get_immunization_by_id(self, imms_id: str) -> tuple[Optional[dict], Optional[str]]:
106106
response = self.table.get_item(Key={"PK": _make_immunization_pk(imms_id)})
107107
item = response.get("Item")
108108

109109
if not item:
110-
return None
110+
return None, None
111+
111112
if item.get("DeletedAt") and item["DeletedAt"] != "reinstated":
112-
return None
113+
return None, None
113114

114-
return {
115-
"Resource": json.loads(item["Resource"]),
116-
"Version": item["Version"]
117-
}
115+
return json.loads(item.get("Resource", {})), str(item.get("Version", ""))
118116

119117
def get_immunization_by_id_all(self, imms_id: str, imms: dict) -> Optional[dict]:
120118
response = self.table.get_item(Key={"PK": _make_immunization_pk(imms_id)})

0 commit comments

Comments
 (0)