diagweave 0.1.1

Core runtime and macros re-export for diagweave error algebra and report diagweaveing.
Documentation
//! Report module - core diagnostic report types and operations.
//!
//! This module provides the main [`Report`] type which wraps errors with rich
//! metadata and context. The module is organized into several submodules:
//!
//! - [`builder`] - Builder-style methods for constructing reports
//! - [`accessors`] - Accessor and visitor methods for reading report data
//! - [`global`] - Global context injection utilities
//! - [`transform`] - Error transformation methods like `map_err`
//! - [`types`] - Core type definitions
//! - [`ext`] - Extension traits for working with reports
//! - [`impls`] - Core trait implementations
//!
//! # Example
//!
//! ```rust
//! use diagweave::prelude::{Report, Severity};
//! use diagweave::Error;
//!
//! #[derive(Debug, Error)]
//! #[display("database connection failed")]
//! struct DatabaseError;
//!
//! let report = Report::new(DatabaseError)
//!     .set_severity(Severity::Error)
//!     .set_error_code("DB-001")
//!     .attach_note("Failed to connect to production database")
//!     .with_ctx("host", "db.example.com")
//!     .with_ctx("port", "5432");
//! ```
//!
//! # Boxed Data
//!
//! `Report` encapsulates its metadata and diagnostics in a boxed `ReportData`
//! structure. This keeps the primary `Report` struct small (only two pointers)
//! and improves performance when reports are moved or passed around.

#[macro_use]
#[path = "report/macros.rs"]
mod macros;
#[path = "report/accessors.rs"]
mod accessors;
#[path = "report/builder.rs"]
mod builder;
#[path = "report/ext.rs"]
mod ext;
#[path = "report/global.rs"]
mod global;
#[path = "report/impls.rs"]
mod impls;
#[cfg(feature = "trace")]
#[path = "report/trace.rs"]
mod trace;
#[path = "report/transform.rs"]
mod transform;
#[path = "report/types.rs"]
mod types;

use alloc::boxed::Box;
use core::error::Error;

pub use ext::{Diagnostic, InspectReportExt, ResultReportExt};
pub use types::{
    Attachment, AttachmentValue, CauseCollectOptions, CauseKind, ContextMap, ContextValue,
    DiagnosticBag, DisplayCauseChain, ErrorCode, ErrorCodeIntError, GlobalErrorMeta, HasSeverity,
    MissingSeverity, ReportMetadata, ReportOptions, Severity, SeverityParseError, SeverityState,
    SourceErrorChain, SourceErrorEntry, SourceErrorItem, StackFrame, StackTrace, StackTraceFormat,
};
pub use types::{AttachmentVisit, CauseTraversalState, GlobalContext, ReportSourceErrorIter};
#[cfg(feature = "json")]
pub use types::{JsonContext, JsonContextEntry};

#[cfg(feature = "std")]
pub use global::RegisterGlobalContextError;
#[cfg(feature = "std")]
pub use global::register_global_injector;
#[cfg(feature = "trace")]
pub use trace::{ReportTrace, TraceContext, TraceEvent, TraceEventAttribute, TraceEventLevel};
#[cfg(feature = "std")]
pub use types::{GlobalConfig, SetGlobalConfigError, set_global_config};

pub(crate) use types::{append_source_chain, limit_depth_source_chain};

/// A high-level diagnostic report that wraps an error with rich metadata and context.
///
/// `Report` provides a comprehensive wrapper around error types, adding:
/// - **Attachments**: Notes and payloads for additional context
/// - **Context**: Key-value pairs for business and system context
/// - **Metadata**: Error code, category, and retryable flag
/// - **Severity**: Error severity level (via typestate pattern)
/// - **Stack traces**: Captured call stack information
/// - **Display causes**: Human-readable cause chain
/// - **Source errors**: Technical error chain for debugging
///
/// # Typestate Pattern
///
/// `Report` uses a typestate pattern for severity:
/// - `Report<E, MissingSeverity>` - Severity not yet set
/// - `Report<E, HasSeverity>` - Severity has been set
///
/// This ensures type safety when severity is required for certain operations.
///
/// # Example
///
/// ```rust
/// use diagweave::prelude::{Report, Severity};
/// use diagweave::Error;
///
/// #[derive(Debug, Error)]
/// #[display("my error")]
/// struct MyError;
///
/// // Create a report without severity
/// let report: Report<MyError, _> = Report::new(MyError);
///
/// // Set severity to get HasSeverity typestate
/// let report = report.set_severity(Severity::Error);
/// ```
pub struct Report<E, State: SeverityState = MissingSeverity> {
    inner: E,
    data: Box<ReportData<State>>,
}

struct ReportData<State: SeverityState> {
    metadata: ReportMetadata<State>,
    options: ReportOptions,
    #[cfg(feature = "trace")]
    trace: ReportTrace,
    bag: DiagnosticBag,
}

impl<E, State> Report<E, State>
where
    State: SeverityState,
{
    /// Returns a reference to the diagnostics bag.
    pub(crate) fn diagnostics(&self) -> &DiagnosticBag {
        &self.data.bag
    }

    /// Returns a mutable reference to the diagnostics bag.
    pub(crate) fn diagnostics_mut(&mut self) -> &mut DiagnosticBag {
        &mut self.data.bag
    }

    /// Returns a reference to the metadata.
    fn metadata_ref(&self) -> &ReportMetadata<State> {
        &self.data.metadata
    }
}

impl<E, State> Report<E, State>
where
    E: Error,
    State: SeverityState,
{
    /// Builds a source error chain view based on stored errors and inner source.
    fn source_errors_view(
        &self,
        stored: Option<&SourceErrorChain>,
        include_inner_source: bool,
        options: CauseCollectOptions,
    ) -> Option<SourceErrorChain> {
        let mut snapshot = stored.cloned();

        if include_inner_source && let Some(source) = self.inner.source() {
            let source_chain = SourceErrorChain::from_source(source, options);
            match snapshot.as_mut() {
                Some(existing) => append_source_chain(existing, source_chain),
                None => snapshot = Some(source_chain),
            }
        }

        let mut snapshot = snapshot?;
        limit_depth_source_chain(&mut snapshot, options, 0);
        if !options.detect_cycle {
            snapshot.clear_cycle_flags();
        }
        Some(snapshot)
    }

    /// Returns the origin source error chain view for rendering.
    pub(crate) fn origin_src_err_view(
        &self,
        options: CauseCollectOptions,
    ) -> Option<SourceErrorChain> {
        let bag: &DiagnosticBag = Report::<E, State>::diagnostics(self);
        Report::<E, State>::source_errors_view(self, bag.origin_src_errors(), true, options)
    }

    /// Returns the diagnostic source error chain view for rendering.
    pub(crate) fn diag_src_err_view(
        &self,
        options: CauseCollectOptions,
    ) -> Option<SourceErrorChain> {
        let bag: &DiagnosticBag = Report::<E, State>::diagnostics(self);
        Report::<E, State>::source_errors_view(self, bag.diag_src_errors(), false, options)
    }
}

impl<E, State> Report<E, State>
where
    State: SeverityState,
{
    /// Sets the severity for the report, transitioning to `HasSeverity` typestate.
    ///
    /// This method consumes the report and returns a new one with the severity
    /// set. This is the primary way to transition from `MissingSeverity` to
    /// `HasSeverity`.
    ///
    /// # Example
    ///
    /// ```rust
    /// use diagweave::prelude::{Report, Severity};
    /// use diagweave::Error;
    ///
    /// #[derive(Debug, Error)]
    /// #[display("my error")]
    /// struct MyError;
    ///
    /// let report = Report::new(MyError)
    ///     .set_severity(Severity::Error);
    /// ```
    pub fn set_severity(self, severity: Severity) -> Report<E, HasSeverity> {
        let Self { inner, data } = self;
        let ReportData {
            metadata,
            options,
            #[cfg(feature = "trace")]
            trace,
            bag,
        } = *data;
        Report {
            inner,
            data: Box::new(ReportData {
                metadata: metadata.into_has_severity(severity),
                options,
                #[cfg(feature = "trace")]
                trace,
                bag,
            }),
        }
    }

    /// Sets the severity only if not already set, transitioning to `HasSeverity` typestate.
    pub fn with_severity(self, severity: Severity) -> Report<E, HasSeverity> {
        if let Some(existing) = self.severity() {
            let Self { inner, data } = self;
            let ReportData {
                metadata,
                options,
                #[cfg(feature = "trace")]
                trace,
                bag,
            } = *data;
            Report {
                inner,
                data: Box::new(ReportData {
                    metadata: metadata.into_has_severity(existing),
                    options,
                    #[cfg(feature = "trace")]
                    trace,
                    bag,
                }),
            }
        } else {
            self.set_severity(severity)
        }
    }
}