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}