Skip to main content

defect_core/
error.rs

1//! Cross-module error utilities.
2//!
3//! If a variant in an error type (e.g. [`crate::llm::ProviderError`], or `tool::ToolError`
4//! in `defect-agent`) needs to propagate an arbitrary `std::error::Error`,
5//! **always use [`BoxError`]** instead of a bare
6//! `Box<dyn std::error::Error + Send + Sync>`.
7//!
8//! Using a newtype (rather than a type alias) has these advantages:
9//! - Shorter, more readable type signatures
10//! - Distinguishes from "any dyn Error" at the type level, making caller intent clearer
11//! - Future implementation changes (e.g. switching to `anyhow::Error`, adding backtrace
12//!   support) require only one change
13
14use std::error::Error as StdError;
15use std::fmt;
16
17/// A type-erased error value. Carries an error from any source in a public API without
18/// exposing the concrete type.
19///
20/// Construction:
21/// - [`BoxError::new`]: wraps any `E: Error + Send + Sync + 'static`
22/// - `From<Box<dyn Error + Send + Sync>>`: migrates from an already-boxed form
23///
24/// **No** `From<E>` for arbitrary `E: Error`: under Rust's coherence rules, this would
25/// overlap with the blanket `From<T> for T` impl (since `BoxError` itself implements
26/// `Error`). Callers should use [`BoxError::new`] to wrap explicitly.
27#[derive(Debug)]
28pub struct BoxError(Box<dyn StdError + Send + Sync>);
29
30impl BoxError {
31    /// Wraps any `std::error::Error`.
32    pub fn new<E>(err: E) -> Self
33    where
34        E: StdError + Send + Sync + 'static,
35    {
36        Self(Box::new(err))
37    }
38}
39
40impl fmt::Display for BoxError {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        self.0.fmt(f)
43    }
44}
45
46impl StdError for BoxError {
47    fn source(&self) -> Option<&(dyn StdError + 'static)> {
48        self.0.source()
49    }
50}
51
52impl From<Box<dyn StdError + Send + Sync>> for BoxError {
53    fn from(value: Box<dyn StdError + Send + Sync>) -> Self {
54        Self(value)
55    }
56}