Skip to content

Commit 0f72b14

Browse files
committed
Address review comments
1 parent 60908f4 commit 0f72b14

File tree

14 files changed

+113
-106
lines changed

14 files changed

+113
-106
lines changed

lambdas/backend/src/controller/aws_apig_event_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from aws_lambda_typing.events import APIGatewayProxyEventV1
66

77
from controller.constants import E_TAG_HEADER_NAME, SUPPLIER_SYSTEM_HEADER_NAME
8-
from models.errors import ResourceVersionNotProvided, UnauthorizedError
8+
from models.errors import ResourceVersionNotProvidedError, UnauthorizedError
99
from utils import dict_utils
1010

1111

@@ -30,6 +30,6 @@ def get_resource_version_header(event: APIGatewayProxyEventV1) -> str:
3030
resource_version_header: Optional[str] = dict_utils.get_field(dict(event), "headers", E_TAG_HEADER_NAME)
3131

3232
if resource_version_header is None:
33-
raise ResourceVersionNotProvided(resource_type="Immunization")
33+
raise ResourceVersionNotProvidedError(resource_type="Immunization")
3434

3535
return resource_version_header

lambdas/backend/src/controller/fhir_api_exception_handler.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@
1010
IdentifierDuplicationError,
1111
InconsistentIdentifierError,
1212
InconsistentResourceVersion,
13-
InvalidStoredData,
1413
ResourceNotFoundError,
1514
)
1615
from constants import GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE
1716
from controller.aws_apig_response_utils import create_response
1817
from models.errors import (
1918
Code,
2019
InconsistentIdError,
21-
InvalidImmunizationId,
20+
InvalidImmunizationIdError,
2221
InvalidJsonError,
23-
InvalidResourceVersion,
24-
ResourceVersionNotProvided,
22+
InvalidResourceVersionError,
23+
InvalidStoredDataError,
24+
ResourceVersionNotProvidedError,
2525
Severity,
2626
UnauthorizedError,
2727
UnauthorizedVaxError,
@@ -33,17 +33,17 @@
3333
InconsistentResourceVersion: 400,
3434
InconsistentIdentifierError: 400, # Identifier refers to the local FHIR identifier composed of system and value.
3535
InconsistentIdError: 400, # ID refers to the top-level ID of the FHIR resource.
36-
InvalidImmunizationId: 400,
36+
InvalidImmunizationIdError: 400,
3737
InvalidJsonError: 400,
38-
InvalidResourceVersion: 400,
38+
InvalidResourceVersionError: 400,
3939
CustomValidationError: 400,
40-
ResourceVersionNotProvided: 400,
40+
ResourceVersionNotProvidedError: 400,
4141
UnauthorizedError: 403,
4242
UnauthorizedVaxError: 403,
4343
ResourceNotFoundError: 404,
4444
IdentifierDuplicationError: 422,
4545
UnhandledResponseError: 500,
46-
InvalidStoredData: 500,
46+
InvalidStoredDataError: 500,
4747
}
4848

4949

