1use crate::error::ForgeError;
2use std::fmt;
3
4#[derive(Debug)]
6pub struct ContextError<E, C> {
7 pub error: E,
9 pub context: C,
11}
12
13impl<E, C> ContextError<E, C> {
14 pub fn new(error: E, context: C) -> Self {
16 Self { error, context }
17 }
18
19 pub fn into_error(self) -> E {
21 self.error
22 }
23
24 pub fn map_context<D, F>(self, f: F) -> ContextError<E, D>
26 where
27 F: FnOnce(C) -> D,
28 {
29 ContextError {
30 error: self.error,
31 context: f(self.context),
32 }
33 }
34
35 pub fn context<D>(self, context: D) -> ContextError<Self, D>
37 where
38 D: std::fmt::Display + std::fmt::Debug + Send + Sync + 'static,
39 {
40 ContextError::new(self, context)
41 }
42}
43
44impl<E: fmt::Display, C: fmt::Display> fmt::Display for ContextError<E, C> {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 write!(f, "{}: {}", self.context, self.error)
47 }
48}
49
50impl<E: std::error::Error + 'static, C: fmt::Display + fmt::Debug + Send + Sync + 'static>
51 std::error::Error for ContextError<E, C>
52{
53 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
54 Some(&self.error)
55 }
56}
57
58pub trait ResultExt<T, E> {
60 fn context<C>(self, context: C) -> Result<T, ContextError<E, C>>;
62
63 fn with_context<C, F>(self, f: F) -> Result<T, ContextError<E, C>>
65 where
66 F: FnOnce() -> C;
67}
68
69impl<T, E> ResultExt<T, E> for Result<T, E> {
70 fn context<C>(self, context: C) -> Result<T, ContextError<E, C>> {
71 self.map_err(|error| ContextError::new(error, context))
72 }
73
74 fn with_context<C, F>(self, f: F) -> Result<T, ContextError<E, C>>
75 where
76 F: FnOnce() -> C,
77 {
78 self.map_err(|error| ContextError::new(error, f()))
79 }
80}
81
82impl<E: ForgeError, C: fmt::Display + fmt::Debug + Send + Sync + 'static> ForgeError
84 for ContextError<E, C>
85{
86 fn kind(&self) -> &'static str {
87 self.error.kind()
88 }
89
90 fn caption(&self) -> &'static str {
91 self.error.caption()
92 }
93
94 fn is_retryable(&self) -> bool {
95 self.error.is_retryable()
96 }
97
98 fn is_fatal(&self) -> bool {
99 self.error.is_fatal()
100 }
101
102 fn status_code(&self) -> u16 {
103 self.error.status_code()
104 }
105
106 fn exit_code(&self) -> i32 {
107 self.error.exit_code()
108 }
109
110 fn user_message(&self) -> String {
111 format!("{}: {}", self.context, self.error.user_message())
112 }
113
114 fn dev_message(&self) -> String {
115 format!("{}: {}", self.context, self.error.dev_message())
116 }
117
118 fn backtrace(&self) -> Option<&std::backtrace::Backtrace> {
119 self.error.backtrace()
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use crate::AppError;
127
128 #[test]
129 fn test_context_error() {
130 let error = AppError::config("Invalid config");
131 let ctx_error = error.context("Failed to load settings");
132
133 assert_eq!(
134 ctx_error.to_string(),
135 "Failed to load settings: ⚙️ Configuration Error: Invalid config"
136 );
137 assert_eq!(ctx_error.kind(), "Config");
138 assert_eq!(ctx_error.caption(), "⚙️ Configuration");
139 }
140
141 #[test]
142 fn test_result_context() {
143 let result: Result<(), AppError> = Err(AppError::config("Invalid config"));
144 let ctx_result = result.context("Failed to load settings");
145
146 assert!(ctx_result.is_err());
147 let err = ctx_result.unwrap_err();
148 assert_eq!(
149 err.to_string(),
150 "Failed to load settings: ⚙️ Configuration Error: Invalid config"
151 );
152 }
153}