summaryrefslogtreecommitdiffstats
path: root/ipalib/errors.py
diff options
context:
space:
mode:
Diffstat (limited to 'ipalib/errors.py')
-rw-r--r--ipalib/errors.py1077
1 files changed, 1077 insertions, 0 deletions
diff --git a/ipalib/errors.py b/ipalib/errors.py
new file mode 100644
index 000000000..f626f359d
--- /dev/null
+++ b/ipalib/errors.py
@@ -0,0 +1,1077 @@
+# Authors:
+# Jason Gerard DeRose <jderose@redhat.com>
+#
+# Copyright (C) 2008 Red Hat
+# see file 'COPYING' for use and warranty inmsgion
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; version 2 only
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Custom exception classes (some which are RPC transparent).
+
+`PrivateError` and its subclasses are custom IPA excetions that will *never* be
+forwarded in a Remote Procedure Call (RPC) response.
+
+On the other hand, `PublicError` and its subclasses can be forwarded in an RPC
+response. These public errors each carry a unique integer error code as well as
+a gettext translated error message (translated at the time the exception is
+raised). The purpose of the public errors is to relay information about
+*expected* user errors, service availability errors, and so on. They should
+*never* be used for *unexpected* programmatic or run-time errors.
+
+For security reasons it is *extremely* important that arbitrary exceptions *not*
+be forwarded in an RPC response. Unexpected exceptions can easily contain
+compromising information in their error messages. Any time the server catches
+any exception that isn't a `PublicError` subclass, it should raise an
+`InternalError`, which itself always has the same, static error message (and
+therefore cannot be populated with information about the true exception).
+
+The public errors are arranging into five main blocks of error code ranges:
+
+ ============= ========================================
+ Error codes Exceptions
+ ============= ========================================
+ 1000 - 1999 `AuthenticationError` and its subclasses
+ 2000 - 2999 `AuthorizationError` and its subclasses
+ 3000 - 3999 `InvocationError` and its subclasses
+ 4000 - 4999 `ExecutionError` and its subclasses
+ 5000 - 5999 `GenericError` and its subclasses
+ ============= ========================================
+
+Within these five blocks some sub-ranges are already allocated for certain types
+of error messages, while others are reserved for future use. Here are the
+current block assignments:
+
+ - **900-5999** `PublicError` and its subclasses
+
+ - **901 - 907** Assigned to special top-level public errors
+
+ - **908 - 999** *Reserved for future use*
+
+ - **1000 - 1999** `AuthenticationError` and its subclasses
+
+ - **1001 - 1099** Open for general authentication errors
+
+ - **1100 - 1199** `KerberosError` and its subclasses
+
+ - **1200 - 1999** *Reserved for future use*
+
+ - **2000 - 2999** `AuthorizationError` and its subclasses
+
+ - **2001 - 2099** Open for general authorization errors
+
+ - **2100 - 2199** `ACIError` and its subclasses
+
+ - **2200 - 2999** *Reserved for future use*
+
+ - **3000 - 3999** `InvocationError` and its subclasses
+
+ - **3001 - 3099** Open for general invocation errors
+
+ - **3100 - 3199** *Reserved for future use*
+
+ - **4000 - 4999** `ExecutionError` and its subclasses
+
+ - **4001 - 4099** Open for general execution errors
+
+ - **4100 - 4199** `BuiltinError` and its subclasses
+
+ - **4200 - 4299** `LDAPError` and its subclasses
+
+ - **4300 - 4999** *Reserved for future use*
+
+ - **5000 - 5999** `GenericError` and its subclasses
+
+ - **5001 - 5099** Open for generic errors
+
+ - **5100 - 5999** *Reserved for future use*
+"""
+
+from inspect import isclass
+from request import ugettext, ungettext
+from constants import TYPE_ERROR
+
+
+class PrivateError(StandardError):
+ """
+ Base class for exceptions that are *never* forwarded in an RPC response.
+ """
+
+ format = ''
+
+ def __init__(self, **kw):
+ self.message = self.format % kw
+ for (key, value) in kw.iteritems():
+ assert not hasattr(self, key), 'conflicting kwarg %s.%s = %r' % (
+ self.__class__.__name__, key, value,
+ )
+ setattr(self, key, value)
+ StandardError.__init__(self, self.message)
+
+
+class SubprocessError(PrivateError):
+ """
+ Raised when ``subprocess.call()`` returns a non-zero exit status.
+
+ This custom exception is needed because Python 2.4 doesn't have the
+ ``subprocess.CalledProcessError`` exception (which was added in Python 2.5).
+
+ For example:
+
+ >>> raise SubprocessError(returncode=2, argv=('ls', '-lh', '/no-foo/'))
+ Traceback (most recent call last):
+ ...
+ SubprocessError: return code 2 from ('ls', '-lh', '/no-foo/')
+
+ The exit code of the sub-process is available via the ``returncode``
+ instance attribute. For example:
+
+ >>> e = SubprocessError(returncode=1, argv=('/bin/false',))
+ >>> e.returncode
+ 1
+ >>> e.argv # argv is also available
+ ('/bin/false',)
+ """
+
+ format = 'return code %(returncode)d from %(argv)r'
+
+
+class PluginSubclassError(PrivateError):
+ """
+ Raised when a plugin doesn't subclass from an allowed base.
+
+ For example:
+
+ >>> raise PluginSubclassError(plugin='bad', bases=('base1', 'base2'))
+ Traceback (most recent call last):
+ ...
+ PluginSubclassError: 'bad' not subclass of any base in ('base1', 'base2')
+
+ """
+
+ format = '%(plugin)r not subclass of any base in %(bases)r'
+
+
+class PluginDuplicateError(PrivateError):
+ """
+ Raised when the same plugin class is registered more than once.
+
+ For example:
+
+ >>> raise PluginDuplicateError(plugin='my_plugin')
+ Traceback (most recent call last):
+ ...
+ PluginDuplicateError: 'my_plugin' was already registered
+ """
+
+ format = '%(plugin)r was already registered'
+
+
+class PluginOverrideError(PrivateError):
+ """
+ Raised when a plugin overrides another without using ``override=True``.
+
+ For example:
+
+ >>> raise PluginOverrideError(base='Command', name='env', plugin='my_env')
+ Traceback (most recent call last):
+ ...
+ PluginOverrideError: unexpected override of Command.env with 'my_env'
+ """
+
+ format = 'unexpected override of %(base)s.%(name)s with %(plugin)r'
+
+
+class PluginMissingOverrideError(PrivateError):
+ """
+ Raised when a plugin overrides another that has not been registered.
+
+ For example:
+
+ >>> raise PluginMissingOverrideError(base='Command', name='env', plugin='my_env')
+ Traceback (most recent call last):
+ ...
+ PluginMissingOverrideError: Command.env not registered, cannot override with 'my_env'
+ """
+
+ format = '%(base)s.%(name)s not registered, cannot override with %(plugin)r'
+
+
+class SkipPluginModule(PrivateError):
+ """
+ Raised to abort the loading of a plugin module.
+ """
+
+ format = '%(reason)s'
+
+
+class PluginsPackageError(PrivateError):
+ """
+ Raised when ``package.plugins`` is a module instead of a sub-package.
+ """
+
+ format = '%(name)s must be sub-package, not module: %(file)r'
+
+
+##############################################################################
+# Public errors:
+
+__messages = []
+
+def _(message):
+ __messages.append(message)
+ return message
+
+
+class PublicError(StandardError):
+ """
+ **900** Base class for exceptions that can be forwarded in an RPC response.
+ """
+
+ errno = 900
+ format = None
+
+ def __init__(self, format=None, message=None, **kw):
+ name = self.__class__.__name__
+ if self.format is not None and format is not None:
+ raise ValueError(
+ 'non-generic %r needs format=None; got format=%r' % (
+ name, format)
+ )
+ if message is None:
+ if self.format is None:
+ if format is None:
+ raise ValueError(
+ '%s.format is None yet format=None, message=None' % name
+ )
+ self.format = format
+ self.forwarded = False
+ self.message = self.format % kw
+ self.strerror = ugettext(self.format) % kw
+ else:
+ if type(message) is not unicode:
+ raise TypeError(
+ TYPE_ERROR % ('message', unicode, message, type(message))
+ )
+ self.forwarded = True
+ self.message = message
+ self.strerror = message
+ for (key, value) in kw.iteritems():
+ assert not hasattr(self, key), 'conflicting kwarg %s.%s = %r' % (
+ name, key, value,
+ )
+ setattr(self, key, value)
+ StandardError.__init__(self, self.message)
+
+
+class VersionError(PublicError):
+ """
+ **901** Raised when client and server versions are incompatible.
+
+ For example:
+
+ >>> raise VersionError(cver='2.0', sver='2.1', server='https://localhost')
+ Traceback (most recent call last):
+ ...
+ VersionError: 2.0 client incompatible with 2.1 server at 'https://localhost'
+
+ """
+
+ errno = 901
+ format = _('%(cver)s client incompatible with %(sver)s server at %(server)r')
+
+
+class UnknownError(PublicError):
+ """
+ **902** Raised when client does not know error it caught from server.
+
+ For example:
+
+ >>> raise UnknownError(code=57, server='localhost', error=u'a new error')
+ ...
+ Traceback (most recent call last):
+ ...
+ UnknownError: unknown error 57 from localhost: a new error
+
+ """
+
+ errno = 902
+ format = _('unknown error %(code)d from %(server)s: %(error)s')
+
+
+class InternalError(PublicError):
+ """
+ **903** Raised to conceal a non-public exception.
+
+ For example:
+
+ >>> raise InternalError()
+ Traceback (most recent call last):
+ ...
+ InternalError: an internal error has occured
+ """
+
+ errno = 903
+ format = _('an internal error has occured')
+
+ def __init__(self, message=None):
+ """
+ Security issue: ignore any information given to constructor.
+ """
+ PublicError.__init__(self)
+
+
+class ServerInternalError(PublicError):
+ """
+ **904** Raised when client catches an `InternalError` from server.
+
+ For example:
+
+ >>> raise ServerInternalError(server='https://localhost')
+ Traceback (most recent call last):
+ ...
+ ServerInternalError: an internal error has occured on server at 'https://localhost'
+ """
+
+ errno = 904
+ format = _('an internal error has occured on server at %(server)r')
+
+
+class CommandError(PublicError):
+ """
+ **905** Raised when an unknown command is called.
+
+ For example:
+
+ >>> raise CommandError(name='foobar')
+ Traceback (most recent call last):
+ ...
+ CommandError: unknown command 'foobar'
+ """
+
+ errno = 905
+ format = _('unknown command %(name)r')
+
+
+class ServerCommandError(PublicError):
+ """
+ **906** Raised when client catches a `CommandError` from server.
+
+ For example:
+
+ >>> e = CommandError(name='foobar')
+ >>> raise ServerCommandError(error=e.message, server='https://localhost')
+ Traceback (most recent call last):
+ ...
+ ServerCommandError: error on server 'https://localhost': unknown command 'foobar'
+ """
+
+ errno = 906
+ format = _('error on server %(server)r: %(error)s')
+
+
+class NetworkError(PublicError):
+ """
+ **907** Raised when a network connection cannot be created.
+
+ For example:
+
+ >>> raise NetworkError(uri='ldap://localhost:389', error='Connection refused')
+ Traceback (most recent call last):
+ ...
+ NetworkError: cannot connect to 'ldap://localhost:389': Connection refused
+ """
+
+ errno = 907
+ format = _('cannot connect to %(uri)r: %(error)s')
+
+
+class ServerNetworkError(PublicError):
+ """
+ **908** Raised when client catches a `NetworkError` from server.
+ """
+
+ errno = 908
+ format = _('error on server %(server)r: %(error)s')
+
+
+
+##############################################################################
+# 1000 - 1999: Authentication errors
+class AuthenticationError(PublicError):
+ """
+ **1000** Base class for authentication errors (*1000 - 1999*).
+ """
+
+ errno = 1000
+
+
+class KerberosError(AuthenticationError):
+ """
+ **1100** Base class for Kerberos authentication errors (*1100 - 1199*).
+ """
+
+ errno = 1100
+
+
+class CCacheError(KerberosError):
+ """
+ **1101** Raised when sever does not recieve Kerberose credentials.
+
+ For example:
+
+ >>> raise CCacheError()
+ Traceback (most recent call last):
+ ...
+ CCacheError: did not receive Kerberos credentials
+
+ """
+
+ errno = 1101
+ format = _('did not receive Kerberos credentials')
+
+
+class ServiceError(KerberosError):
+ """
+ **1102** Raised when service is not found in Kerberos DB.
+
+ For example:
+
+ >>> raise ServiceError(service='HTTP@localhost')
+ Traceback (most recent call last):
+ ...
+ ServiceError: Service 'HTTP@localhost' not found in Kerberos database
+ """
+
+ errno = 1102
+ format = _('Service %(service)r not found in Kerberos database')
+
+
+class NoCCacheError(KerberosError):
+ """
+ **1103** Raised when a client attempts to use Kerberos without a ccache.
+
+ For example:
+
+ >>> raise NoCCacheError()
+ Traceback (most recent call last):
+ ...
+ NoCCacheError: No credentials cache found
+ """
+
+ errno = 1103
+ format = _('No credentials cache found')
+
+
+class TicketExpired(KerberosError):
+ """
+ **1104** Raised when a client attempts to use an expired ticket
+
+ For example:
+
+ >>> raise TicketExpired()
+ Traceback (most recent call last):
+ ...
+ TicketExpired: Ticket expired
+ """
+
+ errno = 1104
+ format = _('Ticket expired')
+
+
+class BadCCachePerms(KerberosError):
+ """
+ **1105** Raised when a client has bad permissions on their ccache
+
+ For example:
+
+ >>> raise BadCCachePerms()
+ Traceback (most recent call last):
+ ...
+ BadCCachePerms: Credentials cache permissions incorrect
+ """
+
+ errno = 1105
+ format = _('Credentials cache permissions incorrect')
+
+
+class BadCCacheFormat(KerberosError):
+ """
+ **1106** Raised when a client has a misformated ccache
+
+ For example:
+
+ >>> raise BadCCacheFormat()
+ Traceback (most recent call last):
+ ...
+ BadCCacheFormat: Bad format in credentials cache
+ """
+
+ errno = 1106
+ format = _('Bad format in credentials cache')
+
+
+class CannotResolveKDC(KerberosError):
+ """
+ **1107** Raised when the KDC can't be resolved
+
+ For example:
+
+ >>> raise CannotResolveKDC()
+ Traceback (most recent call last):
+ ...
+ CannotResolveKDC: Cannot resolve KDC for requested realm
+ """
+
+ errno = 1107
+ format = _('Cannot resolve KDC for requested realm')
+
+
+##############################################################################
+# 2000 - 2999: Authorization errors
+class AuthorizationError(PublicError):
+ """
+ **2000** Base class for authorization errors (*2000 - 2999*).
+ """
+
+ errno = 2000
+
+
+class ACIError(AuthorizationError):
+ """
+ **2100** Base class for ACI authorization errors (*2100 - 2199*).
+ """
+
+ errno = 2100
+ format = _('Insufficient access: %(info)r')
+
+
+
+##############################################################################
+# 3000 - 3999: Invocation errors
+
+class InvocationError(PublicError):
+ """
+ **3000** Base class for command invocation errors (*3000 - 3999*).
+ """
+
+ errno = 3000
+
+
+class EncodingError(InvocationError):
+ """
+ **3001** Raised when received text is incorrectly encoded.
+ """
+
+ errno = 3001
+
+
+class BinaryEncodingError(InvocationError):
+ """
+ **3002** Raised when received binary data is incorrectly encoded.
+ """
+
+ errno = 3002
+
+
+class ZeroArgumentError(InvocationError):
+ """
+ **3003** Raised when a command is called with arguments but takes none.
+
+ For example:
+
+ >>> raise ZeroArgumentError(name='ping')
+ Traceback (most recent call last):
+ ...
+ ZeroArgumentError: command 'ping' takes no arguments
+ """
+
+ errno = 3003
+ format = _('command %(name)r takes no arguments')
+
+
+class MaxArgumentError(InvocationError):
+ """
+ **3004** Raised when a command is called with too many arguments.
+
+ For example:
+
+ >>> raise MaxArgumentError(name='user_add', count=2)
+ Traceback (most recent call last):
+ ...
+ MaxArgumentError: command 'user_add' takes at most 2 arguments
+ """
+
+ errno = 3004
+
+ def __init__(self, message=None, **kw):
+ if message is None:
+ format = ungettext(
+ 'command %(name)r takes at most %(count)d argument',
+ 'command %(name)r takes at most %(count)d arguments',
+ kw['count']
+ )
+ else:
+ format = None
+ InvocationError.__init__(self, format, message, **kw)
+
+
+class OptionError(InvocationError):
+ """
+ **3005** Raised when a command is called with unknown options.
+ """
+
+ errno = 3005
+
+
+class OverlapError(InvocationError):
+ """
+ **3006** Raised when arguments and options overlap.
+
+ For example:
+
+ >>> raise OverlapError(names=['givenname', 'login'])
+ Traceback (most recent call last):
+ ...
+ OverlapError: overlapping arguments and options: ['givenname', 'login']
+ """
+
+ errno = 3006
+ format = _('overlapping arguments and options: %(names)r')
+
+
+class RequirementError(InvocationError):
+ """
+ **3007** Raised when a required parameter is not provided.
+
+ For example:
+
+ >>> raise RequirementError(name='givenname')
+ Traceback (most recent call last):
+ ...
+ RequirementError: 'givenname' is required
+ """
+
+ errno = 3007
+ format = _('%(name)r is required')
+
+
+class ConversionError(InvocationError):
+ """
+ **3008** Raised when parameter value can't be converted to correct type.
+
+ For example:
+
+ >>> raise ConversionError(name='age', error='must be an integer')
+ Traceback (most recent call last):
+ ...
+ ConversionError: invalid 'age': must be an integer
+ """
+
+ errno = 3008
+ format = _('invalid %(name)r: %(error)s')
+
+
+class ValidationError(InvocationError):
+ """
+ **3009** Raised when a parameter value fails a validation rule.
+
+ For example:
+
+ >>> raise ValidationError(name='sn', error='can be at most 128 characters')
+ Traceback (most recent call last):
+ ...
+ ValidationError: invalid 'sn': can be at most 128 characters
+ """
+
+ errno = 3009
+ format = _('invalid %(name)r: %(error)s')
+
+
+class NoSuchNamespaceError(InvocationError):
+ """
+ **3010** Raised when an unknown namespace is requested.
+
+ For example:
+
+ >>> raise NoSuchNamespaceError(name='Plugins')
+ Traceback (most recent call last):
+ ...
+ NoSuchNamespaceError: api has no such namespace: Plugins
+ """
+
+ errno = 3010
+ format = _('api has no such namespace: %(name)r')
+
+
+##############################################################################
+# 4000 - 4999: Execution errors
+
+class ExecutionError(PublicError):
+ """
+ **4000** Base class for execution errors (*4000 - 4999*).
+ """
+
+ errno = 4000
+
+class NotFound(ExecutionError):
+ """
+ **4001** Raised when an entry is not found.
+
+ For example:
+
+ >>> raise NotFound()
+ Traceback (most recent call last):
+ ...
+ NotFound: entry not found
+
+ """
+
+ errno = 4001
+ format = _('entry not found')
+
+class DuplicateEntry(ExecutionError):
+ """
+ **4002** Raised when an entry already exists.
+
+ For example:
+
+ >>> raise DuplicateEntry
+ Traceback (most recent call last):
+ ...
+ DuplicateEntry: This entry already exists
+
+ """
+
+ errno = 4002
+ format = _('This entry already exists')
+
+class HostService(ExecutionError):
+ """
+ **4003** Raised when a host service principal is requested
+
+ For example:
+
+ >>> raise HostService
+ Traceback (most recent call last):
+ ...
+ HostService: You must enroll a host in order to create a host service
+
+ """
+
+ errno = 4003
+ format = _('You must enroll a host in order to create a host service')
+
+class MalformedServicePrincipal(ExecutionError):
+ """
+ **4004** Raised when a service principal is not of the form: service/fully-qualified host name
+
+ For example:
+
+ >>> raise MalformedServicePrincipal
+ Traceback (most recent call last):
+ ...
+ MalformedServicePrincipal: Service principal is not of the form: service/fully-qualified host name
+
+ """
+
+ errno = 4004
+ format = _('Service principal is not of the form: service/fully-qualified host name')
+
+class RealmMismatch(ExecutionError):
+ """
+ **4005** Raised when the requested realm does not match the IPA realm
+
+ For example:
+
+ >>> raise RealmMismatch
+ Traceback (most recent call last):
+ ...
+ RealmMismatch: The realm for the principal does not match the realm for this IPA server
+
+ """
+
+ errno = 4005
+ format = _('The realm for the principal does not match the realm for this IPA server')
+
+class RequiresRoot(ExecutionError):
+ """
+ **4006** Raised when a command requires the unix super-user to run
+
+ For example:
+
+ >>> raise RequiresRoot
+ Traceback (most recent call last):
+ ...
+ RequiresRoot: This command requires root access
+
+ """
+
+ errno = 4006
+ format = _('This command requires root access')
+
+class AlreadyPosixGroup(ExecutionError):
+ """
+ **4007** Raised when a group is already a posix group
+
+ For example:
+
+ >>> raise AlreadyPosixGroup
+ Traceback (most recent call last):
+ ...
+ AlreadyPosixGroup: This is already a posix group
+
+ """
+
+ errno = 4007
+ format = _('This is already a posix group')
+
+class MalformedUserPrincipal(ExecutionError):
+ """
+ **4008** Raised when a user principal is not of the form: user@REALM
+
+ For example:
+
+ >>> raise MalformedUserPrincipal(principal=jsmith@@EXAMPLE.COM)
+ Traceback (most recent call last):
+ ...
+ MalformedUserPrincipal: Principal is not of the form user@REALM: jsmith@@EXAMPLE.COM
+
+ """
+
+ errno = 4008
+ format = _('Principal is not of the form user@REALM: %(principal)r')
+
+class AlreadyActive(ExecutionError):
+ """
+ **4009** Raised when an entry is made active that is already active
+
+ For example:
+
+ >>> raise AlreadyActive()
+ Traceback (most recent call last):
+ ...
+ AlreadyActive: This entry is already unlocked
+
+ """
+
+ errno = 4009
+ format = _('This entry is already unlocked')
+
+class AlreadyInactive(ExecutionError):
+ """
+ **4010** Raised when an entry is made inactive that is already inactive
+
+ For example:
+
+ >>> raise AlreadyInactive()
+ Traceback (most recent call last):
+ ...
+ AlreadyInactive: This entry is already locked
+
+ """
+
+ errno = 4010
+ format = _('This entry is already locked')
+
+class HasNSAccountLock(ExecutionError):
+ """
+ **4011** Raised when an entry has the nsAccountLock attribute set
+
+ For example:
+
+ >>> raise HasNSAccountLock()
+ Traceback (most recent call last):
+ ...
+ HasNSAccountLock: This entry has nsAccountLock set, it cannot be locked or unlocked
+
+ """
+
+ errno = 4011
+ format = _('This entry has nsAccountLock set, it cannot be locked or unlocked')
+
+class NotGroupMember(ExecutionError):
+ """
+ **4012** Raised when a non-member is attempted to be removed from a group
+
+ For example:
+
+ >>> raise NotGroupMember()
+ Traceback (most recent call last):
+ ...
+ NotGroupMember: This entry is not a member of the group
+
+ """
+
+ errno = 4012
+ format = _('This entry is not a member of the group')
+
+class RecursiveGroup(ExecutionError):
+ """
+ **4013** Raised when a group is added as a member of itself
+
+ For example:
+
+ >>> raise RecursiveGroup()
+ Traceback (most recent call last):
+ ...
+ RecursiveGroup: A group may not be a member of itself
+
+ """
+
+ errno = 4013
+ format = _('A group may not be a member of itself')
+
+class AlreadyGroupMember(ExecutionError):
+ """
+ **4014** Raised when a member is attempted to be re-added to a group
+
+ For example:
+
+ >>> raise AlreadyGroupMember()
+ Traceback (most recent call last):
+ ...
+ AlreadyGroupMember: This entry is already a member of the group
+
+ """
+
+ errno = 4014
+ format = _('This entry is already a member of the group')
+
+class BuiltinError(ExecutionError):
+ """
+ **4100** Base class for builtin execution errors (*4100 - 4199*).
+ """
+
+ errno = 4100
+
+
+class HelpError(BuiltinError):
+ """
+ **4101** Raised when requesting help for an unknown topic.
+
+ For example:
+
+ >>> raise HelpError(topic='newfeature')
+ Traceback (most recent call last):
+ ...
+ HelpError: no command nor help topic 'newfeature'
+ """
+
+ errno = 4101
+ format = _('no command nor help topic %(topic)r')
+
+
+class LDAPError(ExecutionError):
+ """
+ **4200** Base class for LDAP execution errors (*4200 - 4299*).
+ """
+
+ errno = 4200
+
+
+class MidairCollision(ExecutionError):
+ """
+ **4201** Raised when a change collides with another change
+
+ For example:
+
+ >>> raise MidairCollision()
+ Traceback (most recent call last):
+ ...
+ MidairCollision: change collided with another change
+ """
+
+ errno = 4201
+ format = _('change collided with another change')
+
+
+class EmptyModlist(ExecutionError):
+ """
+ **4202** Raised when an LDAP update makes no changes
+
+ For example:
+
+ >>> raise EmptyModlist()
+ Traceback (most recent call last):
+ ...
+ EmptyModlist: no modifications to be performed
+ """
+
+ errno = 4202
+ format = _('no modifications to be performed')
+
+
+class DatabaseError(ExecutionError):
+ """
+ **4203** Raised when an LDAP error is not otherwise handled
+
+ For example:
+
+ >>> raise DatabaseError(desc="Can't contact LDAP server", info="")
+ Traceback (most recent call last):
+ ...
+ DatabaseError: Can't contact LDAP server:
+ """
+
+ errno = 4203
+ format = _('%(desc)r:%(info)r')
+
+
+class LimitsExceeded(ExecutionError):
+ """
+ **4204** Raised when search limits are exceeded.
+
+ For example:
+
+ >>> raise LimitsExceeded()
+ Traceback (most recent call last):
+ ...
+ LimitsExceeded: limits exceeded for this query
+ """
+
+ errno = 4204
+ format = _('limits exceeded for this query')
+
+
+##############################################################################
+# 5000 - 5999: Generic errors
+
+class GenericError(PublicError):
+ """
+ **5000** Base class for errors that don't fit elsewhere (*5000 - 5999*).
+ """
+
+ errno = 5000
+
+
+
+def __errors_iter():
+ """
+ Iterate through all the `PublicError` subclasses.
+ """
+ for (key, value) in globals().items():
+ if key.startswith('_') or not isclass(value):
+ continue
+ if issubclass(value, PublicError):
+ yield value
+
+public_errors = tuple(
+ sorted(__errors_iter(), key=lambda E: E.errno)
+)
+
+if __name__ == '__main__':
+ for klass in public_errors:
+ print '%d\t%s' % (klass.code, klass.__name__)
+ print '(%d public errors)' % len(public_errors)