Skip to content

Commit ac99d81

Browse files
authored
Improve Exception handling and CallError responses (mobilityhouse#335)
* Added missing NotSupported to the exception list. * Changed usage of NotImplemented to NotSupported when a handler doesn't exist. *Use the default description and moved additional information to details * Reduce the usage of ValidationError as it is not a valid OCPP error type, doesn't generate a CallError, and crashes the code when handling messages. I've tried to replace them with relevant OCPP error types.
1 parent c9fe26d commit ac99d81

7 files changed

+62
-47
lines changed

ocpp/charge_point.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from ocpp.routing import create_route_map
1111
from ocpp.messages import Call, validate_payload, MessageType
12-
from ocpp.exceptions import OCPPError, NotImplementedError
12+
from ocpp.exceptions import OCPPError, NotSupportedError
1313
from ocpp.messages import unpack
1414

1515
LOGGER = logging.getLogger('ocpp')
@@ -171,8 +171,8 @@ async def _handle_call(self, msg):
171171
try:
172172
handlers = self.route_map[msg.action]
173173
except KeyError:
174-
raise NotImplementedError(f"No handler for '{msg.action}' "
175-
"registered.")
174+
raise NotSupportedError(
175+
details={"cause": f"No handler for {msg.action} registered."})
176176

177177
if not handlers.get('_skip_schema_validation', False):
178178
validate_payload(msg, self._ocpp_version)
@@ -187,8 +187,8 @@ async def _handle_call(self, msg):
187187
try:
188188
handler = handlers['_on_action']
189189
except KeyError:
190-
raise NotImplementedError(f"No handler for '{msg.action}' "
191-
"registered.")
190+
raise NotSupportedError(
191+
details={"cause": f"No handler for {msg.action} registered."})
192192

193193
try:
194194
response = handler(**snake_case_payload)

ocpp/exceptions.py

+22-17
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,19 @@ def __str__(self):
3131

3232
class NotImplementedError(OCPPError):
3333
code = "NotImplemented"
34-
default_description = "Request Action is recognized but not supported by \
35-
the receiver"
34+
default_description = "Requested Action is not known by receiver"
35+
36+
37+
class NotSupportedError(OCPPError):
38+
code = "NotSupported"
39+
default_description = ("Request Action is recognized but not supported by "
40+
"the receiver")
3641

3742

3843
class InternalError(OCPPError):
3944
code = "InternalError"
40-
default_description = "An internal error occurred and the receiver was \
41-
able to process the requested Action successfully"
45+
default_description = ("An internal error occurred and the receiver was "
46+
"able to process the requested Action successfully")
4247

4348

4449
class ProtocolError(OCPPError):
@@ -48,35 +53,35 @@ class ProtocolError(OCPPError):
4853

4954
class SecurityError(OCPPError):
5055
code = "SecurityError"
51-
default_description = "During the processing of Action a security issue \
52-
occurred preventing receiver from completing the \
53-
Action successfully"
56+
default_description = ("During the processing of Action a security issue "
57+
"occurred preventing receiver from completing the "
58+
"Action successfully")
5459

5560

5661
class FormatViolationError(OCPPError):
5762
code = "FormatViolation"
58-
default_description = "Payload for Action is syntactically incorrect or \
59-
structure for Action"
63+
default_description = ("Payload for Action is syntactically incorrect or "
64+
"structure for Action")
6065

6166

6267
class PropertyConstraintViolationError(OCPPError):
6368
code = "PropertyConstraintViolation"
64-
default_description = "Payload is syntactically correct but at least \
65-
one field contains an invalid value"
69+
default_description = ("Payload is syntactically correct but at least "
70+
"one field contains an invalid value")
6671

6772

6873
class OccurenceConstraintViolationError(OCPPError):
6974
code = "OccurenceConstraintViolation"
70-
default_description = "Payload for Action is syntactically correct but \
71-
at least one of the fields violates occurence \
72-
constraints"
75+
default_description = ("Payload for Action is syntactically correct but "
76+
"at least one of the fields violates occurence "
77+
"constraints")
7378

7479

7580
class TypeConstraintViolationError(OCPPError):
7681
code = "TypeConstraintViolation"
77-
default_description = "Payload for Action is syntactically correct but at \
78-
least one of the fields violates data type \
79-
constraints (e.g. “somestring”: 12)"
82+
default_description = ("Payload for Action is syntactically correct but "
83+
"at least one of the fields violates data type "
84+
"constraints (e.g. “somestring”: 12)")
8085

8186

8287
class GenericError(OCPPError):

ocpp/messages.py

+22-13
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
from ocpp.exceptions import (OCPPError, FormatViolationError,
1313
PropertyConstraintViolationError,
1414
ProtocolError, TypeConstraintViolationError,
15-
ValidationError, UnknownCallErrorCodeError)
15+
NotImplementedError, ValidationError,
16+
UnknownCallErrorCodeError)
1617

1718
_validators: Dict[str, Draft4Validator] = {}
1819

