-
Notifications
You must be signed in to change notification settings - Fork 339
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improved error handling during schema validation #294
Improved error handling during schema validation #294
Conversation
Added error diferentiation when schema is invalid. supporting: - <Error in schema> : <Error sent to CP> - Type mismatch: TypeConstraintViolationError - Lack of properties: ProtocolError - Non-requested properties: FormatViolationError Now catches exceptions thrown by schema validation errors instead of dropping the WS connection.
Hello @joaomariord, thanks for the PR! Although it's a backwards incompatible change, I think it's a good improvement. |
Corrected linter errors Added tests for features
Hello @OrangeTux, thank you for promptly answering the PR, I've corrected the lint errors and adapted tests for the new behavior. I think we could keep the NotImplemented exception for core ("required") OCPP routes, and only answer with CALLERROR-NotImplemented for the optional routes. I can do a PR on this if you agree. Another thing that would be nice is to allow the programmer to give is own exception and error handling routines. I think the decorator could be used set the error handling callback (e.g. @on(Action.Heartbeat, my_error_handler)). An alternative, more complete, way would be to allow the routing of the errors to a given error handle (e.g. @error(Action.Hearbeat, TypeConstraintError)). |
Thanks for the feedback. The current routing system works fine for most cases, but is limited if your the use case is more complex. You give a good example of one of the routing system's limitation: one is unable to control the response of a call if any exception is raised in this library. #295 highlights another limitation. With your suggestion you gave me some food for though. |
This would actually help a lot. I am trying to build a fully-fledged CSMS and this is much needed to handle all possible real-world scenarios. |
Let's explore the idea of the
You suggest an action specific route using |
After reading both the documentation for OCPP-1.6 and OCPP-2.0.1, I found that the errors are based on whether the payload received by the CSMS follows the RPC framework or whether the CP sends a CALL which is not a BootNotification request or a message triggered by TriggerMessage request @joaomariord handled the first case elegantly. If it's possible to create a 'catch all' error handler that knows from which |
@jainmohit2001 I completely agree with your idea of a 'catch all' error handler.
instead of:
The "switch-case" is just a quick and dirty way to do it, but ignore it for the sake of the example, this could be solved in many and more elegant ways. |
I actually thought of something else though. |
That should be easy enough, it's an elegant solution.
|
You mean something like this, @jainmohit2001 ? # ocpp.charge_point.py
class ChargePoint:
...
async def on_exception(self, call: Call, exc: Exception) -> Union[CallResult, CallError]:
""" Default implementation just reraise the error to keep backwards compatibility. """
raise exc from ocpp.v16 import ChargePoint
class MyChargePoint(ChargePoint):
....
async def on_exception(self, call: Call, exc: Exception) -> Union[CallResult, CallError]:
""" Override that allows for custom error handling. """
if isinstance(call, call.Heartbeat):
return call_result.HeartbeatPayload(timestamp=datetime.now(tz=timezone.utc).isoformat())
raise exc A couple of notes about this implementation:
|
Yeah something like this, but where can we integrate this in the code? I think the following should preserve the backward compatibility # ocpp.charge_point.py
try:
response = handler(**snake_case_payload)
if inspect.isawaitable(response):
response = await response
except Exception as e:
LOGGER.exception("Error while handling request '%s'", msg)
response = msg.create_call_error(e).to_json()
await self._send(response)
# await self.on_exception() # Pass the relevant args and kwargs
return I think a simple async def on_exception(self, call: Call, exc : Exception):
""" Do nothing """
return I never meant to change the way the code currently handles the exceptions. That is required and should not be changed. I just wanted to do something more after the error is raised and the corresponding |
Yeah, I totally agree with you here. The name of the function should reflect that it is called after the CALLERROR is sent and the user wants to do something more. |
Personally I like the proposed idea of an error decorator rather than a single function. A single function will end up with a rather long set of isinstance() statements depending on how many messages have error handling implemented for. |
Thanks for your opinion @proelke. I agree with you, the idea of an |
I think it could be added quite quickly and I can't imagine off hand how it could break backwards compatibility. It could be designed based on the after decorator. |
If it's possible, please include this funtionality. @joaomariord @OrangeTux @proelke . |
@OrangeTux can you review this PR? It would be great to have this merged by the end of the week. I think that what we discussed needs to be further developed and tested, and in that sense this PR could advance right away. |
I've a last suggestion that combines several of the discussed variants in one. What if we introduce a decorater class Charger(CP):
@after_exception()
async def on_exception(self, call: Call, exc: Exception) -> None:
""" Override that allows for custom error handling. """
if isinstance(call, call.Heartbeat):
// do something The handler has similar semantics as the I choose the decorator approach over the approach of overriding a method. The former is consistent with In a future release we could implement filtering by passing arguments to The change is backwards compatible. If an exception is raised in the @joaomariord I have no plans to work on this topic this week, but I'm happy to receive a PR. |
I was talking about the current PR. If I have the time I can try to work our conversation into an actual PR. |
Hi @OrangeTux, @jainmohit2001, and @proelke, I've not been available for the past few weeks, but have been passing my eyes through the code and something came up. Would something like:
, work? This could be easily made with a custom decorator, like this one: def ocpp_error_handler(f, y=None):
async def wrapper(payload):
result = None
try:
validate_payload(snake_to_camel_case(payload), ocpp_version)
result = await f(payload)
except OCPPError as e:
log(e)
if y is not None:
result = await y()
return result
setattr(wrapper, "__name__", f.__name__)
return wrapper There could be a problem with the return, but I leave it for you to find that and other problems :D |
Added error diferentiation when schema is invalid. supporting: - <Error in schema> : <Error sent to CP> - Type mismatch: TypeConstraintViolationError - Lack of properties: ProtocolError - Non-requested properties: FormatViolationError Now catches exceptions thrown by schema validation errors instead of dropping the WS connection.
Added error differentiation when schema is invalid. supporting:
- :
- Type mismatch: TypeConstraintViolationError
- Lack of properties: ProtocolError
- Non-requested properties: FormatViolationError
Now catches exceptions thrown by schema validation errors instead of
dropping the WS connection.
Previous behavior was to drop the socket connection whenever a request payload was not compliant with the schema, this PR introduces support for schema error handling according to the OCPP specification (from 1.6 to 2.0.1).