use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct ErrorContext {
user_message: Option<String>,
context: HashMap<String, String>,
timestamp: Option<std::time::SystemTime>,
request_id: Option<String>,
operation: Option<String>,
component: Option<String>,
}
impl ErrorContext {
pub fn new() -> Self {
Self {
user_message: None,
context: HashMap::new(),
timestamp: Some(std::time::SystemTime::now()),
request_id: None,
operation: None,
component: None,
}
}
pub fn with_user_message(message: impl Into<String>) -> Self {
Self {
user_message: Some(message.into()),
context: HashMap::new(),
timestamp: Some(std::time::SystemTime::now()),
request_id: None,
operation: None,
component: None,
}
}
pub fn with_operation(operation: impl Into<String>) -> Self {
Self {
user_message: None,
context: HashMap::new(),
timestamp: Some(std::time::SystemTime::now()),
request_id: None,
operation: Some(operation.into()),
component: None,
}
}
pub fn set_user_message(&mut self, message: impl Into<String>) {
self.user_message = Some(message.into());
}
pub fn user_message(&self) -> Option<&str> {
self.user_message.as_deref()
}
pub fn add_context(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
self.context.insert(key.into(), value.into());
self
}
pub fn extend_context<I, K, V>(&mut self, iter: I) -> &mut Self
where
I: IntoIterator<Item = (K, V)>,
K: Into<String>,
V: Into<String>,
{
self.context
.extend(iter.into_iter().map(|(k, v)| (k.into(), v.into())));
self
}
pub fn get_context(&self, key: &str) -> Option<&str> {
self.context.get(key).map(|s| s.as_str())
}
pub fn all_context(&self) -> &HashMap<String, String> {
&self.context
}
pub fn has_context(&self, key: &str) -> bool {
self.context.contains_key(key)
}
pub fn set_request_id(&mut self, request_id: impl Into<String>) -> &mut Self {
self.request_id = Some(request_id.into());
self
}
pub fn request_id(&self) -> Option<&str> {
self.request_id.as_deref()
}
pub fn set_operation(&mut self, operation: impl Into<String>) -> &mut Self {
self.operation = Some(operation.into());
self
}
pub fn operation(&self) -> Option<&str> {
self.operation.as_deref()
}
pub fn set_component(&mut self, component: impl Into<String>) -> &mut Self {
self.component = Some(component.into());
self
}
pub fn component(&self) -> Option<&str> {
self.component.as_deref()
}
pub fn timestamp(&self) -> Option<std::time::SystemTime> {
self.timestamp
}
pub fn clear_context(&mut self) {
self.context.clear();
}
pub fn context_len(&self) -> usize {
self.context.len()
}
pub fn is_empty(&self) -> bool {
self.user_message.is_none()
&& self.context.is_empty()
&& self.request_id.is_none()
&& self.operation.is_none()
&& self.component.is_none()
}
pub fn debug_format(&self) -> String {
let mut parts = Vec::new();
if let Some(timestamp) = self.timestamp {
let duration = timestamp
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default();
let secs = duration.as_secs();
let datetime =
chrono::DateTime::from_timestamp(secs as i64, 0).unwrap_or_else(chrono::Utc::now);
parts.push(format!(
"时间: {}",
datetime.format("%Y-%m-%d %H:%M:%S UTC")
));
}
if let Some(component) = &self.component {
parts.push(format!("组件: {}", component));
}
if let Some(operation) = &self.operation {
parts.push(format!("操作: {}", operation));
}
if let Some(request_id) = &self.request_id {
parts.push(format!("请求ID: {}", request_id));
}
if !self.context.is_empty() {
parts.push("上下文:".to_string());
for (key, value) in &self.context {
parts.push(format!(" {}: {}", key, value));
}
}
if let Some(message) = &self.user_message {
parts.push(format!("用户消息: {}", message));
}
parts.join("\n")
}
pub fn clone_with(&self) -> Self {
let mut clone = self.clone();
clone.timestamp = Some(std::time::SystemTime::now());
clone
}
}
impl Default for ErrorContext {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ErrorContextBuilder {
context: ErrorContext,
}
impl ErrorContextBuilder {
pub fn new() -> Self {
Self {
context: ErrorContext::new(),
}
}
pub fn user_message(mut self, message: impl Into<String>) -> Self {
self.context.set_user_message(message);
self
}
pub fn context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.context.add_context(key, value);
self
}
pub fn extend_context<I, K, V>(mut self, iter: I) -> Self
where
I: IntoIterator<Item = (K, V)>,
K: Into<String>,
V: Into<String>,
{
self.context.extend_context(iter);
self
}
pub fn request_id(mut self, request_id: impl Into<String>) -> Self {
self.context.set_request_id(request_id);
self
}
pub fn operation(mut self, operation: impl Into<String>) -> Self {
self.context.set_operation(operation);
self
}
pub fn component(mut self, component: impl Into<String>) -> Self {
self.context.set_component(component);
self
}
pub fn build(self) -> ErrorContext {
self.context
}
}
impl Default for ErrorContextBuilder {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for ErrorContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.debug_format())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_context_basic() {
let mut context = ErrorContext::new();
context.add_context("endpoint", "https://api.example.com");
context.set_user_message("连接失败");
assert!(context.has_context("endpoint"));
assert_eq!(
context.get_context("endpoint"),
Some("https://api.example.com")
);
assert_eq!(context.user_message(), Some("连接失败"));
}
#[test]
fn test_error_context_constructors() {
let context_with_msg = ErrorContext::with_user_message("测试消息");
assert_eq!(context_with_msg.user_message(), Some("测试消息"));
let context_with_op = ErrorContext::with_operation("api_call");
assert_eq!(context_with_op.operation(), Some("api_call"));
}
#[test]
fn test_error_context_builder() {
let context = ErrorContextBuilder::new()
.user_message("构建器测试")
.context("url", "https://example.com")
.context("method", "GET")
.operation("http_request")
.component("http_client")
.request_id("req-123")
.build();
assert_eq!(context.user_message(), Some("构建器测试"));
assert_eq!(context.get_context("url"), Some("https://example.com"));
assert_eq!(context.get_context("method"), Some("GET"));
assert_eq!(context.operation(), Some("http_request"));
assert_eq!(context.component(), Some("http_client"));
assert_eq!(context.request_id(), Some("req-123"));
}
#[test]
fn test_error_context_chain_operations() {
let mut context = ErrorContext::new()
.add_context("key1", "value1")
.add_context("key2", "value2")
.clone_with();
context
.add_context("key3", "value3")
.set_operation("test_operation");
assert_eq!(context.get_context("key1"), Some("value1"));
assert_eq!(context.get_context("key2"), Some("value2"));
assert_eq!(context.get_context("key3"), Some("value3"));
assert_eq!(context.operation(), Some("test_operation"));
}
#[test]
fn test_error_context_extend() {
let mut context = ErrorContext::new();
context.extend_context(vec![
("key1", "value1"),
("key2", "value2"),
("key3", "value3"),
]);
assert_eq!(context.context_len(), 3);
assert!(context.has_context("key1"));
assert!(context.has_context("key2"));
assert!(context.has_context("key3"));
}
#[test]
fn test_error_context_debug_format() {
let context = ErrorContextBuilder::new()
.user_message("测试错误")
.context("api", "send_message")
.operation("message_send")
.component("communication")
.request_id("req-456")
.build();
let debug_str = context.debug_format();
assert!(debug_str.contains("测试错误"));
assert!(debug_str.contains("api"));
assert!(debug_str.contains("send_message"));
assert!(debug_str.contains("message_send"));
assert!(debug_str.contains("communication"));
assert!(debug_str.contains("req-456"));
assert!(debug_str.contains("时间:"));
}
#[test]
fn test_error_context_is_empty() {
let empty_context = ErrorContext::new();
assert!(empty_context.is_empty());
let mut non_empty_context = ErrorContext::new();
non_empty_context.add_context("test", "value");
assert!(!non_empty_context.is_empty());
}
#[test]
fn test_error_context_clone_with() {
let mut original = ErrorContext::new();
original.add_context("original", "data");
original.set_user_message("原始消息");
let cloned = original.clone_with();
assert_eq!(cloned.get_context("original"), Some("data"));
assert_eq!(cloned.user_message(), Some("原始消息"));
assert!(cloned.timestamp() > original.timestamp());
}
}