auths-core 0.1.3

Core cryptography and keychain integration for Auths
Documentation
use std::fmt;

/// Domain error type for all storage port operations.
///
/// Adapters map backend-specific errors (e.g., `git2::Error`, `std::io::Error`)
/// into these variants before returning. Domain logic never sees infrastructure
/// error types.
///
/// Usage:
/// ```ignore
/// use auths_core::ports::storage::StorageError;
///
/// fn handle(err: StorageError) {
///     match err {
///         StorageError::NotFound { path } => eprintln!("missing: {path}"),
///         StorageError::AlreadyExists { path } => eprintln!("duplicate: {path}"),
///         StorageError::CasConflict => eprintln!("concurrent modification"),
///         StorageError::Io(msg) => eprintln!("I/O: {msg}"),
///         StorageError::Internal(inner) => eprintln!("bug: {inner}"),
///     }
/// }
/// ```
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum StorageError {
    /// The item was not found.
    #[error("not found: {path}")]
    NotFound {
        /// Path of the missing item.
        path: String,
    },

    /// An item already exists at this path.
    #[error("already exists: {path}")]
    AlreadyExists {
        /// Path of the existing item.
        path: String,
    },

    /// Optimistic concurrency conflict; retry the operation.
    #[error("compare-and-swap conflict")]
    CasConflict,

    /// An I/O error occurred.
    #[error("storage I/O error: {0}")]
    Io(String),

    /// An unexpected internal error.
    #[error("internal storage error: {0}")]
    Internal(Box<dyn std::error::Error + Send + Sync>),
}

impl auths_crypto::AuthsErrorInfo for StorageError {
    fn error_code(&self) -> &'static str {
        match self {
            Self::NotFound { .. } => "AUTHS-E3501",
            Self::AlreadyExists { .. } => "AUTHS-E3502",
            Self::CasConflict => "AUTHS-E3503",
            Self::Io(_) => "AUTHS-E3504",
            Self::Internal(_) => "AUTHS-E3505",
        }
    }

    fn suggestion(&self) -> Option<&'static str> {
        match self {
            Self::CasConflict => {
                Some("Retry the operation — another process made a concurrent change")
            }
            Self::Io(_) => Some("Check file permissions and disk space"),
            _ => None,
        }
    }
}

impl From<auths_keri::kel_io::KelStorageError> for StorageError {
    fn from(err: auths_keri::kel_io::KelStorageError) -> Self {
        match err {
            auths_keri::kel_io::KelStorageError::NotFound { path } => {
                StorageError::NotFound { path }
            }
            auths_keri::kel_io::KelStorageError::AlreadyExists { path } => {
                StorageError::AlreadyExists { path }
            }
            auths_keri::kel_io::KelStorageError::CasConflict => StorageError::CasConflict,
            auths_keri::kel_io::KelStorageError::Io(s) => StorageError::Io(s),
            auths_keri::kel_io::KelStorageError::Internal(e) => StorageError::Internal(e),
            // #[non_exhaustive]: forward any future variants as internal errors
            other => StorageError::Internal(Box::new(other)),
        }
    }
}

impl StorageError {
    /// Convenience constructor for `NotFound`.
    pub fn not_found(path: impl fmt::Display) -> Self {
        StorageError::NotFound {
            path: path.to_string(),
        }
    }

    /// Convenience constructor for `AlreadyExists`.
    pub fn already_exists(path: impl fmt::Display) -> Self {
        StorageError::AlreadyExists {
            path: path.to_string(),
        }
    }

    /// Convenience constructor wrapping any error as `Internal`.
    pub fn internal(err: impl std::error::Error + Send + Sync + 'static) -> Self {
        StorageError::Internal(Box::new(err))
    }
}