Source code for calendaring_jmap.error

# SPDX-FileCopyrightText: 2026 calendaring-jmap contributors
# SPDX-License-Identifier: AGPL-3.0-or-later

"""JMAP error hierarchy.

:rfc:`8620#section-3.6.2` defines the standard method-level error types.

If ``caldav`` happens to be installed alongside this package, JMAPError and
JMAPAuthError subclass its DAVError/AuthorizationError too, so code that
catches ``caldav.lib.error.DAVError`` around CalDAV calls also catches JMAP
errors raised here. There is no hard dependency on caldav, just an optional
compatibility hook when it's present.
"""

from __future__ import annotations

#: Fallback ``error_type`` used when a server's error response omits its
#: own ``"type"`` field. Not itself a real RFC 8620 section 3.6.2 error type, just
#: this package's own default for a nonconformant response.
_DEFAULT_ERROR_TYPE = "serverFail"

try:
    # Both branches define _CaldavDAVError/_CaldavAuthorizationError; only
    # one ever runs, but static checkers see it as one name defined twice
    # and flag the second definition as incompatible with the first. mypy
    # calls this `no-redef`; pyright/Pylance calls it `reportAssignmentType`.
    # Both comments are needed since the two tools use different codes for
    # the same complaint about the same lines.
    from caldav.lib.error import (
        AuthorizationError as _CaldavAuthorizationError,  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
    )
    from caldav.lib.error import (
        DAVError as _CaldavDAVError,  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
    )
except ImportError:

    class _CaldavDAVError(Exception):  # type: ignore[no-redef]
        pass

    class _CaldavAuthorizationError(_CaldavDAVError):  # type: ignore[no-redef]
        pass


[docs] class JMAPBaseError(_CaldavDAVError): """Base for all calendaring-jmap errors: a URL, a reason, nothing more.""" url: str | None = None reason: str = "no reason" def __init__(self, url: str | None = None, reason: str | None = None) -> None: if url: self.url = url if reason: self.reason = reason def __str__(self) -> str: return f"{self.__class__.__name__} at '{self.url}', reason {self.reason}"
[docs] class JMAPError(JMAPBaseError): """Base class for all JMAP protocol errors. Adds ``error_type`` to carry the :rfc:`8620` error type string (e.g. ``"unknownMethod"``, ``"invalidArguments"``). """ error_type: str = _DEFAULT_ERROR_TYPE def __init__( self, url: str | None = None, reason: str | None = None, error_type: str | None = None, ) -> None: super().__init__(url=url, reason=reason) if error_type is not None: self.error_type = error_type def __str__(self) -> str: return ( f"{self.__class__.__name__} (type={self.error_type}) " f"at '{self.url}', reason: {self.reason}" )
[docs] class JMAPCapabilityError(JMAPError): """Server does not advertise the required JMAP capability. Raised when the Session object returned by the server does not include ``urn:ietf:params:jmap:calendars`` in the account capabilities. """ error_type = "capabilityNotSupported" reason = "Server does not support urn:ietf:params:jmap:calendars"
[docs] class JMAPAuthError(_CaldavAuthorizationError, JMAPError): """HTTP 401 or 403 received from a JMAP server. JMAP does not use a 401-challenge-retry dance. A 401/403 on the session GET or any API call is a hard failure. """ error_type = "forbidden" reason = "Authentication failed"
[docs] class JMAPMethodError(JMAPError): """A JMAP method call returned an error response. :rfc:`8620#section-3.6.2` generic error types that may be set as ``error_type``: - ``serverUnavailable``: temporary, retrying later may succeed - ``serverFail``: unexpected server-side error (default here, used when the server's response omits ``type`` entirely) - ``serverPartialFail``: partial failure, some calls succeeded - ``unknownMethod``: method name not recognised - ``invalidArguments``: bad argument types or values - ``invalidResultReference``: bad ``#result`` reference - ``forbidden``: not allowed to perform this call - ``accountNotFound``: ``accountId`` does not exist - ``accountNotSupportedByMethod``: account lacks needed capability - ``accountReadOnly``: account is read-only Individual methods define further, method-specific error types beyond this generic set (e.g. ``notFound``, ``calendarHasEvent``, ``expandDurationTooLarge``); see each method builder's own docstring. """ error_type = _DEFAULT_ERROR_TYPE