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}