Skip to main content

gix_error/exn/
ext.rs

1// Copyright 2025 FastLabs Developers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::{Error, Exn, ExnResult, Result};
16
17use super::impls::into_frame;
18
19/// Raise native errors with caller locations and optional context.
20pub trait ErrorExt: std::error::Error + Send + Sync + 'static {
21    /// Raise this error at the caller's location, returning a public [`Error`].
22    /// An existing [`Error`] is returned unchanged, preserving its representation and allocation.
23    #[track_caller]
24    fn raise(self) -> Error
25    where
26        Self: Sized,
27    {
28        let mut error = Some(self);
29        if let Some(error) = (&mut error as &mut dyn std::any::Any).downcast_mut::<Option<Error>>() {
30            return error.take().expect("the error has not been consumed");
31        }
32        Exn::new(error.expect("a different error type was not taken")).into_error()
33    }
34
35    /// Raise this error as a typed exception, retaining `Self` even when it is already an [`Error`].
36    #[track_caller]
37    fn raise_typed(self) -> Exn<Self>
38    where
39        Self: Sized,
40    {
41        Exn::new(self)
42    }
43
44    /// Raise this error as a cause of `context`, returning a public [`Error`].
45    ///
46    /// Tree-backed [`crate::Error`] values reuse their existing frame, retaining its original caller location.
47    #[track_caller]
48    fn and_raise<T: std::error::Error + Send + Sync + 'static>(self, context: T) -> Error
49    where
50        Self: Sized,
51    {
52        self.and_raise_typed(context).into_error()
53    }
54
55    /// Like [`Self::and_raise()`], retaining the context's type in an exception.
56    #[track_caller]
57    fn and_raise_typed<T: std::error::Error + Send + Sync + 'static>(self, context: T) -> Exn<T>
58    where
59        Self: Sized,
60    {
61        Exn::with_cause(self, context)
62    }
63
64    /// Raise this error as a new exception, with type erasure.
65    /// Tree-backed [`crate::Error`] values reuse their existing frame and caller location.
66    #[track_caller]
67    fn raise_erased(self) -> Exn
68    where
69        Self: Sized,
70    {
71        Exn::from_boxed_frame(into_frame(self))
72    }
73
74    /// Raise this error as a new exception, with `sources` as causes.
75    #[track_caller]
76    fn raise_all<T, I>(self, sources: I) -> Exn<Self>
77    where
78        Self: Sized,
79        T: std::error::Error + Send + Sync + 'static,
80        I: IntoIterator,
81        I::Item: Into<Exn<T>>,
82    {
83        Exn::raise_all(sources, self)
84    }
85}
86
87impl<T> ErrorExt for T where T: std::error::Error + Send + Sync + 'static {}
88
89/// Raise errors lazily on [`Option::None`].
90pub trait OptionExt {
91    /// The `Some` type.
92    type Some;
93
94    /// Raise `err()` on `None`, returning a public [`Result`]. Existing [`Error`] values are retained unchanged.
95    fn ok_or_raise<A, F>(self, err: F) -> Result<Self::Some>
96    where
97        A: std::error::Error + Send + Sync + 'static,
98        F: FnOnce() -> A;
99
100    /// Construct a typed [`Exn`] on the `None` variant.
101    fn ok_or_raise_typed<A, F>(self, err: F) -> ExnResult<Self::Some, A>
102    where
103        A: std::error::Error + Send + Sync + 'static,
104        F: FnOnce() -> A;
105
106    /// Construct a new [`Exn`] on the `None` variant, with type erasure.
107    /// The generated error is raised with [`ErrorExt::raise_erased`].
108    fn ok_or_raise_erased<A, F>(self, err: F) -> ExnResult<Self::Some>
109    where
110        A: std::error::Error + Send + Sync + 'static,
111        F: FnOnce() -> A;
112}
113
114impl<T> OptionExt for Option<T> {
115    type Some = T;
116
117    #[track_caller]
118    fn ok_or_raise<A, F>(self, err: F) -> Result<T>
119    where
120        A: std::error::Error + Send + Sync + 'static,
121        F: FnOnce() -> A,
122    {
123        match self {
124            Some(v) => Ok(v),
125            None => Err(err().raise()),
126        }
127    }
128
129    #[track_caller]
130    fn ok_or_raise_typed<A, F>(self, err: F) -> ExnResult<T, A>
131    where
132        A: std::error::Error + Send + Sync + 'static,
133        F: FnOnce() -> A,
134    {
135        match self {
136            Some(v) => Ok(v),
137            None => Err(Exn::new(err())),
138        }
139    }
140
141    #[track_caller]
142    fn ok_or_raise_erased<A, F>(self, err: F) -> ExnResult<T>
143    where
144        A: std::error::Error + Send + Sync + 'static,
145        F: FnOnce() -> A,
146    {
147        match self {
148            Some(v) => Ok(v),
149            None => Err(err().raise_erased()),
150        }
151    }
152}
153
154/// Convert native errors and exceptions into public [`Result`]s, or add context lazily on failure.
155pub trait ResultExt {
156    /// The `Ok` type.
157    type Success;
158
159    /// The `Err` type that would be wrapped in an [`Exn`].
160    type Error: std::error::Error + Send + Sync + 'static;
161
162    /// Add `err()` as context on failure, returning a public [`Result`].
163    ///
164    /// Reuses existing exception trees and preserves native sources and their caller locations.
165    #[track_caller]
166    fn or_raise<A, F>(self, err: F) -> Result<Self::Success>
167    where
168        Self: Sized,
169        A: std::error::Error + Send + Sync + 'static,
170        F: FnOnce() -> A,
171    {
172        self.or_raise_typed(err).map_err(Exn::into_error)
173    }
174
175    /// Convert to a public [`Result`] without adding context.
176    /// Native errors are raised at the caller; existing [`Error`] values and exceptions retain their locations.
177    #[track_caller]
178    fn or_error(self) -> Result<Self::Success>;
179
180    /// Like [`Self::or_raise()`], retaining the context's type in an exception.
181    fn or_raise_typed<A, F>(self, err: F) -> ExnResult<Self::Success, A>
182    where
183        A: std::error::Error + Send + Sync + 'static,
184        F: FnOnce() -> A;
185
186    /// Raise a new exception on the [`Exn`] inside the [`Result`], but erase its type.
187    /// Errors implementing [`std::error::Error`] use [`ErrorExt::raise_erased`].
188    ///
189    /// Apply [`Exn::erased`] on the `Err` variant, refer to it for more information.
190    fn or_erased(self) -> ExnResult<Self::Success>;
191
192    /// Raise a new exception on the [`Exn`] inside the [`Result`], and type-erase the result.
193    ///
194    /// Apply [`Exn::raise`] and [`Exn::erased`] on the `Err` variant, refer to it for more information.
195    #[track_caller]
196    fn or_raise_erased<A, F>(self, err: F) -> ExnResult<Self::Success>
197    where
198        Self: Sized,
199        A: std::error::Error + Send + Sync + 'static,
200        F: FnOnce() -> A,
201    {
202        self.or_raise_typed(err).map_err(Exn::erased)
203    }
204}
205
206impl<T, E> ResultExt for std::result::Result<T, E>
207where
208    E: std::error::Error + Send + Sync + 'static,
209{
210    type Success = T;
211    type Error = E;
212
213    #[track_caller]
214    fn or_raise_typed<A, F>(self, err: F) -> ExnResult<Self::Success, A>
215    where
216        A: std::error::Error + Send + Sync + 'static,
217        F: FnOnce() -> A,
218    {
219        match self {
220            Ok(v) => Ok(v),
221            Err(e) => Err(e.and_raise_typed(err())),
222        }
223    }
224
225    #[track_caller]
226    fn or_error(self) -> Result<T> {
227        match self {
228            Ok(v) => Ok(v),
229            Err(e) => Err(e.raise()),
230        }
231    }
232
233    #[track_caller]
234    fn or_erased(self) -> ExnResult<Self::Success> {
235        match self {
236            Ok(v) => Ok(v),
237            Err(e) => Err(e.raise_erased()),
238        }
239    }
240}
241
242/// Extension methods for results containing an already boxed error.
243///
244/// This complements [`ResultExt`], whose blanket implementation cannot accept boxed trait objects.
245pub trait BoxedResultExt {
246    /// The `Ok` type.
247    type Success;
248
249    /// Type-erase the boxed error inside the [`Result`].
250    fn or_erased(self) -> ExnResult<Self::Success>;
251}
252
253impl<T> BoxedResultExt for std::result::Result<T, Box<dyn std::error::Error + Send + Sync + 'static>> {
254    type Success = T;
255
256    #[track_caller]
257    fn or_erased(self) -> ExnResult<Self::Success> {
258        match self {
259            Ok(v) => Ok(v),
260            Err(e) => Err(Exn::new(crate::exn::Untyped::from_boxed(e))),
261        }
262    }
263}
264
265impl<T, E> ResultExt for ExnResult<T, E>
266where
267    E: std::error::Error + Send + Sync + 'static,
268{
269    type Success = T;
270    type Error = E;
271
272    #[track_caller]
273    fn or_raise_typed<A, F>(self, err: F) -> ExnResult<Self::Success, A>
274    where
275        A: std::error::Error + Send + Sync + 'static,
276        F: FnOnce() -> A,
277    {
278        match self {
279            Ok(v) => Ok(v),
280            Err(e) => Err(e.raise(err())),
281        }
282    }
283
284    fn or_error(self) -> Result<T> {
285        self.map_err(Exn::into_error)
286    }
287
288    #[track_caller]
289    fn or_erased(self) -> ExnResult<Self::Success> {
290        match self {
291            Ok(v) => Ok(v),
292            Err(e) => Err(e.erased()),
293        }
294    }
295}