Skip to main content

error2/
context.rs

1use std::error::Error;
2
3use crate::{Error2, Location, transform::SourceToTarget};
4
5/// Type conversion for errors, `Option`, and `Result`.
6///
7/// The `Context` trait provides `.context()` and `.with_context()` methods for
8/// converting between types. It performs three core transformations:
9///
10/// 1. `Result<T, Source> -> Result<T, Target>` - Convert error types
11/// 2. `Option<T> -> Result<T, E>` - Convert `None` to an error
12/// 3. `E -> Result<T, E>` - Convert an error directly to `Result`
13///
14/// # Converting Result Error Types
15///
16/// Convert from one error type to another:
17///
18/// ```
19/// use std::io;
20///
21/// use error2::prelude::*;
22///
23/// #[derive(Debug, Error2)]
24/// pub enum AppError {
25///     #[error2(display("file error"))]
26///     FileError {
27///         source: io::Error,
28///         backtrace: Backtrace,
29///     },
30/// }
31///
32/// fn read_file(path: &str) -> Result<String, AppError> {
33///     // Result<String, io::Error> -> Result<String, AppError>
34///     std::fs::read_to_string(path).context(FileError2)
35/// }
36/// ```
37///
38/// The context struct (e.g., `FileError2`) is automatically generated by the
39/// `#[derive(Error2)]` macro.
40///
41/// # Converting Option to Result
42///
43/// Convert `Option<T>` to `Result<T, E>`:
44///
45/// ```
46/// use std::collections::HashMap;
47///
48/// use error2::prelude::*;
49///
50/// #[derive(Debug, Error2)]
51/// #[error2(display("key not found: {key}"))]
52/// struct NotFound {
53///     key: String,
54///     backtrace: Backtrace,
55/// }
56///
57/// # fn main() -> Result<(), NotFound> {
58/// let mut map = HashMap::new();
59/// map.insert("name", "Alice");
60///
61/// // Option<&str> -> Result<&str, NotFound>
62/// let name = map.get("name").context(NotFound2 { key: "name" })?;
63///
64/// assert_eq!(*name, "Alice");
65/// # Ok(())
66/// # }
67/// ```
68///
69/// # Converting Error to Result
70///
71/// Convert an error value directly to `Result`:
72///
73/// ```
74/// use std::io::{self, ErrorKind};
75///
76/// use error2::prelude::*;
77///
78/// #[derive(Debug, Error2)]
79/// #[error2(display("read line error"))]
80/// struct ReadLineError {
81///     source: io::Error,
82///     backtrace: Backtrace,
83/// }
84///
85/// # fn read_line() -> Result<Vec<u8>, io::Error> { Ok(vec![]) }
86/// fn process_line() -> Result<Option<Vec<u8>>, ReadLineError> {
87///     let bytes = match read_line() {
88///         Ok(bytes) => bytes,
89///         Err(ref e) if e.kind() == ErrorKind::TimedOut => return Ok(None),
90///         Err(e) => {
91///             // io::Error -> Result<_, ReadLineError>
92///             return e.context(ReadLineError2);
93///         }
94///     };
95///     Ok(Some(bytes))
96/// }
97/// ```
98///
99/// # Lazy Evaluation
100///
101/// Use `.with_context()` when creating context is expensive:
102///
103/// ```
104/// # use error2::prelude::*;
105/// # #[derive(Debug, Error2)]
106/// # enum AppError {
107/// #     #[error2(display("error: {msg}"))]
108/// #     Error { msg: String, backtrace: Backtrace },
109/// # }
110/// # fn expensive_debug_info() -> String { String::new() }
111/// # fn may_fail() -> Option<i32> { Some(42) }
112/// # fn example() -> Result<i32, AppError> {
113/// let value = may_fail().with_context(|| Error2 {
114///     msg: expensive_debug_info(), // Only called if None
115/// })?;
116/// # Ok(value)
117/// # }
118/// ```
119pub trait Context<T, M, Source, Middle, Target, C>: Sized
120where
121    Source: Into<Middle>,
122    C: SourceToTarget<M, Source, Middle, Target>,
123{
124    /// Converts error or `None` into `Target` error type.
125    ///
126    /// Automatically captures the caller's location for backtrace.
127    #[inline]
128    #[track_caller]
129    fn context(self, context: C) -> Result<T, Target> {
130        self.context_and_location(context, Location::caller())
131    }
132
133    /// Converts with explicit location (rarely needed).
134    fn context_and_location(self, context: C, location: Location) -> Result<T, Target>;
135
136    /// Converts using lazy evaluation for context creation.
137    ///
138    /// The closure is only called if an error occurs.
139    #[inline]
140    #[track_caller]
141    fn with_context<F>(self, f: F) -> Result<T, Target>
142    where
143        F: FnOnce() -> C,
144    {
145        self.with_context_and_location(f, Location::caller())
146    }
147
148    /// Lazy conversion with explicit location.
149    fn with_context_and_location<F>(self, f: F, location: Location) -> Result<T, Target>
150    where
151        F: FnOnce() -> C;
152}
153
154impl<T, M, Source, Middle, Target, C> Context<T, M, Source, Middle, Target, C> for Source
155where
156    Source: Error + Into<Middle>,
157    Middle: Error,
158    Target: Error2,
159    C: SourceToTarget<M, Source, Middle, Target>,
160{
161    #[inline]
162    fn context_and_location(self, context: C, location: Location) -> Result<T, Target> {
163        Err(context.source_to_target(self, location))
164    }
165
166    #[inline]
167    fn with_context_and_location<F>(self, f: F, location: Location) -> Result<T, Target>
168    where
169        F: FnOnce() -> C,
170    {
171        let context = f();
172        Err(context.source_to_target(self, location))
173    }
174}
175
176impl<T, M, Target, C> Context<T, M, (), (), Target, C> for Option<T>
177where
178    Target: Error2,
179    C: SourceToTarget<M, (), (), Target>,
180{
181    #[inline]
182    fn context_and_location(self, context: C, location: Location) -> Result<T, Target> {
183        match self {
184            Some(t) => Ok(t),
185            None => Err(context.source_to_target((), location)),
186        }
187    }
188
189    #[inline]
190    fn with_context_and_location<F>(self, f: F, location: Location) -> Result<T, Target>
191    where
192        F: FnOnce() -> C,
193    {
194        match self {
195            Some(t) => Ok(t),
196            None => {
197                let context = f();
198                Err(context.source_to_target((), location))
199            }
200        }
201    }
202}
203
204impl<T, M, Source, Middle, Target, C> Context<T, M, Source, Middle, Target, C> for Result<T, Source>
205where
206    Source: Error + Into<Middle>,
207    Middle: Error,
208    Target: Error2,
209    C: SourceToTarget<M, Source, Middle, Target>,
210{
211    #[inline]
212    fn context_and_location(self, context: C, location: Location) -> Result<T, Target> {
213        match self {
214            Ok(t) => Ok(t),
215            Err(source) => source.context_and_location(context, location),
216        }
217    }
218
219    #[inline]
220    fn with_context_and_location<F>(self, f: F, location: Location) -> Result<T, Target>
221    where
222        F: FnOnce() -> C,
223    {
224        match self {
225            Ok(t) => Ok(t),
226            Err(source) => source.with_context_and_location(f, location),
227        }
228    }
229}