"""Typed exception hierarchy for the storage provider abstraction.
Provider and remote-mirror failures raised by
:class:`adapters.outbound.storage.protocol.StorageProvider` implementations use
subclasses of :class:`OutboundStorageError` so the application layer's sync
coordinator can dispatch on the concrete failure mode without parsing upstream
error strings. Each public leaf binds to a stable
:class:`core.errors.error_codes.ErrorCode` through
:mod:`core.errors.registry` so the CLI taxonomy stays explicit.
:class:`StorageCorruptionError` is the deliberate exception: it derives from
:class:`core.errors.hierarchy.CoreError` because it represents structurally invalid
sidecar metadata, not a remote-provider transport, quota, permission, or mirror
failure.
The `Outbound` prefix disambiguates this hierarchy from the persistence
side :class:`adapters.persistence.storage.errors.StorageError`, which
covers at-rest persistence and has a different parent chain.
See Also:
:class:`adapters.outbound.storage.protocol.StorageProvider`
Provider Protocol whose implementations raise this hierarchy.
:class:`adapters.outbound.storage.records.ProviderObjectMetadata`
Boundary record paired with integrity and corruption checks.
"""
from __future__ import annotations
from collections.abc import Mapping
from ....application.operator_actions.models import PreconditionVerdict
from ....core.errors.hierarchy import CadrumoError, CoreError, TerminalPreconditionErrorMixin
[docs]
class OutboundStorageError(TerminalPreconditionErrorMixin[PreconditionVerdict], CadrumoError):
"""Base class for every outbound storage-provider failure."""
[docs]
class OutboundStorageValidationError(OutboundStorageError):
"""Raised when storage operation parameters fail validation.
Pydantic-facing callers translate this registered error at their narrow
validator boundary when required; provider code keeps the registered
outbound-storage type.
"""
def __init__(
self,
message: str | None = None,
*,
context: Mapping[str, object] | None = None,
translated_message: str | None = None,
precondition_verdict: PreconditionVerdict | None = None,
) -> None:
"""Initialize this public contract."""
super().__init__(
message,
context=context,
translated_message=translated_message,
precondition_verdict=precondition_verdict,
)
[docs]
class OutboundStorageNotFoundError(OutboundStorageError):
"""Raised when a requested object or namespace does not exist."""
[docs]
class OutboundStorageConflictError(OutboundStorageError):
"""Raised when a put / move / rename collides with an existing object."""
[docs]
class OutboundStoragePermissionError(OutboundStorageError):
"""Raised when the active credentials lack the required scope or grant."""
[docs]
class OutboundStorageQuotaError(OutboundStorageError):
"""Raised when the backend rejects an operation due to quota exhaustion."""
[docs]
class OutboundStorageNetworkError(OutboundStorageError):
"""Raised when the backend endpoint is unreachable (DNS/TLS/timeout)."""
[docs]
class OutboundStorageIntegrityError(OutboundStorageError):
"""Raised when fetched provider data fails integrity checks.
The shared hash comparison path is
:func:`adapters.outbound.storage._integrity.verify_content_hash`.
"""
[docs]
class OutboundStorageUnavailableError(OutboundStorageError):
"""Raised when the backend is reachable but signals temporary unavailability."""
[docs]
class StorageCorruptionError(TerminalPreconditionErrorMixin[PreconditionVerdict], CoreError):
"""Raised when a sidecar file contains structurally invalid field types.
As a :class:`core.errors.hierarchy.CoreError`, this indicates on-disk data
corruption: the sidecar JSON parses successfully but a required field (e.g.
``byte_length``) carries a type that the runtime cannot coerce to the
expected primitive. Unlike :class:`OutboundStorageIntegrityError`, which
covers payload-byte hash mismatches from
:func:`adapters.outbound.storage._integrity.verify_content_hash`, this
error surfaces schema-level violations in the sidecar metadata file itself.
"""
def __init__(
self,
message: str | None = None,
*,
context: Mapping[str, object] | None = None,
translated_message: str | None = None,
precondition_verdict: PreconditionVerdict | None = None,
) -> None:
"""Initialize this public contract."""
super().__init__(
message,
context=context,
translated_message=translated_message,
precondition_verdict=precondition_verdict,
)
__all__ = [
"OutboundStorageConflictError",
"OutboundStorageError",
"OutboundStorageIntegrityError",
"OutboundStorageNetworkError",
"OutboundStorageNotFoundError",
"OutboundStoragePathTooLongError",
"OutboundStoragePermissionError",
"OutboundStorageQuotaError",
"OutboundStorageUnavailableError",
"OutboundStorageValidationError",
"StorageCorruptionError",
]