Skip to main content

xls_rs/
error_traits.rs

1//! Trait-based error handling
2//!
3//! Provides trait-based error types for better composability and testability.
4
5use std::error::Error as StdError;
6use std::fmt;
7
8/// Trait for error types that can provide context
9pub trait ErrorContextProvider {
10    fn file(&self) -> Option<&str>;
11    fn row(&self) -> Option<usize>;
12    fn column(&self) -> Option<usize>;
13    fn cell_ref(&self) -> Option<&str>;
14    fn column_name(&self) -> Option<&str>;
15}
16
17/// Trait for error types that can be converted to user-friendly messages
18pub trait UserFriendlyError {
19    fn user_message(&self) -> String;
20    fn suggestion(&self) -> Option<String>;
21}
22
23/// Trait for error types that support error recovery
24pub trait RecoverableError {
25    fn can_recover(&self) -> bool;
26    fn recovery_action(&self) -> Option<String>;
27}
28
29/// Trait for error types that support error categorization
30pub trait ErrorCategory {
31    fn category(&self) -> ErrorCategoryType;
32    fn severity(&self) -> ErrorSeverity;
33}
34
35/// Error category types
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum ErrorCategoryType {
38    Validation,
39    IO,
40    Format,
41    Type,
42    Calculation,
43    Configuration,
44    Network,
45    Other,
46}
47
48/// Error severity levels
49#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
50pub enum ErrorSeverity {
51    Info,
52    Warning,
53    Error,
54    Critical,
55}
56
57/// Enhanced error type implementing all error traits
58#[derive(Debug)]
59pub struct TraitBasedError {
60    pub message: String,
61    pub category: ErrorCategoryType,
62    pub severity: ErrorSeverity,
63    pub context: ErrorContext,
64    pub suggestion: Option<String>,
65    pub recovery: Option<String>,
66}
67
68#[derive(Debug, Clone, Default)]
69pub struct ErrorContext {
70    pub file: Option<String>,
71    pub row: Option<usize>,
72    pub column: Option<usize>,
73    pub cell_ref: Option<String>,
74    pub column_name: Option<String>,
75}
76
77impl ErrorContextProvider for TraitBasedError {
78    fn file(&self) -> Option<&str> {
79        self.context.file.as_deref()
80    }
81
82    fn row(&self) -> Option<usize> {
83        self.context.row
84    }
85
86    fn column(&self) -> Option<usize> {
87        self.context.column
88    }
89
90    fn cell_ref(&self) -> Option<&str> {
91        self.context.cell_ref.as_deref()
92    }
93
94    fn column_name(&self) -> Option<&str> {
95        self.context.column_name.as_deref()
96    }
97}
98
99impl UserFriendlyError for TraitBasedError {
100    fn user_message(&self) -> String {
101        let mut msg = self.message.clone();
102
103        if let Some(file) = &self.context.file {
104            msg.push_str(&format!(" (file: {})", file));
105        }
106
107        if let Some(row) = self.context.row {
108            msg.push_str(&format!(" (row: {})", row + 1));
109        }
110
111        if let Some(col) = self.context.column {
112            msg.push_str(&format!(" (column: {})", col + 1));
113        }
114
115        msg
116    }
117
118    fn suggestion(&self) -> Option<String> {
119        self.suggestion.clone()
120    }
121}
122
123impl RecoverableError for TraitBasedError {
124    fn can_recover(&self) -> bool {
125        self.recovery.is_some()
126    }
127
128    fn recovery_action(&self) -> Option<String> {
129        self.recovery.clone()
130    }
131}
132
133impl ErrorCategory for TraitBasedError {
134    fn category(&self) -> ErrorCategoryType {
135        self.category
136    }
137
138    fn severity(&self) -> ErrorSeverity {
139        self.severity
140    }
141}
142
143impl fmt::Display for TraitBasedError {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        write!(f, "{}", self.user_message())
146    }
147}
148
149impl StdError for TraitBasedError {}
150
151impl TraitBasedError {
152    pub fn new(message: String, category: ErrorCategoryType, severity: ErrorSeverity) -> Self {
153        Self {
154            message,
155            category,
156            severity,
157            context: ErrorContext::default(),
158            suggestion: None,
159            recovery: None,
160        }
161    }
162
163    pub fn with_context(mut self, context: ErrorContext) -> Self {
164        self.context = context;
165        self
166    }
167
168    pub fn with_suggestion(mut self, suggestion: String) -> Self {
169        self.suggestion = Some(suggestion);
170        self
171    }
172
173    pub fn with_recovery(mut self, recovery: String) -> Self {
174        self.recovery = Some(recovery);
175        self
176    }
177}
178
179/// Helper to convert anyhow::Error to TraitBasedError
180pub trait ToTraitBasedError {
181    fn to_trait_error(
182        self,
183        category: ErrorCategoryType,
184        severity: ErrorSeverity,
185    ) -> TraitBasedError;
186}
187
188impl ToTraitBasedError for anyhow::Error {
189    fn to_trait_error(
190        self,
191        category: ErrorCategoryType,
192        severity: ErrorSeverity,
193    ) -> TraitBasedError {
194        TraitBasedError::new(self.to_string(), category, severity)
195    }
196}