vstorage 0.9.0

Common API for various icalendar/vcard storages.
Documentation
use std::backtrace::Backtrace;

pub type Result<T, E = Error> = std::result::Result<T, E>;

/// Variants used to categorise [`Error`] instances.
#[derive(Debug, PartialEq)]
pub enum ErrorKind {
    /// A storage, collection or resource does not exist.
    DoesNotExist,
    /// Referenced resource is not a collection.
    NotACollection,
    /// Referenced resource is not a storage.
    NotAStorage,
    /// Access was denied by the underlying storage.
    AccessDenied,
    /// Generic input / output error.
    Io,
    /// Storage returned invalid data.
    InvalidData,
    /// Input provided is invalid.
    InvalidInput,
    /// Resources is read-only.
    ReadOnly,
    /// Collection is not empty.
    CollectionNotEmpty,
    /// The resource already exists.
    Exists,
    /// A precondition has failed.
    ///
    /// Typically, this error is returned when attempting to operate on an item using a stale
    /// [`crate::Etag`].
    PreconditionFailed,
    /// The requested operation is not possible on this specific instance.
    Unavailable,
    /// Storage implementation does not support a required feature.
    Unsupported,
    /// Uncategorised error.
    ///
    /// Deprecated and should not be used for any new error paths.
    // #[deprecated]
    Uncategorised,
}

impl ErrorKind {
    /// Create a new error of this kind.
    pub fn error<E>(self, source: E) -> Error
    where
        E: Into<Box<dyn std::error::Error + Send + Sync>>,
    {
        Error {
            kind: self,
            source: Some(source.into()),
            backtrace: Backtrace::capture(),
        }
    }

    #[must_use]
    const fn as_str(&self) -> &'static str {
        match self {
            ErrorKind::DoesNotExist => "resource does not exist",
            ErrorKind::NotACollection => "resource exists, but is not a collection",
            ErrorKind::NotAStorage => "resource exists, but is not a storage",
            ErrorKind::AccessDenied => "access to the resource was denied",
            ErrorKind::Io => "input/output error",
            ErrorKind::InvalidData => "operation returned data, but it is not valid",
            ErrorKind::InvalidInput => "input data is invalid",
            ErrorKind::ReadOnly => "the resource is read-only",
            ErrorKind::CollectionNotEmpty => "the collection is not empty",
            ErrorKind::Exists => "resource already exists",
            ErrorKind::PreconditionFailed => "a required condition was not met",
            ErrorKind::Unavailable => "the operation is not possible on this instance",
            ErrorKind::Unsupported => "the operation is not supported",
            ErrorKind::Uncategorised => "uncategorised error",
        }
    }
}

impl std::fmt::Display for ErrorKind {
    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        fmt.write_str(self.as_str())
    }
}

/// Common error type used by all Storage implementations.
///
/// Use [`ErrorKind`] as an entry point to create new instances.
#[derive(Debug)]
pub struct Error {
    pub(crate) kind: ErrorKind,
    source: Option<Box<dyn std::error::Error + Send + Sync>>,
    backtrace: Backtrace,
}

impl Error {
    /// Return the kind of this error.
    #[must_use]
    pub fn kind(&self) -> &ErrorKind {
        &self.kind
    }

    /// Return the backtrace for this error.
    ///
    /// A backtrace is not always available, see documentation for the [`::std::backtrace`] module.
    pub fn backtrace(&self) -> &Backtrace {
        &self.backtrace
    }
}

impl From<ErrorKind> for Error {
    fn from(kind: ErrorKind) -> Self {
        Error {
            kind,
            source: None,
            backtrace: Backtrace::capture(),
        }
    }
}

impl From<std::io::Error> for Error {
    fn from(value: std::io::Error) -> Self {
        let kind = match value.kind() {
            std::io::ErrorKind::NotFound => ErrorKind::DoesNotExist,
            std::io::ErrorKind::PermissionDenied => ErrorKind::AccessDenied,
            std::io::ErrorKind::AlreadyExists => ErrorKind::Exists,
            std::io::ErrorKind::InvalidInput => ErrorKind::InvalidInput,
            std::io::ErrorKind::InvalidData => ErrorKind::InvalidData,
            _ => ErrorKind::Io,
        };
        Error {
            kind,
            source: Some(value.into()),
            backtrace: Backtrace::capture(),
        }
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.source {
            Some(ref s) => write!(fmt, "{}: {}", self.kind, s),
            None => self.kind.fmt(fmt),
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match &self.source {
            Some(e) => Some(e.as_ref()),
            None => None,
        }
    }
}