@@ -65,22 +66,29 @@ def unpack(msg):
6566
"""
6667
try:
6768
msg = json.loads(msg)
68-
except json.JSONDecodeError as e:
69-
raise FormatViolationError(f'Message is not valid JSON: {e}')
69+
except json.JSONDecodeError:
70+
raise FormatViolationError(
71+
details={"cause": "Message is not valid JSON"})
7072

7173
if not isinstance(msg, list):
72-
raise ProtocolError("OCPP message hasn't the correct format. It "
73-
f"should be a list, but got {type(msg)} instead")
74+
raise ProtocolError(
75+
details={"cause": ("OCPP message hasn't the correct format. It "
76+
f"should be a list, but got '{type(msg)}' "
77+
"instead")})
7478

7579
for cls in [Call, CallResult, CallError]:
7680
try:
7781
if msg[0] == cls.message_type_id:
7882
return cls(*msg[1:])
7983
except IndexError:
80-
raise ProtocolError("Message doesn\'t contain MessageTypeID")
84+
raise ProtocolError(
85+
details={"cause": "Message does not contain MessageTypeId"})
86+
except TypeError:
87+
raise ProtocolError(
88+
details={"cause": "Message is missing elements."})
8189

82-
raise PropertyConstraintViolationError(f"MessageTypeId '{msg[0]}' isn't "
83-
"valid")
90+
raise PropertyConstraintViolationError(
91+
details={f"MessageTypeId '{msg[0]}' isn't valid"})
8492

8593

8694
def pack(msg):
@@ -184,9 +192,9 @@ def validate_payload(message, ocpp_version):
184192
validator = get_validator(
185193
message.message_type_id, message.action, ocpp_version
186194
)
187-
except (OSError, json.JSONDecodeError) as e:
188-
raise ValidationError("Failed to load validation schema for action "
189-
f"'{message.action}': {e}")
195+
except (OSError, json.JSONDecodeError):
196+
raise NotImplementedError(
197+
details={"cause": f"Failed to validate action: {message.action}"})
190198

191199
try:
192200
validator.validate(message.payload)
@@ -201,8 +209,9 @@ def validate_payload(message, ocpp_version):
201209
raise TypeConstraintViolationError(
202210
details={"cause": e.message}) from e
203211
else:
204-
raise ValidationError(f"Payload '{message.payload} for action "
205-
f"'{message.action}' is not valid: {e}")
212+
raise FormatViolationError(
213+
details={"cause": f"Payload '{message.payload} for action "
214+
f"'{message.action}' is not valid: {e}"})
206215

207216

208217
class Call:

tests/test_messages.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
FormatViolationError,
99
PropertyConstraintViolationError,
1010
TypeConstraintViolationError,
11+
NotImplementedError,
1112
UnknownCallErrorCodeError)
1213
from ocpp.messages import (validate_payload, get_validator, _validators,
1314
unpack, Call, CallError, CallResult, MessageType,
@@ -239,7 +240,7 @@ def test_validate_payload_with_non_existing_schema():
239240
payload={'invalid_key': True},
240241
)
241242

242-
with pytest.raises(ValidationError):
243+
with pytest.raises(NotImplementedError):
243244
validate_payload(message, ocpp_version="1.6")
244245

245246

tests/v16/test_v16_charge_point.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import asyncio
44
from unittest import mock
55

6-
from ocpp.exceptions import ValidationError, GenericError
6+
from ocpp.exceptions import FormatViolationError, GenericError
77
from ocpp.messages import CallError
88
from ocpp.routing import on, after, create_route_map
99
from ocpp.v16.enums import Action
@@ -115,9 +115,9 @@ async def test_route_message_with_no_route(base_central_system,
115115
json.dumps([
116116
4,
117117
1,
118-
"NotImplemented",
119-
"No handler for \'Heartbeat\' registered.",
120-
{}
118+
"NotSupported",
119+
"Request Action is recognized but not supported by the receiver",
120+
{"cause": "No handler for Heartbeat registered."}
121121
],
122122
separators=(',', ':')
123123
)
@@ -147,7 +147,7 @@ async def test_send_call_with_timeout(connection):
147147
async def test_send_invalid_call(base_central_system):
148148
payload = call.ResetPayload(type="Medium")
149149

150-
with pytest.raises(ValidationError):
150+
with pytest.raises(FormatViolationError):
151151
await base_central_system.call(payload)
152152

153153

tests/v20/test_v20_charge_point.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ async def test_route_message_with_no_route(base_central_system,
7373
json.dumps([
7474
4,
7575
1,
76-
"NotImplemented",
77-
"No handler for \'Heartbeat\' registered.",
78-
{}
76+
"NotSupported",
77+
"Request Action is recognized but not supported by the receiver",
78+
{"cause": "No handler for Heartbeat registered."}
7979
],
8080
separators=(',', ':')
8181
)

tests/v201/test_v201_charge_point.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ async def test_route_message_with_no_route(base_central_system,
7373
json.dumps([
7474
4,
7575
1,
76-
"NotImplemented",
77-
"No handler for \'Heartbeat\' registered.",
78-
{}
76+
"NotSupported",
77+
"Request Action is recognized but not supported by the receiver",
78+
{"cause": "No handler for Heartbeat registered."}
7979
],
8080
separators=(',', ':')
8181
)

0 commit comments

Comments
 (0)