lambdas/backend/src/controller/fhir_controller.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222
from models.errors import (
2323
Code,
2424
InconsistentIdError,
25-
InvalidImmunizationId,
25+
InvalidImmunizationIdError,
2626
InvalidJsonError,
27-
InvalidResourceVersion,
28-
ParameterException,
27+
InvalidResourceVersionError,
28+
ParameterExceptionError,
2929
Severity,
3030
UnauthorizedError,
3131
UnauthorizedVaxError,
@@ -100,7 +100,7 @@ def get_immunization_by_id(self, aws_event: APIGatewayProxyEventV1) -> dict:
100100
imms_id = get_path_parameter(aws_event, "id")
101101

102102
if not self._is_valid_immunization_id(imms_id):
103-
raise InvalidImmunizationId()
103+
raise InvalidImmunizationIdError()
104104

105105
supplier_system = get_supplier_system_header(aws_event)
106106

@@ -132,10 +132,10 @@ def update_immunization(self, aws_event: APIGatewayProxyEventV1) -> dict:
132132
resource_version = get_resource_version_header(aws_event)
133133

134134
if not self._is_valid_immunization_id(imms_id):
135-
raise InvalidImmunizationId()
135+
raise InvalidImmunizationIdError()
136136

137137
if not self._is_valid_resource_version(resource_version):
138-
raise InvalidResourceVersion(resource_version=resource_version)
138+
raise InvalidResourceVersionError(resource_version=resource_version)
139139

140140
try:
141141
immunization = json.loads(aws_event["body"], parse_float=Decimal)
@@ -156,7 +156,7 @@ def delete_immunization(self, aws_event: APIGatewayProxyEventV1) -> dict:
156156
imms_id = get_path_parameter(aws_event, "id")
157157

158158
if not self._is_valid_immunization_id(imms_id):
159-
raise InvalidImmunizationId()
159+
raise InvalidImmunizationIdError()
160160

161161
supplier_system = get_supplier_system_header(aws_event)
162162

@@ -167,10 +167,10 @@ def delete_immunization(self, aws_event: APIGatewayProxyEventV1) -> dict:
167167
def search_immunizations(self, aws_event: APIGatewayProxyEventV1) -> dict:
168168
try:
169169
search_params = process_search_params(process_params(aws_event))
170-
except ParameterException as e:
170+
except ParameterExceptionError as e:
171171
return self._create_bad_request(e.message)
172172
if search_params is None:
173-
raise ParameterException("Failed to parse parameters.")
173+
raise ParameterExceptionError("Failed to parse parameters.")
174174

175175
# Check vaxx type permissions- start
176176
try:

lambdas/backend/src/models/errors.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def to_operation_outcome() -> dict:
6666

6767

6868
@dataclass
69-
class ResourceVersionNotProvided(RuntimeError):
69+
class ResourceVersionNotProvidedError(RuntimeError):
7070
"""Return this error when client has failed to provide the FHIR resource version where required"""
7171

7272
resource_type: str
@@ -84,15 +84,15 @@ def to_operation_outcome(self) -> dict:
8484

8585

8686
@dataclass
87-
class ParameterException(RuntimeError):
87+
class ParameterExceptionError(RuntimeError):
8888
message: str
8989

9090
def __str__(self):
9191
return self.message
9292

9393

9494
@dataclass
95-
class InvalidImmunizationId(ApiValidationError):
95+
class InvalidImmunizationIdError(ApiValidationError):
9696
"""Use this when the unique Immunization ID is invalid"""
9797

9898
def to_operation_outcome(self) -> dict:
@@ -105,7 +105,7 @@ def to_operation_outcome(self) -> dict:
105105

106106

107107
@dataclass
108-
class InvalidResourceVersion(ApiValidationError):
108+
class InvalidResourceVersionError(ApiValidationError):
109109
"""Use this when the resource version is invalid"""
110110

111111
resource_version: Any
@@ -155,3 +155,21 @@ def to_operation_outcome(self) -> dict:
155155
code=Code.invalid,
156156
diagnostics=self.message,
157157
)
158+
159+
160+
@dataclass
161+
class InvalidStoredDataError(RuntimeError):
162+
"""Use this when a piece of stored data is invalid and the operation cannot be completed"""
163+
164+
data_type: str
165+
166+
def __str__(self):
167+
return f"Invalid data stored for immunization record: {self.data_type}"
168+
169+
def to_operation_outcome(self) -> dict:
170+
return create_operation_outcome(
171+
resource_id=str(uuid.uuid4()),
172+
severity=Severity.error,
173+
code=Code.server_error,
174+
diagnostics=self.__str__(),
175+
)

lambdas/backend/src/parameter_parser.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from common.models.constants import Constants
1010
from common.models.utils.generic_utils import nhs_number_mod11_check
1111
from common.redis_client import get_redis_client
12-
from models.errors import ParameterException
12+
from models.errors import ParameterExceptionError
1313

1414
ERROR_MESSAGE_DUPLICATED_PARAMETERS = 'Parameters may not be duplicated. Use commas for "or".'
1515

@@ -48,20 +48,20 @@ def process_patient_identifier(identifier_params: ParamContainer) -> str:
4848
patient_identifier = patient_identifiers[0] if len(patient_identifiers) == 1 else None
4949

5050
if patient_identifier is None:
51-
raise ParameterException(f"Search parameter {patient_identifier_key} must have one value.")
51+
raise ParameterExceptionError(f"Search parameter {patient_identifier_key} must have one value.")
5252

