errcraft 0.1.0

Beautiful, structured, and colorful error handling for Rust.
Documentation
//! Context management for error frames.
//!
//! This module provides types for attaching rich contextual information to errors.

#[cfg(not(feature = "std"))]
use alloc::{borrow::Cow, string::String, vec::Vec};
#[cfg(feature = "std")]
use std::borrow::Cow;

/// A single layer of context attached to an error.
#[derive(Debug, Clone)]
pub struct ContextLayer {
    /// Optional key for structured context
    pub(crate) key: Option<Cow<'static, str>>,
    /// The context value
    pub(crate) value: ContextValue,
}

impl ContextLayer {
    /// Creates a new context layer with a key-value pair.
    pub fn with_key<K, V>(key: K, value: V) -> Self
    where
        K: Into<Cow<'static, str>>,
        V: IntoContextValue,
    {
        Self {
            key: Some(key.into()),
            value: value.into_context_value(),
        }
    }

    /// Creates a new context layer with just text (no key).
    pub fn text<T: Into<Cow<'static, str>>>(text: T) -> Self {
        Self {
            key: None,
            value: ContextValue::Text(text.into()),
        }
    }

    /// Returns the key, if any.
    pub fn key(&self) -> Option<&str> {
        self.key.as_deref()
    }

    /// Returns a reference to the value.
    pub fn value(&self) -> &ContextValue {
        &self.value
    }
}

/// A context value that can be attached to an error.
#[derive(Debug, Clone)]
pub enum ContextValue {
    /// Plain text context
    Text(Cow<'static, str>),
    /// Debug-formatted value
    Debugged(String),
}

impl ContextValue {
    /// Returns the value as a string slice.
    pub fn as_str(&self) -> &str {
        match self {
            ContextValue::Text(s) => s.as_ref(),
            ContextValue::Debugged(s) => s.as_str(),
        }
    }
}

/// Trait for types that can be converted into context values.
pub trait IntoContextValue {
    /// Converts this value into a `ContextValue`.
    fn into_context_value(self) -> ContextValue;
}

impl IntoContextValue for String {
    fn into_context_value(self) -> ContextValue {
        ContextValue::Text(Cow::Owned(self))
    }
}

impl IntoContextValue for &'static str {
    fn into_context_value(self) -> ContextValue {
        ContextValue::Text(Cow::Borrowed(self))
    }
}

impl IntoContextValue for Cow<'static, str> {
    fn into_context_value(self) -> ContextValue {
        ContextValue::Text(self)
    }
}

// Implement for common types explicitly to avoid conflicts
macro_rules! impl_into_context_value_debug {
    ($($t:ty),*) => {
        $(
            impl IntoContextValue for $t {
                fn into_context_value(self) -> ContextValue {
                    #[cfg(feature = "std")]
                    {
                        ContextValue::Debugged(format!("{:?}", self))
                    }
                    #[cfg(not(feature = "std"))]
                    {
                        use alloc::format;
                        ContextValue::Debugged(format!("{:?}", self))
                    }
                }
            }
        )*
    };
}

impl_into_context_value_debug!(
    i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64, bool, char
);

/// Container for multiple context layers, preserving insertion order.
#[derive(Debug, Clone, Default)]
pub(crate) struct ContextChain {
    layers: Vec<ContextLayer>,
}

impl ContextChain {
    /// Creates a new empty context chain.
    pub fn new() -> Self {
        Self { layers: Vec::new() }
    }

    /// Adds a context layer to the chain.
    pub fn push(&mut self, layer: ContextLayer) {
        self.layers.push(layer);
    }

    /// Returns an iterator over the context layers.
    pub fn iter(&self) -> impl Iterator<Item = &ContextLayer> {
        self.layers.iter()
    }

    /// Returns whether the chain is empty.
    pub fn is_empty(&self) -> bool {
        self.layers.is_empty()
    }

    /// Returns the number of layers in the chain.
    #[allow(dead_code)]
    pub fn len(&self) -> usize {
        self.layers.len()
    }
}