dbuff 0.1.0

Double-buffered state with async command chains, streaming, and keyed task pools for ratatui applications
Documentation
use std::sync::Arc;

/// A simple string-based error type for [`TaskStatus::Error`].
struct StringError(String);

impl std::fmt::Display for StringError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

impl std::fmt::Debug for StringError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

impl std::error::Error for StringError {}

/// Lifecycle status for a one-shot async task.
///
/// Track the progress of a command execution through its lifecycle:
/// [`Idle`] → [`Pending`] → [`Resolved`] / [`Error`] / [`Aborted`].
///
/// Designed to be stored in domain state and polled from a render loop via
/// `domain.read()`, enabling UI patterns like:
///
/// ```
/// use dbuff::TaskStatus;
///
/// let status = TaskStatus::Resolved(42);
/// assert!(status.is_resolved());
/// assert_eq!(status.resolved(), Some(&42));
///
/// let pending: TaskStatus<String> = TaskStatus::Pending;
/// assert!(pending.is_pending());
///
/// let err: TaskStatus<i32> = "timeout".to_string().into();
/// assert!(err.is_error());
/// assert_eq!(err.error().unwrap().to_string(), "timeout");
/// ```
#[derive(Debug, Clone, Default)]
pub enum TaskStatus<T> {
    /// No computation has been started.
    #[default]
    Idle,
    /// Computation is in progress.
    Pending,
    /// Computation completed successfully with a value.
    Resolved(T),
    /// Computation was cancelled.
    Aborted,
    /// Computation failed with an error message.
    Error(Arc<dyn std::error::Error + Send + Sync>),
}

impl<T> TaskStatus<T> {
    /// Returns `true` if the status is [`Idle`](TaskStatus::Idle).
    pub fn is_idle(&self) -> bool {
        matches!(self, TaskStatus::Idle)
    }

    /// Returns `true` if the status is [`Pending`](TaskStatus::Pending).
    pub fn is_pending(&self) -> bool {
        matches!(self, TaskStatus::Pending)
    }

    /// Returns `true` if the status is [`Resolved`](TaskStatus::Resolved).
    pub fn is_resolved(&self) -> bool {
        matches!(self, TaskStatus::Resolved(_))
    }

    /// Returns `true` if the status is [`Aborted`](TaskStatus::Aborted).
    pub fn is_aborted(&self) -> bool {
        matches!(self, TaskStatus::Aborted)
    }

    /// Returns `true` if the status is [`Error`](TaskStatus::Error).
    pub fn is_error(&self) -> bool {
        matches!(self, TaskStatus::Error(_))
    }

    /// Returns a reference to the resolved value, if any.
    pub fn resolved(&self) -> Option<&T> {
        match self {
            TaskStatus::Resolved(v) => Some(v),
            _ => None,
        }
    }

    /// Returns a reference to the error, if any.
    pub fn error(&self) -> Option<&(dyn std::error::Error + Send + Sync)> {
        match self {
            TaskStatus::Error(err) => Some(err.as_ref()),
            _ => None,
        }
    }
}

impl<T> PartialEq for TaskStatus<T>
where
    T: PartialEq,
{
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (TaskStatus::Resolved(a), TaskStatus::Resolved(b)) => a == b,
            (
                TaskStatus::Idle | TaskStatus::Pending | TaskStatus::Aborted,
                TaskStatus::Idle | TaskStatus::Pending | TaskStatus::Aborted,
            ) => true,
            (TaskStatus::Error(a), TaskStatus::Error(b)) => a.to_string() == b.to_string(),
            _ => false,
        }
    }
}

impl<T> Eq for TaskStatus<T> where T: Eq {}

impl<T> From<String> for TaskStatus<T> {
    fn from(msg: String) -> Self {
        TaskStatus::Error(Arc::new(StringError(msg)))
    }
}