1use serde::{Deserialize, Serialize};
6use std::fmt;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct MetadataError {
11 pub kind: ErrorKind,
13 pub message: String,
15 pub context: Option<String>,
17 pub source: Option<String>,
19 pub timestamp: Option<String>,
21 pub metadata: std::collections::HashMap<String, String>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
27pub enum ErrorKind {
28 Validation,
30 Network,
32 FileSystem,
34 Serialization,
36 Configuration,
38 Security,
40 Performance,
42 Browser,
44 Storage,
46 ImageProcessing,
48 Template,
50 Unknown,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
56pub enum ErrorSeverity {
57 Low,
59 Medium,
61 High,
63 Critical,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct ErrorContext {
70 pub component: Option<String>,
72 pub operation: Option<String>,
74 pub environment: Option<String>,
76 pub request_id: Option<String>,
78 pub stack_trace: Option<String>,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct ErrorReportingConfig {
85 pub enabled: bool,
87 pub max_errors_per_session: usize,
89 pub endpoint: Option<String>,
91 pub include_stack_traces: bool,
93 pub include_user_context: bool,
95 pub sampling_rate: f64,
97}
98
99impl MetadataError {
100 pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
102 Self {
103 kind,
104 message: message.into(),
105 context: None,
106 source: None,
107 timestamp: Some(Self::current_timestamp()),
108 metadata: std::collections::HashMap::new(),
109 }
110 }
111
112 pub fn with_context(mut self, context: impl Into<String>) -> Self {
114 self.context = Some(context.into());
115 self
116 }
117
118 pub fn with_source(mut self, source: impl Into<String>) -> Self {
120 self.source = Some(source.into());
121 self
122 }
123
124 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
126 self.metadata.insert(key.into(), value.into());
127 self
128 }
129
130 pub fn severity(&self) -> ErrorSeverity {
132 match self.kind {
133 ErrorKind::Validation => ErrorSeverity::Medium,
134 ErrorKind::Network => ErrorSeverity::High,
135 ErrorKind::FileSystem => ErrorSeverity::High,
136 ErrorKind::Serialization => ErrorSeverity::Medium,
137 ErrorKind::Configuration => ErrorSeverity::High,
138 ErrorKind::Security => ErrorSeverity::Critical,
139 ErrorKind::Performance => ErrorSeverity::Low,
140 ErrorKind::Browser => ErrorSeverity::Medium,
141 ErrorKind::Storage => ErrorSeverity::Medium,
142 ErrorKind::ImageProcessing => ErrorSeverity::Medium,
143 ErrorKind::Template => ErrorSeverity::Medium,
144 ErrorKind::Unknown => ErrorSeverity::High,
145 }
146 }
147
148 pub fn is_recoverable(&self) -> bool {
150 matches!(
151 self.kind,
152 ErrorKind::Validation
153 | ErrorKind::Performance
154 | ErrorKind::Browser
155 | ErrorKind::Storage
156 )
157 }
158
159 pub fn user_message(&self) -> String {
161 match self.kind {
162 ErrorKind::Validation => "Please check your input and try again.".to_string(),
163 ErrorKind::Network => {
164 "Network connection issue. Please check your internet connection.".to_string()
165 }
166 ErrorKind::FileSystem => "File system error. Please try again.".to_string(),
167 ErrorKind::Serialization => "Data processing error. Please try again.".to_string(),
168 ErrorKind::Configuration => "Configuration error. Please contact support.".to_string(),
169 ErrorKind::Security => "Security error detected. Please refresh the page.".to_string(),
170 ErrorKind::Performance => {
171 "Performance issue detected. The page may be slow.".to_string()
172 }
173 ErrorKind::Browser => {
174 "Browser compatibility issue. Please try a different browser.".to_string()
175 }
176 ErrorKind::Storage => "Storage error. Your data may not be saved.".to_string(),
177 ErrorKind::ImageProcessing => {
178 "Image processing error. Please try a different image.".to_string()
179 }
180 ErrorKind::Template => "Template rendering error. Please try again.".to_string(),
181 ErrorKind::Unknown => "An unexpected error occurred. Please try again.".to_string(),
182 }
183 }
184
185 pub fn to_json(&self) -> Result<String, serde_json::Error> {
187 serde_json::to_string_pretty(self)
188 }
189
190 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
192 serde_json::from_str(json)
193 }
194
195 fn current_timestamp() -> String {
197 #[cfg(target_arch = "wasm32")]
198 {
199 if let Some(_window) = web_sys::window() {
201 let date = js_sys::Date::new_0();
202 date.to_iso_string()
203 .as_string()
204 .unwrap_or_else(|| "unknown".to_string())
205 } else {
206 "unknown".to_string()
207 }
208 }
209 #[cfg(not(target_arch = "wasm32"))]
210 {
211 use std::time::{SystemTime, UNIX_EPOCH};
213 SystemTime::now()
214 .duration_since(UNIX_EPOCH)
215 .map(|d| d.as_secs().to_string())
216 .unwrap_or_else(|_| "unknown".to_string())
217 }
218 }
219}
220
221impl fmt::Display for MetadataError {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 write!(f, "[{:?}] {}", self.kind, self.message)?;
224 if let Some(context) = &self.context {
225 write!(f, " (Context: {})", context)?;
226 }
227 if let Some(source) = &self.source {
228 write!(f, " (Source: {})", source)?;
229 }
230 Ok(())
231 }
232}
233
234impl std::error::Error for MetadataError {}
235
236pub type MetadataResult<T> = Result<T, MetadataError>;
238
239pub trait ErrorHandler {
241 fn handle_error(&self, error: &MetadataError) -> Result<(), MetadataError>;
243
244 fn report_error(&self, error: &MetadataError) -> Result<(), MetadataError>;
246
247 fn log_error(&self, error: &MetadataError) -> Result<(), MetadataError>;
249}
250
251#[cfg(target_arch = "wasm32")]
253pub struct ConsoleErrorHandler {
254 config: ErrorReportingConfig,
255}
256
257#[cfg(target_arch = "wasm32")]
258impl ConsoleErrorHandler {
259 pub fn new(config: ErrorReportingConfig) -> Self {
260 Self { config }
261 }
262}
263
264#[cfg(target_arch = "wasm32")]
265impl ErrorHandler for ConsoleErrorHandler {
266 fn handle_error(&self, error: &MetadataError) -> Result<(), MetadataError> {
267 web_sys::console::error_1(&format!("[ERROR] {}", error).into());
269
270 if self.config.enabled {
272 self.report_error(error)?;
273 }
274
275 Ok(())
276 }
277
278 fn report_error(&self, error: &MetadataError) -> Result<(), MetadataError> {
279 web_sys::console::warn_1(&format!("[REPORT] {}", error).into());
281 Ok(())
282 }
283
284 fn log_error(&self, error: &MetadataError) -> Result<(), MetadataError> {
285 web_sys::console::log_1(&format!("[LOG] {}", error).into());
286 Ok(())
287 }
288}
289
290#[cfg(not(target_arch = "wasm32"))]
292pub struct FileErrorHandler {
293 config: ErrorReportingConfig,
294 log_file: Option<std::path::PathBuf>,
295}
296
297#[cfg(not(target_arch = "wasm32"))]
298impl FileErrorHandler {
299 pub fn new(config: ErrorReportingConfig, log_file: Option<std::path::PathBuf>) -> Self {
300 Self { config, log_file }
301 }
302}
303
304#[cfg(not(target_arch = "wasm32"))]
305impl ErrorHandler for FileErrorHandler {
306 fn handle_error(&self, error: &MetadataError) -> Result<(), MetadataError> {
307 eprintln!("[ERROR] {}", error);
309
310 if let Some(log_file) = &self.log_file {
312 if let Ok(mut file) = std::fs::OpenOptions::new()
313 .create(true)
314 .append(true)
315 .open(log_file)
316 {
317 use std::io::Write;
318 let _ = writeln!(
319 file,
320 "[{}] [ERROR] {}",
321 chrono::Utc::now().to_rfc3339(),
322 error
323 );
324 }
325 }
326
327 if self.config.enabled {
329 self.report_error(error)?;
330 }
331
332 Ok(())
333 }
334
335 fn report_error(&self, error: &MetadataError) -> Result<(), MetadataError> {
336 eprintln!("[REPORT] {}", error);
338 Ok(())
339 }
340
341 fn log_error(&self, error: &MetadataError) -> Result<(), MetadataError> {
342 println!("[LOG] {}", error);
343 Ok(())
344 }
345}
346
347pub struct ErrorContextBuilder {
349 context: ErrorContext,
350}
351
352impl ErrorContextBuilder {
353 pub fn new() -> Self {
354 Self {
355 context: ErrorContext {
356 component: None,
357 operation: None,
358 environment: None,
359 request_id: None,
360 stack_trace: None,
361 },
362 }
363 }
364
365 pub fn component(mut self, component: impl Into<String>) -> Self {
366 self.context.component = Some(component.into());
367 self
368 }
369
370 pub fn operation(mut self, operation: impl Into<String>) -> Self {
371 self.context.operation = Some(operation.into());
372 self
373 }
374
375 pub fn environment(mut self, environment: impl Into<String>) -> Self {
376 self.context.environment = Some(environment.into());
377 self
378 }
379
380 pub fn request_id(mut self, request_id: impl Into<String>) -> Self {
381 self.context.request_id = Some(request_id.into());
382 self
383 }
384
385 pub fn stack_trace(mut self, stack_trace: impl Into<String>) -> Self {
386 self.context.stack_trace = Some(stack_trace.into());
387 self
388 }
389
390 pub fn build(self) -> ErrorContext {
391 self.context
392 }
393}
394
395pub struct ErrorUtils;
397
398impl ErrorUtils {
399 pub fn validation_error(message: impl Into<String>) -> MetadataError {
401 MetadataError::new(ErrorKind::Validation, message)
402 }
403
404 pub fn network_error(message: impl Into<String>) -> MetadataError {
406 MetadataError::new(ErrorKind::Network, message)
407 }
408
409 pub fn security_error(message: impl Into<String>) -> MetadataError {
411 MetadataError::new(ErrorKind::Security, message)
412 }
413
414 #[cfg(target_arch = "wasm32")]
416 pub fn browser_error(message: impl Into<String>) -> MetadataError {
417 MetadataError::new(ErrorKind::Browser, message)
418 }
419
420 pub fn storage_error(message: impl Into<String>) -> MetadataError {
422 MetadataError::new(ErrorKind::Storage, message)
423 }
424
425 pub fn image_processing_error(message: impl Into<String>) -> MetadataError {
427 MetadataError::new(ErrorKind::ImageProcessing, message)
428 }
429
430 pub fn wrap_error(
432 error: impl std::error::Error + Send + Sync + 'static,
433 kind: ErrorKind,
434 ) -> MetadataError {
435 MetadataError::new(kind, error.to_string())
436 }
437
438 pub fn get_error_stats(errors: &[MetadataError]) -> ErrorStats {
440 let mut stats = ErrorStats::default();
441
442 for error in errors {
443 stats.total_errors += 1;
444
445 match error.kind {
446 ErrorKind::Validation => stats.validation_errors += 1,
447 ErrorKind::Network => stats.network_errors += 1,
448 ErrorKind::Security => stats.security_errors += 1,
449 ErrorKind::Browser => stats.browser_errors += 1,
450 ErrorKind::Storage => stats.storage_errors += 1,
451 ErrorKind::ImageProcessing => stats.image_processing_errors += 1,
452 _ => stats.other_errors += 1,
453 }
454
455 match error.severity() {
456 ErrorSeverity::Low => stats.low_severity += 1,
457 ErrorSeverity::Medium => stats.medium_severity += 1,
458 ErrorSeverity::High => stats.high_severity += 1,
459 ErrorSeverity::Critical => stats.critical_severity += 1,
460 }
461 }
462
463 stats
464 }
465}
466
467#[derive(Debug, Default, Clone, Serialize, Deserialize)]
469pub struct ErrorStats {
470 pub total_errors: usize,
471 pub validation_errors: usize,
472 pub network_errors: usize,
473 pub security_errors: usize,
474 pub browser_errors: usize,
475 pub storage_errors: usize,
476 pub image_processing_errors: usize,
477 pub other_errors: usize,
478 pub low_severity: usize,
479 pub medium_severity: usize,
480 pub high_severity: usize,
481 pub critical_severity: usize,
482}
483
484impl ErrorStats {
485 pub fn error_rate_by_kind(&self) -> std::collections::HashMap<String, f64> {
487 let mut rates = std::collections::HashMap::new();
488 if self.total_errors > 0 {
489 rates.insert(
490 "validation".to_string(),
491 self.validation_errors as f64 / self.total_errors as f64,
492 );
493 rates.insert(
494 "network".to_string(),
495 self.network_errors as f64 / self.total_errors as f64,
496 );
497 rates.insert(
498 "security".to_string(),
499 self.security_errors as f64 / self.total_errors as f64,
500 );
501 rates.insert(
502 "browser".to_string(),
503 self.browser_errors as f64 / self.total_errors as f64,
504 );
505 rates.insert(
506 "storage".to_string(),
507 self.storage_errors as f64 / self.total_errors as f64,
508 );
509 rates.insert(
510 "image_processing".to_string(),
511 self.image_processing_errors as f64 / self.total_errors as f64,
512 );
513 rates.insert(
514 "other".to_string(),
515 self.other_errors as f64 / self.total_errors as f64,
516 );
517 }
518 rates
519 }
520
521 pub fn severity_distribution(&self) -> std::collections::HashMap<String, f64> {
523 let mut distribution = std::collections::HashMap::new();
524 if self.total_errors > 0 {
525 distribution.insert(
526 "low".to_string(),
527 self.low_severity as f64 / self.total_errors as f64,
528 );
529 distribution.insert(
530 "medium".to_string(),
531 self.medium_severity as f64 / self.total_errors as f64,
532 );
533 distribution.insert(
534 "high".to_string(),
535 self.high_severity as f64 / self.total_errors as f64,
536 );
537 distribution.insert(
538 "critical".to_string(),
539 self.critical_severity as f64 / self.total_errors as f64,
540 );
541 }
542 distribution
543 }
544}