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}