Skip to main content

scoped_error/
error.rs

1// Copyright (C) 2026 Kan-Ru Chen <kanru@kanru.info>
2//
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
5//! Core error types and traits for context-aware error handling.
6//!
7//! This module defines [`WithContext`], the central trait that enables
8//! error context propagation, along with [`scoped_error::Error`] (a ready-to-use error
9//! type) and [`Frame`] (a single layer of context).
10
11use std::any::Any;
12use std::borrow::Cow;
13use std::error::Error as StdError;
14use std::fmt::{Debug, Display};
15use std::panic::Location;
16
17use crate::ext::ErrorExt;
18
19/// A trait for error types that can carry context information.
20///
21/// Types implementing this trait can have context (message + location)
22/// attached to them via [`with_context`](Self::with_context). This is the
23/// core abstraction enabling the library's context propagation.
24///
25/// # Implementing
26///
27/// For most use cases, use the [`impl_context_error!`](crate::impl_context_error)
28/// macro instead of implementing this manually.
29///
30/// # Example
31///
32/// ```
33/// use std::borrow::Cow;
34/// use std::error::Error;
35/// use std::fmt::{Debug, Display};
36/// use std::panic::Location;
37///
38/// use scoped_error::Frame;
39/// use scoped_error::WithContext;
40///
41/// // Custom error type with context support
42/// #[derive(Debug)]
43/// struct MyError {
44///     message: Cow<'static, str>,
45///     source: Option<Box<dyn Error + Send + Sync>>,
46///     location: Option<&'static Location<'static>>,
47/// }
48///
49/// impl Error for MyError {}
50/// impl Display for MyError {
51///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52///         if let Some(loc) = self.location {
53///             write!(f, "{}, at {}", self.message, loc)
54///         } else {
55///             f.write_str(&self.message)
56///         }
57///     }
58/// }
59///
60/// impl WithContext for MyError {
61///     fn with_context(mut self, context: Frame) -> Self {
62///         self.source = Some(context.source);
63///         self.location = Some(context.location);
64///         self
65///     }
66///
67///     fn location(&self) -> Option<&'static Location<'static>> {
68///         self.location
69///     }
70/// }
71/// ```
72pub trait WithContext: StdError + Any {
73    /// Attach a context layer to this error.
74    ///
75    /// The context includes a source error and the location where the
76    /// context was added (captured via `#[track_caller]`).
77    fn with_context(self, context: Frame) -> Self;
78
79    /// Get the location where this error was created or where context was attached.
80    fn location(&self) -> Option<&'static Location<'static>>;
81}
82
83/// A single layer of error context.
84///
85/// Contains the source error that caused this context layer, along with
86/// the location where the context was added. Created automatically by
87/// the conversion functions when an error occurs.
88///
89/// # Creation
90///
91/// `Frame` is typically created via `From<T>` where `T` can be
92/// converted to `Box<dyn Error + Send + Sync>`. The location is captured
93/// using `#[track_caller]`.
94pub struct Frame {
95    /// The underlying error that caused this context.
96    pub source: Box<dyn StdError + Send + Sync + 'static>,
97    /// The location where this context was attached.
98    pub location: &'static Location<'static>,
99}
100
101impl<T> From<T> for Frame
102where
103    T: Into<Box<dyn StdError + Send + Sync + 'static>>,
104{
105    /// Creates an `Frame` from any error type, capturing the caller's location.
106    #[track_caller]
107    fn from(value: T) -> Self {
108        let source = value.into();
109        let location = Location::caller();
110        Frame { source, location }
111    }
112}
113
114/// A convenient, ready-to-use error type with built-in context support.
115///
116/// [`Error`] can be used directly without defining custom error types.
117/// It stores an error message, optional source error, and optional location.
118///
119/// # Example
120///
121/// ```
122/// use scoped_error::Error;
123///
124/// fn fallible() -> Result<(), Error> {
125///     // &str or String can be converted to Error
126///     Err(Error::new("Something went wrong"))
127/// }
128/// ```
129pub struct Error {
130    /// The error message.
131    pub message: Cow<'static, str>,
132    /// The underlying error that caused this one.
133    pub source: Option<Box<dyn StdError + Send + Sync + 'static>>,
134    /// The location where this error was created.
135    pub location: Option<&'static Location<'static>>,
136}
137
138impl Error {
139    /// Create a new `Error` with the given message.
140    ///
141    /// The source and location are initially `None`.
142    pub fn new(msg: impl Into<Cow<'static, str>>) -> Error {
143        Error {
144            message: msg.into(),
145            source: None,
146            location: None,
147        }
148    }
149}
150
151impl Debug for Error {
152    /// Formats using the error report for human-readable output.
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        self.report().fmt(f)
155    }
156}
157
158impl Display for Error {
159    /// Displays the message with location if available.
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        if let Some(loc) = self.location {
162            write!(f, "{}, at {}", self.message, loc)
163        } else {
164            f.write_str(&self.message)
165        }
166    }
167}
168
169impl StdError for Error {
170    fn source(&self) -> Option<&(dyn StdError + 'static)> {
171        self.source.as_deref().map(|s| s as _)
172    }
173}
174
175impl WithContext for Error {
176    fn with_context(mut self, context: Frame) -> Self {
177        self.source = Some(context.source);
178        self.location = Some(context.location);
179        self
180    }
181    fn location(&self) -> Option<&'static Location<'static>> {
182        self.location
183    }
184}
185
186impl From<(Cow<'static, str>, Frame)> for Error {
187    fn from(value: (Cow<'static, str>, Frame)) -> Self {
188        Error {
189            message: value.0,
190            source: Some(value.1.source),
191            location: Some(value.1.location),
192        }
193    }
194}