1use std::collections::HashMap;
6
7#[derive(Debug, Clone)]
11pub struct ErrorContext {
12 user_message: Option<String>,
14 context: HashMap<String, String>,
16 timestamp: Option<std::time::SystemTime>,
18 request_id: Option<String>,
20 operation: Option<String>,
22 component: Option<String>,
24}
25
26impl ErrorContext {
27 pub fn new() -> Self {
29 Self {
30 user_message: None,
31 context: HashMap::new(),
32 timestamp: Some(std::time::SystemTime::now()),
33 request_id: None,
34 operation: None,
35 component: None,
36 }
37 }
38
39 pub fn with_user_message(message: impl Into<String>) -> Self {
41 Self {
42 user_message: Some(message.into()),
43 context: HashMap::new(),
44 timestamp: Some(std::time::SystemTime::now()),
45 request_id: None,
46 operation: None,
47 component: None,
48 }
49 }
50
51 pub fn with_operation(operation: impl Into<String>) -> Self {
53 Self {
54 user_message: None,
55 context: HashMap::new(),
56 timestamp: Some(std::time::SystemTime::now()),
57 request_id: None,
58 operation: Some(operation.into()),
59 component: None,
60 }
61 }
62
63 pub fn set_user_message(&mut self, message: impl Into<String>) {
65 self.user_message = Some(message.into());
66 }
67
68 pub fn user_message(&self) -> Option<&str> {
70 self.user_message.as_deref()
71 }
72
73 pub fn add_context(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
75 self.context.insert(key.into(), value.into());
76 self
77 }
78
79 pub fn extend_context<I, K, V>(&mut self, iter: I) -> &mut Self
81 where
82 I: IntoIterator<Item = (K, V)>,
83 K: Into<String>,
84 V: Into<String>,
85 {
86 self.context
87 .extend(iter.into_iter().map(|(k, v)| (k.into(), v.into())));
88 self
89 }
90
91 pub fn get_context(&self, key: &str) -> Option<&str> {
93 self.context.get(key).map(|s| s.as_str())
94 }
95
96 pub fn all_context(&self) -> &HashMap<String, String> {
98 &self.context
99 }
100
101 pub fn has_context(&self, key: &str) -> bool {
103 self.context.contains_key(key)
104 }
105
106 pub fn set_request_id(&mut self, request_id: impl Into<String>) -> &mut Self {
108 self.request_id = Some(request_id.into());
109 self
110 }
111
112 pub fn request_id(&self) -> Option<&str> {
114 self.request_id.as_deref()
115 }
116
117 pub fn set_operation(&mut self, operation: impl Into<String>) -> &mut Self {
119 self.operation = Some(operation.into());
120 self
121 }
122
123 pub fn operation(&self) -> Option<&str> {
125 self.operation.as_deref()
126 }
127
128 pub fn set_component(&mut self, component: impl Into<String>) -> &mut Self {
130 self.component = Some(component.into());
131 self
132 }
133
134 pub fn component(&self) -> Option<&str> {
136 self.component.as_deref()
137 }
138
139 pub fn timestamp(&self) -> Option<std::time::SystemTime> {
141 self.timestamp
142 }
143
144 pub fn clear_context(&mut self) {
147 self.context.clear();
148 }
149
150 pub fn context_len(&self) -> usize {
152 self.context.len()
153 }
154
155 pub fn is_empty(&self) -> bool {
157 self.user_message.is_none()
158 && self.context.is_empty()
159 && self.request_id.is_none()
160 && self.operation.is_none()
161 && self.component.is_none()
162 }
163
164 pub fn debug_format(&self) -> String {
166 let mut parts = Vec::new();
167
168 if let Some(timestamp) = self.timestamp {
169 let duration = timestamp
171 .duration_since(std::time::UNIX_EPOCH)
172 .unwrap_or_default();
173 let secs = duration.as_secs();
174 let datetime =
175 chrono::DateTime::from_timestamp(secs as i64, 0).unwrap_or_else(chrono::Utc::now);
176 parts.push(format!(
177 "时间: {}",
178 datetime.format("%Y-%m-%d %H:%M:%S UTC")
179 ));
180 }
181
182 if let Some(component) = &self.component {
183 parts.push(format!("组件: {}", component));
184 }
185
186 if let Some(operation) = &self.operation {
187 parts.push(format!("操作: {}", operation));
188 }
189
190 if let Some(request_id) = &self.request_id {
191 parts.push(format!("请求ID: {}", request_id));
192 }
193
194 if !self.context.is_empty() {
195 parts.push("上下文:".to_string());
196 for (key, value) in &self.context {
197 parts.push(format!(" {}: {}", key, value));
198 }
199 }
200
201 if let Some(message) = &self.user_message {
202 parts.push(format!("用户消息: {}", message));
203 }
204
205 parts.join("\n")
206 }
207
208 pub fn clone_with(&self) -> Self {
210 let mut clone = self.clone();
211 clone.timestamp = Some(std::time::SystemTime::now());
212 clone
213 }
214}
215
216impl Default for ErrorContext {
217 fn default() -> Self {
218 Self::new()
219 }
220}
221
222#[derive(Debug, Clone)]
226pub struct ErrorContextBuilder {
227 context: ErrorContext,
228}
229
230impl ErrorContextBuilder {
231 pub fn new() -> Self {
233 Self {
234 context: ErrorContext::new(),
235 }
236 }
237
238 pub fn user_message(mut self, message: impl Into<String>) -> Self {
240 self.context.set_user_message(message);
241 self
242 }
243
244 pub fn context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
246 self.context.add_context(key, value);
247 self
248 }
249
250 pub fn extend_context<I, K, V>(mut self, iter: I) -> Self
252 where
253 I: IntoIterator<Item = (K, V)>,
254 K: Into<String>,
255 V: Into<String>,
256 {
257 self.context.extend_context(iter);
258 self
259 }
260
261 pub fn request_id(mut self, request_id: impl Into<String>) -> Self {
263 self.context.set_request_id(request_id);
264 self
265 }
266
267 pub fn operation(mut self, operation: impl Into<String>) -> Self {
269 self.context.set_operation(operation);
270 self
271 }
272
273 pub fn component(mut self, component: impl Into<String>) -> Self {
275 self.context.set_component(component);
276 self
277 }
278
279 pub fn build(self) -> ErrorContext {
281 self.context
282 }
283}
284
285impl Default for ErrorContextBuilder {
286 fn default() -> Self {
287 Self::new()
288 }
289}
290
291impl std::fmt::Display for ErrorContext {
292 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293 write!(f, "{}", self.debug_format())
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_error_context_basic() {
303 let mut context = ErrorContext::new();
304 context.add_context("endpoint", "https://api.example.com");
305 context.set_user_message("连接失败");
306
307 assert!(context.has_context("endpoint"));
308 assert_eq!(
309 context.get_context("endpoint"),
310 Some("https://api.example.com")
311 );
312 assert_eq!(context.user_message(), Some("连接失败"));
313 }
314
315 #[test]
316 fn test_error_context_constructors() {
317 let context_with_msg = ErrorContext::with_user_message("测试消息");
318 assert_eq!(context_with_msg.user_message(), Some("测试消息"));
319
320 let context_with_op = ErrorContext::with_operation("api_call");
321 assert_eq!(context_with_op.operation(), Some("api_call"));
322 }
323
324 #[test]
325 fn test_error_context_builder() {
326 let context = ErrorContextBuilder::new()
327 .user_message("构建器测试")
328 .context("url", "https://example.com")
329 .context("method", "GET")
330 .operation("http_request")
331 .component("http_client")
332 .request_id("req-123")
333 .build();
334
335 assert_eq!(context.user_message(), Some("构建器测试"));
336 assert_eq!(context.get_context("url"), Some("https://example.com"));
337 assert_eq!(context.get_context("method"), Some("GET"));
338 assert_eq!(context.operation(), Some("http_request"));
339 assert_eq!(context.component(), Some("http_client"));
340 assert_eq!(context.request_id(), Some("req-123"));
341 }
342
343 #[test]
344 fn test_error_context_chain_operations() {
345 let mut context = ErrorContext::new()
346 .add_context("key1", "value1")
347 .add_context("key2", "value2")
348 .clone_with();
349
350 context
351 .add_context("key3", "value3")
352 .set_operation("test_operation");
353
354 assert_eq!(context.get_context("key1"), Some("value1"));
355 assert_eq!(context.get_context("key2"), Some("value2"));
356 assert_eq!(context.get_context("key3"), Some("value3"));
357 assert_eq!(context.operation(), Some("test_operation"));
358 }
359
360 #[test]
361 fn test_error_context_extend() {
362 let mut context = ErrorContext::new();
363 context.extend_context(vec![
364 ("key1", "value1"),
365 ("key2", "value2"),
366 ("key3", "value3"),
367 ]);
368
369 assert_eq!(context.context_len(), 3);
370 assert!(context.has_context("key1"));
371 assert!(context.has_context("key2"));
372 assert!(context.has_context("key3"));
373 }
374
375 #[test]
376 fn test_error_context_debug_format() {
377 let context = ErrorContextBuilder::new()
378 .user_message("测试错误")
379 .context("api", "send_message")
380 .operation("message_send")
381 .component("communication")
382 .request_id("req-456")
383 .build();
384
385 let debug_str = context.debug_format();
386 assert!(debug_str.contains("测试错误"));
387 assert!(debug_str.contains("api"));
388 assert!(debug_str.contains("send_message"));
389 assert!(debug_str.contains("message_send"));
390 assert!(debug_str.contains("communication"));
391 assert!(debug_str.contains("req-456"));
392 assert!(debug_str.contains("时间:"));
393 }
394
395 #[test]
396 fn test_error_context_is_empty() {
397 let empty_context = ErrorContext::new();
398 assert!(empty_context.is_empty());
399
400 let mut non_empty_context = ErrorContext::new();
401 non_empty_context.add_context("test", "value");
402 assert!(!non_empty_context.is_empty());
403 }
404
405 #[test]
406 fn test_error_context_clone_with() {
407 let mut original = ErrorContext::new();
408 original.add_context("original", "data");
409 original.set_user_message("原始消息");
410
411 let cloned = original.clone_with();
412
413 assert_eq!(cloned.get_context("original"), Some("data"));
415 assert_eq!(cloned.user_message(), Some("原始消息"));
416
417 assert!(cloned.timestamp() > original.timestamp());
419 }
420}