5353
patient_identifier_parts = patient_identifier.split("|")
5454
identifier_system = patient_identifier_parts[0]
5555
if len(patient_identifier_parts) != 2 or identifier_system != patient_identifier_system:
56-
raise ParameterException(
56+
raise ParameterExceptionError(
5757
"patient.identifier must be in the format of "
5858
f'"{patient_identifier_system}|{{NHS number}}" '
5959
f'e.g. "{patient_identifier_system}|9000000009"'
6060
)
6161

6262
nhs_number = patient_identifier_parts[1]
6363
if not nhs_number_mod11_check(nhs_number):
64-
raise ParameterException("Search parameter patient.identifier must be a valid NHS number.")
64+
raise ParameterExceptionError("Search parameter patient.identifier must be a valid NHS number.")
6565

6666
return nhs_number
6767

@@ -75,11 +75,11 @@ def process_immunization_target(imms_params: ParamContainer) -> list[str]:
7575
vaccine_type for vaccine_type in set(imms_params.get(immunization_target_key, [])) if vaccine_type is not None
7676
]
7777
if len(vaccine_types) < 1:
78-
raise ParameterException(f"Search parameter {immunization_target_key} must have one or more values.")
78+
raise ParameterExceptionError(f"Search parameter {immunization_target_key} must have one or more values.")
7979

