Skip to content

Commit 154a5df

Browse files
committed
VED-79: seperation of concerns for mns sub files
1 parent 04ea667 commit 154a5df

File tree

12 files changed

+1079
-4
lines changed

12 files changed

+1079
-4
lines changed

mns_subscription/Makefile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
test:
2+
@PYTHONPATH=src:tests python -m unittest
3+
4+
cover:
5+
@PYTHONPATH=src:tests coverage run -m unittest discover
6+
7+
report:
8+
coverage report -m
9+
.PHONY: build package test

mns_subscription/models/errors.py

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
import uuid
2+
from dataclasses import dataclass
3+
from enum import Enum
4+
from typing import Union
5+
6+
7+
class Severity(str, Enum):
8+
error = "error"
9+
warning = "warning"
10+
11+
12+
class Code(str, Enum):
13+
forbidden = "forbidden"
14+
not_found = "not-found"
15+
invalid = "invalid"
16+
server_error = "exception"
17+
invariant = "invariant"
18+
not_supported = "not-supported"
19+
duplicate = "duplicate"
20+
# Added an unauthorized code its used when returning a response for an unauthorized vaccine type search.
21+
unauthorized = "unauthorized"
22+
23+
24+
@dataclass
25+
class UnauthorizedError(RuntimeError):
26+
@staticmethod
27+
def to_operation_outcome() -> dict:
28+
msg = f"Unauthorized request"
29+
return create_operation_outcome(
30+
resource_id=str(uuid.uuid4()),
31+
severity=Severity.error,
32+
code=Code.forbidden,
33+
diagnostics=msg,
34+
)
35+
36+
37+
@dataclass
38+
class UnauthorizedVaxError(RuntimeError):
39+
@staticmethod
40+
def to_operation_outcome() -> dict:
41+
msg = "Unauthorized request for vaccine type"
42+
return create_operation_outcome(
43+
resource_id=str(uuid.uuid4()),
44+
severity=Severity.error,
45+
code=Code.forbidden,
46+
diagnostics=msg,
47+
)
48+
49+
50+
@dataclass
51+
class UnauthorizedVaxOnRecordError(RuntimeError):
52+
@staticmethod
53+
def to_operation_outcome() -> dict:
54+
msg = "Unauthorized request for vaccine type present in the stored immunization resource"
55+
return create_operation_outcome(
56+
resource_id=str(uuid.uuid4()),
57+
severity=Severity.error,
58+
code=Code.forbidden,
59+
diagnostics=msg,
60+
)
61+
62+
63+
@dataclass
64+
class ResourceNotFoundError(RuntimeError):
65+
"""Return this error when the requested FHIR resource does not exist"""
66+
67+
resource_type: str
68+
resource_id: str
69+
70+
def __str__(self):
71+
return f"{self.resource_type} resource does not exist. ID: {self.resource_id}"
72+
73+
def to_operation_outcome(self) -> dict:
74+
return create_operation_outcome(
75+
resource_id=str(uuid.uuid4()),
76+
severity=Severity.error,
77+
code=Code.not_found,
78+
diagnostics=self.__str__(),
79+
)
80+
81+
82+
@dataclass
83+
class ResourceFoundError(RuntimeError):
84+
"""Return this error when the requested FHIR resource does exist"""
85+
86+
resource_type: str
87+
resource_id: str
88+
89+
def __str__(self):
90+
return f"{self.resource_type} resource does exist. ID: {self.resource_id}"
91+
92+
def to_operation_outcome(self) -> dict:
93+
return create_operation_outcome(
94+
resource_id=str(uuid.uuid4()),
95+
severity=Severity.error,
96+
code=Code.not_found,
97+
diagnostics=self.__str__(),
98+
)
99+
100+
101+
@dataclass
102+
class UnhandledResponseError(RuntimeError):
103+
"""Use this error when the response from an external service (ex: dynamodb) can't be handled"""
104+
105+
response: Union[dict, str]
106+
message: str
107+
108+
def __str__(self):
109+
return f"{self.message}\n{self.response}"
110+
111+
def to_operation_outcome(self) -> dict:
112+
return create_operation_outcome(
113+
resource_id=str(uuid.uuid4()),
114+
severity=Severity.error,
115+
code=Code.server_error,
116+
diagnostics=self.__str__(),
117+
)
118+
119+
120+
class MandatoryError(Exception):
121+
def __init__(self, message=None):
122+
self.message = message
123+
124+
125+
class ValidationError(RuntimeError):
126+
def to_operation_outcome(self) -> dict:
127+
pass
128+
129+
130+
@dataclass
131+
class InvalidPatientId(ValidationError):
132+
"""Use this when NHS Number is invalid or doesn't exist"""
133+
134+
patient_identifier: str
135+
136+
def __str__(self):
137+
return f"NHS Number: {self.patient_identifier} is invalid or it doesn't exist."
138+
139+
def to_operation_outcome(self) -> dict:
140+
return create_operation_outcome(
141+
resource_id=str(uuid.uuid4()),
142+
severity=Severity.error,
143+
code=Code.server_error,
144+
diagnostics=self.__str__(),
145+
)
146+
147+
148+
@dataclass
149+
class InconsistentIdError(ValidationError):
150+
"""Use this when the specified id in the message is inconsistent with the path
151+
see: http://hl7.org/fhir/R4/http.html#update"""
152+
153+
imms_id: str
154+
155+
def __str__(self):
156+
return f"The provided id:{self.imms_id} doesn't match with the content of the message"
157+
158+
def to_operation_outcome(self) -> dict:
159+
return create_operation_outcome(
160+
resource_id=str(uuid.uuid4()),
161+
severity=Severity.error,
162+
code=Code.server_error,
163+
diagnostics=self.__str__(),
164+
)
165+
166+
167+
@dataclass
168+
class CustomValidationError(ValidationError):
169+
"""Custom validation error"""
170+
171+
message: str
172+
173+
def __str__(self):
174+
return self.message
175+
176+
def to_operation_outcome(self) -> dict:
177+
return create_operation_outcome(
178+
resource_id=str(uuid.uuid4()),
179+
severity=Severity.error,
180+
code=Code.invariant,
181+
diagnostics=self.__str__(),
182+
)
183+
184+
185+
@dataclass
186+
class IdentifierDuplicationError(RuntimeError):
187+
"""Fine grain validation"""
188+
189+
identifier: str
190+
191+
def __str__(self) -> str:
192+
return f"The provided identifier: {self.identifier} is duplicated"
193+
194+
def to_operation_outcome(self) -> dict:
195+
msg = self.__str__()
196+
return create_operation_outcome(
197+
resource_id=str(uuid.uuid4()),
198+
severity=Severity.error,
199+
code=Code.duplicate,
200+
diagnostics=msg,
201+
)
202+
203+
204+
def create_operation_outcome(resource_id: str, severity: Severity, code: Code, diagnostics: str) -> dict:
205+
"""Create an OperationOutcome object. Do not use `fhir.resource` library since it adds unnecessary validations"""
206+
return {
207+
"resourceType": "OperationOutcome",
208+
"id": resource_id,
209+
"meta": {"profile": ["https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome"]},
210+
"issue": [
211+
{
212+
"severity": severity,
213+
"code": code,
214+
"details": {
215+
"coding": [
216+
{
217+
"system": "https://fhir.nhs.uk/Codesystem/http-error-codes",
218+
"code": code.upper(),
219+
}
220+
]
221+
},
222+
"diagnostics": diagnostics,
223+
}
224+
],
225+
}
226+
227+
228+
@dataclass
229+
class ParameterException(RuntimeError):
230+
message: str
231+
232+
def __str__(self):
233+
return self.message
234+
235+
236+
class UnauthorizedSystemError(RuntimeError):
237+
def __init__(self, message="Unauthorized system"):
238+
super().__init__(message)
239+
self.message = message
240+
241+
def to_operation_outcome(self) -> dict:
242+
return create_operation_outcome(
243+
resource_id=str(uuid.uuid4()),
244+
severity=Severity.error,
245+
code=Code.forbidden,
246+
diagnostics=self.message,
247+
)
248+
249+
250+
class MessageNotSuccessfulError(Exception):
251+
"""
252+
Generic error message for any scenario which either prevents sending to the Imms API, or which results in a
253+
non-successful response from the Imms API
254+
"""
255+
256+
def __init__(self, message=None):
257+
self.message = message
258+
259+
260+
class RecordProcessorError(Exception):
261+
"""
262+
Exception for re-raising exceptions which have already occurred in the Record Processor.
263+
The diagnostics dictionary received from the Record Processor is passed to the exception as an argument
264+
and is stored as an attribute.
265+
"""
266+
267+
def __init__(self, diagnostics_dictionary: dict):
268+
self.diagnostics_dictionary = diagnostics_dictionary

0 commit comments

Comments
 (0)