Skip to content

Commit d3d0dc3

Browse files
ldapobject: store message id for exceptions raised from result4()
Otherwise calling result4() can return exceptions like NO_SUCH_OBJECT or COMPARE_TRUE without the caller being able to find the matching asynchronous operation that failed. This change adds the message id as an argument to the exception.
1 parent 860ea96 commit d3d0dc3

File tree

5 files changed

+39
-2
lines changed

5 files changed

+39
-2
lines changed

Doc/reference/ldap.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,14 @@ The module defines the following exceptions:
321321
is set to a truncated form of the name provided or alias dereferenced
322322
for the lowest entry (object or alias) that was matched.
323323

324+
For use in asynchronous operations an optional field :py:const:`msg_id` is
325+
also set in the dictionary in cases where the exception can be associated
326+
with a request. This can be used in asynchronous code where
327+
:py:meth:`result()` returns an exception that is effectively the result of a
328+
previously started asynchronous operation. For example, this is the case for
329+
asynchronous (:py:meth:`compare()`), where the boolean result is always
330+
raised as an exception (:py:exc:`COMPARE_TRUE` or :py:exc:`COMPARE_FALSE`).
331+
324332

325333
.. py:exception:: ADMINLIMIT_EXCEEDED
326334

Modules/LDAPObject.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,7 +1174,7 @@ l_ldap_result4(LDAPObject *self, PyObject *args)
11741174
ldap_controls_free(serverctrls);
11751175
ldap_msgfree(msg);
11761176
Py_XDECREF(valuestr);
1177-
return LDAPerror(self->ldap, e);
1177+
return LDAPerror_with_message_id(self->ldap, e, res_msgid);
11781178
}
11791179

11801180
if (!(pyctrls = LDAPControls_to_List(serverctrls))) {
@@ -1186,7 +1186,7 @@ l_ldap_result4(LDAPObject *self, PyObject *args)
11861186
ldap_controls_free(serverctrls);
11871187
ldap_msgfree(msg);
11881188
Py_XDECREF(valuestr);
1189-
return LDAPerror(self->ldap, "LDAPControls_to_List");
1189+
return LDAPerror_with_message_id(self->ldap, "LDAPControls_to_List", res_msgid);
11901190
}
11911191
ldap_controls_free(serverctrls);
11921192

Modules/constants.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ LDAPerr(int errnum)
4949
/* Convert an LDAP error into an informative python exception */
5050
PyObject *
5151
LDAPerror(LDAP *l, char *msg)
52+
{
53+
return LDAPerror_with_message_id(l, msg, -1);
54+
}
55+
56+
/* Convert an LDAP error into an informative python exception containing the message id */
57+
PyObject *
58+
LDAPerror_with_message_id(LDAP *l, char *msg, int msg_id)
5259
{
5360
if (l == NULL) {
5461
PyErr_SetFromErrno(LDAPexception_class);
@@ -93,6 +100,13 @@ LDAPerror(LDAP *l, char *msg)
93100
Py_XDECREF(pyerrno);
94101
}
95102

103+
if (msg_id > 0) {
104+
PyObject *pymsg_id = PyInt_FromLong(msg_id);
105+
if (pymsg_id)
106+
PyDict_SetItemString(info, "msg_id", pymsg_id);
107+
Py_XDECREF(pymsg_id);
108+
}
109+
96110
if (ldap_get_option(l, LDAP_OPT_MATCHED_DN, &matched) >= 0
97111
&& matched != NULL) {
98112
if (*matched != '\0') {

Modules/constants.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ extern PyObject *LDAPconstant(int);
1212

1313
extern PyObject *LDAPexception_class;
1414
extern PyObject *LDAPerror(LDAP *, char *msg);
15+
extern PyObject *LDAPerror_with_message_id(LDAP *, char *msg, int msg_id);
1516
PyObject *LDAPerr(int errnum);
1617

1718
#ifndef LDAP_CONTROL_PAGE_OID

Tests/t_ldapobject.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,20 @@ def test_compare_s_invalidattr(self):
673673
with self.assertRaises(ldap.UNDEFINED_TYPE):
674674
result = l.compare_s('cn=Foo1,%s' % base, 'invalidattr', b'invalid')
675675

676+
def test_compare_true_exception_contains_message_id(self):
677+
base = self.server.suffix
678+
l = self._ldap_conn
679+
msg_id = l.compare('cn=Foo1,%s' % base, 'cn', b'Foo1')
680+
with self.assertRaises(ldap.COMPARE_TRUE) as cm:
681+
l.result()
682+
self.assertEqual(cm.exception.args[0]["msg_id"], msg_id)
683+
684+
def test_async_search_no_such_object_exception_contains_message_id(self):
685+
msg_id = self._ldap_conn.search("CN=XXX", ldap.SCOPE_SUBTREE)
686+
with self.assertRaises(ldap.NO_SUCH_OBJECT) as cm:
687+
self._ldap_conn.result()
688+
self.assertEqual(cm.exception.args[0]["msg_id"], msg_id)
689+
676690

677691
class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject):
678692
"""

0 commit comments

Comments
 (0)