8080
valid_vaccine_types = get_redis_client().hkeys(Constants.VACCINE_TYPE_TO_DISEASES_HASH_KEY)
8181
if any(x not in valid_vaccine_types for x in vaccine_types):
82-
raise ParameterException(
82+
raise ParameterExceptionError(
8383
f"immunization-target must be one or more of the following: {', '.join(valid_vaccine_types)}"
8484
)
8585

@@ -148,7 +148,7 @@ def process_search_params(params: ParamContainer) -> SearchParams:
148148
errors.append(f"Search parameter {date_from_key} must be before {date_to_key}")
149149

150150
if errors:
151-
raise ParameterException("; ".join(errors))
151+
raise ParameterExceptionError("; ".join(errors))
152152

153153
return SearchParams(patient_identifier, vaccine_types, date_from, date_to, include)
154154

@@ -163,7 +163,7 @@ def parse_multi_value_query_parameters(
163163
multi_value_query_params: dict[str, list[str]],
164164
) -> ParamContainer:
165165
if any(len(v) > 1 for k, v in multi_value_query_params.items()):
166-
raise ParameterException(ERROR_MESSAGE_DUPLICATED_PARAMETERS)
166+
raise ParameterExceptionError(ERROR_MESSAGE_DUPLICATED_PARAMETERS)
167167
params = [(k, split_and_flatten(v)) for k, v in multi_value_query_params.items()]
168168

169169
return dict(params)
@@ -177,7 +177,7 @@ def parse_body_params(aws_event: APIGatewayProxyEventV1) -> ParamContainer:
177177
parsed_body = parse_qs(decoded_body)
178178

179179
if any(len(v) > 1 for k, v in parsed_body.items()):
180-
raise ParameterException(ERROR_MESSAGE_DUPLICATED_PARAMETERS)
180+
raise ParameterExceptionError(ERROR_MESSAGE_DUPLICATED_PARAMETERS)
181181
items = {k: split_and_flatten(v) for k, v in parsed_body.items()}
182182
return items
183183
return {}
@@ -186,7 +186,7 @@ def parse_body_params(aws_event: APIGatewayProxyEventV1) -> ParamContainer:
186186
body_params = parse_body_params(aws_event)
187187

188188
if len(set(query_params.keys()) & set(body_params.keys())) > 0:
189-
raise ParameterException(ERROR_MESSAGE_DUPLICATED_PARAMETERS)
189+
raise ParameterExceptionError(ERROR_MESSAGE_DUPLICATED_PARAMETERS)
190190

191191
parsed_params = {
192192
key: sorted(query_params.get(key, []) + body_params.get(key, []))

lambdas/backend/src/repository/fhir_repository.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from responses import logger
1616

1717
from common.models.constants import Constants
18-
from common.models.errors import InvalidStoredData, ResourceNotFoundError
18+
from common.models.errors import ResourceNotFoundError
1919
from common.models.immunization_record_metadata import ImmunizationRecordMetadata
2020
from common.models.utils.generic_utils import (
2121
get_contained_patient,
@@ -24,7 +24,7 @@
2424
from common.models.utils.validation_utils import (
2525
get_vaccine_type,
2626
)
27-
from models.errors import UnhandledResponseError
27+
from models.errors import InvalidStoredDataError, UnhandledResponseError
2828

2929

3030
def create_table(table_name=None, endpoint_url=None, region_name="eu-west-2"):
@@ -55,7 +55,7 @@ def get_fhir_identifier_from_identifier_pk(identifier_pk: str) -> Identifier:
5555
split_identifier = identifier_pk.split("#", 1)
5656

5757
if len(split_identifier) != 2:
58-
raise InvalidStoredData(data_type="identifier")
58+
raise InvalidStoredDataError(data_type="identifier")
5959

6060
supplier_code = split_identifier[0]
6161
supplier_unique_id = split_identifier[1]
@@ -133,13 +133,13 @@ def get_immunization_resource_and_metadata_by_id(
133133
return None, None
134134

135135
# The FHIR Identifier which is returned in the metadata is based on the IdentifierPK from the database because
136-
# it is valid for the IdentifierPK and Resource system and value to mismatch due to the V2 to V5 data uplift.
137-
# Please see VED-893 for more details.
136+
# we keep this attribute up to date in case of any changes rather than modifying the JSON resource. For example,
137+
# when we performed the V2 to V5 data migration as part of issue VED-893.
138138

139139
identifier_pk = item.get("IdentifierPK")
140140

141141
if identifier_pk is None:
142-
raise InvalidStoredData(data_type="identifier")
142+
raise InvalidStoredDataError(data_type="identifier")
143143

144144
identifier = get_fhir_identifier_from_identifier_pk(identifier_pk)
145145

lambdas/backend/src/service/fhir_service.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,10 @@ def update_immunization(self, imms_id: str, immunization: dict, supplier_system:
164164
):
165165
raise UnauthorizedVaxError()
166166

167-
immunization_fhir_entity = Immunization.parse_obj(immunization)
168-
identifier = cast(Identifier, immunization_fhir_entity.identifier[0])
167+
identifier = Identifier.construct(
168+
system=immunization["identifier"][0]["system"],
169+
value=immunization["identifier"][0]["value"],
170+
)
169171

170172
validate_identifiers_match(identifier, existing_immunization_meta.identifier)
171173

lambdas/backend/tests/controller/test_fhir_api_exception_handler.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
from models.errors import (
1414
InconsistentIdError,
1515
InvalidJsonError,
16-
InvalidResourceVersion,
17-
ResourceVersionNotProvided,
16+
InvalidResourceVersionError,
17+
InvalidStoredDataError,
18+
ResourceVersionNotProvidedError,
1819
UnauthorizedError,
1920
UnauthorizedVaxError,
2021
UnhandledResponseError,
@@ -58,13 +59,13 @@ def test_exception_handler_handles_custom_exception_and_returns_fhir_response(se
5859
(InvalidJsonError("Invalid JSON provided"), 400, "invalid", "Invalid JSON provided"),
5960
(CustomValidationError("This field was invalid"), 400, "invariant", "This field was invalid"),
6061
(
61-
ResourceVersionNotProvided(resource_type="Immunization"),
62+
ResourceVersionNotProvidedError(resource_type="Immunization"),
6263
400,
6364
"invariant",
6465
"Validation errors: Immunization resource version not specified in the request headers",
6566
),
6667
(
67-
InvalidResourceVersion(resource_version="badVersion"),
68+
InvalidResourceVersionError(resource_version="badVersion"),
6869
400,
6970
"invariant",
7071
"Validation errors: Immunization resource version:badVersion in the request headers is invalid.",
@@ -94,6 +95,12 @@ def test_exception_handler_handles_custom_exception_and_returns_fhir_response(se
9495
"exception",
9596
"Critical error\n{'outcome': 'critical error'}",
9697
),
98+
(
99+
InvalidStoredDataError("data_type"),
100+
500,
101+
"exception",
102+
"Invalid data stored for immunization record: data_type",
103+
),
97104
]
98105

99106
for error, expected_status, expected_code, expected_message in test_cases:

lambdas/backend/tests/controller/test_fhir_controller.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from controller.aws_apig_response_utils import create_response
1818
from controller.fhir_controller import FhirController
1919
from models.errors import (
20-
ParameterException,
20+
ParameterExceptionError,
2121
UnauthorizedVaxError,
2222
UnhandledResponseError,
2323
)
@@ -1570,7 +1570,7 @@ def test_search_immunizations_returns_400_on_ParameterException_from_parameter_p
15701570
}
15711571
}
15721572

1573-
process_search_params.side_effect = ParameterException("Test")
1573+
process_search_params.side_effect = ParameterExceptionError("Test")
15741574
response = self.controller.search_immunizations(lambda_event)
15751575

15761576
# Then

0 commit comments

Comments
 (0)