stream-rs 0.1.0

Zero-dependency, spec-compliant streaming toolkit for LLM responses (SSE, incremental JSON, OpenAI/Anthropic delta accumulators).
Documentation
//! Error types for the streaming toolkit.

use core::fmt;

use alloc::string::String;

/// Error produced when a JSON byte stream ends while a top-level value is still
/// incomplete (see [`JsonSplitter::finish`](crate::incremental_json::JsonSplitter::finish)).
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct TruncatedJson {
    /// The number of bytes that were buffered for the unfinished value.
    pub buffered: usize,
}

impl fmt::Display for TruncatedJson {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "stream ended with {} buffered byte(s) of an incomplete top-level JSON value",
            self.buffered
        )
    }
}

#[cfg(feature = "std")]
impl std::error::Error for TruncatedJson {}

/// Error produced by a [`JsonSplitter`](crate::incremental_json::JsonSplitter)
/// running in **strict** mode when the byte stream violates top-level JSON
/// framing — for example a stray closing bracket (`}` or `]`) at depth zero, as
/// in `}{"a":1}`.
///
/// The default (non-strict) splitter never produces this error: it is a
/// framing scanner, not a validator. Strict mode is opt-in via
/// [`JsonSplitter::strict`](crate::incremental_json::JsonSplitter::strict) for
/// callers that want adversarial framing rejected up front rather than passing
/// a malformed value on to a real JSON parser.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct MalformedJson {
    /// The offending byte (e.g. `b'}'` or `b']'`).
    pub byte: u8,
    /// How many top-level values had already been emitted before the violation,
    /// which helps a caller locate the bad frame.
    pub values_emitted: usize,
}

impl fmt::Display for MalformedJson {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "malformed top-level JSON framing: unexpected byte {:?} at depth 0 after {} value(s)",
            self.byte as char, self.values_emitted
        )
    }
}

#[cfg(feature = "std")]
impl std::error::Error for MalformedJson {}

/// Errors produced while accumulating provider-specific streaming deltas.
///
/// `#[non_exhaustive]`: more variants may be added as further ordering or
/// validation checks are introduced, without it being a breaking change.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum AccumulateError {
    /// An event arrived in an order the state machine does not allow
    /// (for example an Anthropic `content_block_delta` before `content_block_start`,
    /// a block event before `message_start`, or any block event after `message_stop`).
    UnexpectedEvent {
        /// Human-readable description of what was received.
        got: String,
    },
    /// A delta targeted a content block whose kind does not match the delta
    /// (for example a `text_delta` applied to a `tool_use` block).
    BlockKindMismatch {
        /// Index of the content block.
        index: usize,
        /// The block kind that was expected by the delta.
        expected: &'static str,
        /// The kind the block actually has.
        actual: &'static str,
    },
}

impl fmt::Display for AccumulateError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::UnexpectedEvent { got } => write!(f, "unexpected event: {got}"),
            Self::BlockKindMismatch {
                index,
                expected,
                actual,
            } => write!(
                f,
                "block kind mismatch at content block {index}: expected {expected}, got {actual}"
            ),
        }
    }
}

#[cfg(feature = "std")]
impl std::error::Error for AccumulateError {}