From 4a1378e583ef36eb881880f5977a5b528b63d2d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Noel?= Date: Fri, 27 Jun 2025 10:31:58 +0200 Subject: [PATCH 1/2] varlink/error.py: Do not assume that namespaced error receive namespaced message Or if this assertion is valid, the way the Client handles error is broken then. Adds a new test file checking errors serialization/deserialization. --- varlink/error.py | 31 +++++++++++++++++++------------ varlink/tests/test_error.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 12 deletions(-) create mode 100644 varlink/tests/test_error.py diff --git a/varlink/error.py b/varlink/error.py index d72e872..d2cf9c9 100644 --- a/varlink/error.py +++ b/varlink/error.py @@ -45,15 +45,19 @@ def error(self): """returns the exception varlink error name""" return self.args[0].get("error") - def parameters(self, namespaced=False): - """returns the exception varlink error parameters""" + @staticmethod + def message_parameters(message, namespaced=False): if namespaced: return json.loads( - json.dumps(self.args[0]["parameters"]), + json.dumps(message["parameters"]), object_hook=lambda d: SimpleNamespace(**d), ) else: - return self.args[0].get("parameters") + return message.get("parameters") + + def parameters(self, namespaced=False): + """returns the exception varlink error parameters""" + return self.message_parameters(self.args[0], namespaced) def as_dict(self): return self.args[0] @@ -64,9 +68,11 @@ class InterfaceNotFound(VarlinkError): @classmethod def new(cls, message, namespaced=False): - return cls( - namespaced and message["parameters"].interface or message["parameters"].get("interface", None) - ) + parameters = cls.message_parameters(message, namespaced) + if parameters is None: + # Back-compatibility error + raise KeyError("parameters") + return cls(parameters.interface if namespaced else parameters.get("interface", None)) def __init__(self, interface): VarlinkError.__init__( @@ -83,7 +89,8 @@ class MethodNotFound(VarlinkError): @classmethod def new(cls, message, namespaced=False): - return cls(namespaced and message["parameters"].method or message["parameters"].get("method", None)) + parameters = cls.message_parameters(message, namespaced) + return cls(namespaced and parameters.method or parameters.get("method", None)) def __init__(self, method): VarlinkError.__init__( @@ -100,7 +107,8 @@ class MethodNotImplemented(VarlinkError): @classmethod def new(cls, message, namespaced=False): - return cls(namespaced and message["parameters"].method or message["parameters"].get("method", None)) + parameters = cls.message_parameters(message, namespaced) + return cls(namespaced and parameters.method or parameters.get("method", None)) def __init__(self, method): VarlinkError.__init__( @@ -117,9 +125,8 @@ class InvalidParameter(VarlinkError): @classmethod def new(cls, message, namespaced=False): - return cls( - namespaced and message["parameters"].parameter or message["parameters"].get("parameter", None) - ) + parameters = cls.message_parameters(message, namespaced) + return cls(namespaced and parameters.parameter or parameters.get("parameter", None)) def __init__(self, name): VarlinkError.__init__( diff --git a/varlink/tests/test_error.py b/varlink/tests/test_error.py new file mode 100644 index 0000000..028931e --- /dev/null +++ b/varlink/tests/test_error.py @@ -0,0 +1,33 @@ +import json +import unittest + +import varlink + + +class OneMessageClientHandler(varlink.ClientInterfaceHandler): + def __init__(self, interface, namespaced, next_message): + # No interface but we do not use them + super().__init__(interface, namespaced) + self.next_message = next_message + + def _next_message(self): + yield self.next_message + + +class TestError(unittest.TestCase): + def test_pack_unpack(self): + dummy_if = varlink.Interface("interface org.example.dummy") + for error in [ + varlink.InterfaceNotFound("org.varlink.notfound"), + varlink.MethodNotImplemented("Abstract"), + varlink.InvalidParameter("Struct.param"), + ]: + for namespaced in (True, False): + with self.subTest(error=error, namespaced=namespaced): + # encode error + encoded = json.dumps(error, cls=varlink.VarlinkEncoder) + + # Emulates the client receiving an error + handler = OneMessageClientHandler(dummy_if, namespaced, encoded) + with self.assertRaises(error.__class__): + handler._next_varlink_message() From 5cb710c3130bfa62548b3024c7228253259b7219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Noel?= Date: Fri, 27 Jun 2025 10:32:06 +0200 Subject: [PATCH 2/2] varlink.error: Instanciate MethodNotFound from VarlinkError.new --- varlink/error.py | 3 +++ varlink/tests/test_error.py | 1 + 2 files changed, 4 insertions(+) diff --git a/varlink/error.py b/varlink/error.py index d2cf9c9..13548d3 100644 --- a/varlink/error.py +++ b/varlink/error.py @@ -32,6 +32,9 @@ def new(cls, message, namespaced=False): elif message["error"] == "org.varlink.service.MethodNotImplemented": return MethodNotImplemented.new(message, namespaced) + elif message["error"] == "org.varlink.service.MethodNotFound": + return MethodNotFound.new(message, namespaced) + else: return cls(message, namespaced) diff --git a/varlink/tests/test_error.py b/varlink/tests/test_error.py index 028931e..b61c28b 100644 --- a/varlink/tests/test_error.py +++ b/varlink/tests/test_error.py @@ -19,6 +19,7 @@ def test_pack_unpack(self): dummy_if = varlink.Interface("interface org.example.dummy") for error in [ varlink.InterfaceNotFound("org.varlink.notfound"), + varlink.MethodNotFound("Method"), varlink.MethodNotImplemented("Abstract"), varlink.InvalidParameter("Struct.param"), ]: