Skip to main content

rajac_base/
result.rs

1use crate::error::RajacError;
2use crate::shared_string::SharedString;
3use std::error::Error as StdError;
4use std::panic::Location;
5
6pub type RajacResult<T> = Result<T, RajacError>;
7
8pub trait ResultExt<T> {
9    #[track_caller]
10    fn context(self, context: impl Into<SharedString>) -> RajacResult<T>;
11
12    #[track_caller]
13    fn with_context<C, S>(self, context: C) -> RajacResult<T>
14    where
15        C: FnOnce() -> S,
16        S: Into<SharedString>;
17}
18
19pub trait OptionExt<T> {
20    #[track_caller]
21    fn context(self, context: impl Into<SharedString>) -> RajacResult<T>;
22
23    #[track_caller]
24    fn with_context<C, S>(self, context: C) -> RajacResult<T>
25    where
26        C: FnOnce() -> S,
27        S: Into<SharedString>;
28}
29
30impl<T, E> ResultExt<T> for Result<T, E>
31where
32    E: StdError + Send + Sync + 'static,
33{
34    #[track_caller]
35    fn context(self, context: impl Into<SharedString>) -> RajacResult<T> {
36        let caller = Location::caller();
37        self.map_err(|error| {
38            RajacError::message_at_location(context.into(), caller)
39                .with_std_source_at_location(error, caller)
40        })
41    }
42
43    #[track_caller]
44    fn with_context<C, S>(self, context: C) -> RajacResult<T>
45    where
46        C: FnOnce() -> S,
47        S: Into<SharedString>,
48    {
49        let caller = Location::caller();
50        self.map_err(|error| {
51            RajacError::message_at_location(context(), caller)
52                .with_std_source_at_location(error, caller)
53        })
54    }
55}
56
57impl<T> ResultExt<T> for Result<T, RajacError> {
58    #[track_caller]
59    fn context(self, context: impl Into<SharedString>) -> RajacResult<T> {
60        let caller = Location::caller();
61        self.map_err(|error| {
62            RajacError::message_at_location(context.into(), caller).with_source(error)
63        })
64    }
65
66    #[track_caller]
67    fn with_context<C, S>(self, context: C) -> RajacResult<T>
68    where
69        C: FnOnce() -> S,
70        S: Into<SharedString>,
71    {
72        let caller = Location::caller();
73        self.map_err(|error| RajacError::message_at_location(context(), caller).with_source(error))
74    }
75}
76
77impl<T> OptionExt<T> for Option<T> {
78    #[track_caller]
79    fn context(self, context: impl Into<SharedString>) -> RajacResult<T> {
80        let caller = Location::caller();
81        self.ok_or_else(|| RajacError::message_at_location(context.into(), caller))
82    }
83
84    #[track_caller]
85    fn with_context<C, S>(self, context: C) -> RajacResult<T>
86    where
87        C: FnOnce() -> S,
88        S: Into<SharedString>,
89    {
90        let caller = Location::caller();
91        self.ok_or_else(|| RajacError::message_at_location(context(), caller))
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use crate::result::{OptionExt, ResultExt};
98    use std::io;
99
100    #[test]
101    fn test_with_context_is_lazy_for_ok_results() {
102        use std::cell::Cell;
103
104        let context_called = Cell::new(false);
105        let result: Result<i32, io::Error> = Ok(123);
106        let value = result
107            .with_context(|| {
108                context_called.set(true);
109                "should not be used"
110            })
111            .unwrap();
112        assert_eq!(value, 123);
113        assert!(!context_called.get());
114    }
115
116    #[test]
117    fn test_option_with_context_is_lazy_for_some_results() {
118        use std::cell::Cell;
119
120        let context_called = Cell::new(false);
121        let value = Some(123)
122            .with_context(|| {
123                context_called.set(true);
124                "should not be used"
125            })
126            .unwrap();
127        assert_eq!(value, 123);
128        assert!(!context_called.get());
129    }
130
131    #[test]
132    fn test_with_context_wraps_rajac_error_results() {
133        let result: crate::result::RajacResult<i32> = Err(crate::err!("root cause"));
134        let error = result.with_context(|| "outer context").unwrap_err();
135        let rendered = error.to_test_string();
136
137        assert!(rendered.contains("outer context"));
138        assert!(rendered.contains("root cause"));
139    }
140}