dbuff 0.1.0

Double-buffered state with async command chains, streaming, and keyed task pools for ratatui applications
Documentation
/// Lifecycle state of a stream.
///
/// Tracks whether a stream is idle, actively producing items, completed,
/// encountered an error, or was aborted.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum StreamState {
    /// No stream has been started.
    #[default]
    Idle,
    /// Stream is actively producing items.
    Streaming,
    /// Stream completed naturally (source returned `None`).
    Completed,
    /// Stream encountered an error. The buffer retains partial data.
    Error(String),
    /// Stream was cancelled via [`StreamHandle::abort`](crate::StreamHandle::abort).
    Aborted,
}

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

    /// Returns `true` if the state is [`Streaming`](StreamState::Streaming).
    pub fn is_streaming(&self) -> bool {
        matches!(self, StreamState::Streaming)
    }

    /// Returns `true` if the state is [`Completed`](StreamState::Completed).
    pub fn is_completed(&self) -> bool {
        matches!(self, StreamState::Completed)
    }

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

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

    /// Returns the error message, if the state is [`Error`](StreamState::Error).
    pub fn error(&self) -> Option<&str> {
        match self {
            StreamState::Error(msg) => Some(msg),
            _ => None,
        }
    }
}

/// Combined stream status and accumulated buffer.
///
/// Designed to be stored as a single field in domain state and polled from
/// a render loop via `domain.read()`. The buffer persists across all non-Idle
/// states, so partial data is always accessible even on error or abort.
///
/// # State transitions
///
/// ```text
/// Idle → Streaming (on stream start)
/// Streaming → Completed (on stream end)
/// Streaming → Error(msg) (on stream error)
/// Any → Aborted (on abort)
/// Any → Idle (manual reset via domain.modify)
/// ```
///
/// # Example
///
/// ```
/// use dbuff::StreamStatus;
///
/// let status: StreamStatus<Vec<String>> = StreamStatus::idle();
/// assert!(status.is_idle());
/// assert!(status.buffer().is_none());
///
/// let streaming = StreamStatus::streaming(vec!["hello".to_string()]);
/// assert!(streaming.is_streaming());
/// assert_eq!(streaming.buffer().unwrap().len(), 1);
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StreamStatus<T> {
    state: StreamState,
    buffer: Option<T>,
}

impl<T> StreamStatus<T> {
    /// Create an idle status with no buffer.
    pub fn idle() -> Self {
        Self {
            state: StreamState::Idle,
            buffer: None,
        }
    }

    /// Create a streaming status with an initial buffer.
    pub fn streaming(buffer: T) -> Self {
        Self {
            state: StreamState::Streaming,
            buffer: Some(buffer),
        }
    }

    /// Create a completed status with the final buffer.
    pub fn completed(buffer: T) -> Self {
        Self {
            state: StreamState::Completed,
            buffer: Some(buffer),
        }
    }

    /// Create an error status. The buffer retains partial data.
    pub fn error(message: impl Into<String>, buffer: T) -> Self {
        Self {
            state: StreamState::Error(message.into()),
            buffer: Some(buffer),
        }
    }

    /// Create an aborted status. The buffer retains data at abort time.
    pub fn aborted(buffer: T) -> Self {
        Self {
            state: StreamState::Aborted,
            buffer: Some(buffer),
        }
    }

    /// Returns a reference to the current stream state.
    pub fn state(&self) -> &StreamState {
        &self.state
    }

    /// Returns a reference to the buffer, if present.
    ///
    /// The buffer is `None` only in the [`Idle`](StreamState::Idle) state.
    /// In all other states, the buffer contains accumulated data.
    pub fn buffer(&self) -> Option<&T> {
        self.buffer.as_ref()
    }

    /// Returns `true` if the state is [`Idle`](StreamState::Idle).
    pub fn is_idle(&self) -> bool {
        self.state.is_idle()
    }

    /// Returns `true` if the state is [`Streaming`](StreamState::Streaming).
    pub fn is_streaming(&self) -> bool {
        self.state.is_streaming()
    }

    /// Returns `true` if the state is [`Completed`](StreamState::Completed).
    pub fn is_completed(&self) -> bool {
        self.state.is_completed()
    }

    /// Returns `true` if the state is [`Error`](StreamState::Error).
    pub fn is_error(&self) -> bool {
        self.state.is_error()
    }

    /// Returns `true` if the state is [`Aborted`](StreamState::Aborted).
    pub fn is_aborted(&self) -> bool {
        self.state.is_aborted()
    }

    /// Returns the error message, if the state is [`Error`](StreamState::Error).
    pub fn error_message(&self) -> Option<&str> {
        self.state.error()
    }
}

impl<T> Default for StreamStatus<T> {
    fn default() -> Self {
        Self::idle()
    }
}