1use quick_xml::Error as XmlError;
2use serde_json::Error as JsonError;
3use std::collections::HashMap;
4use std::error::Error;
5use std::io;
6use std::sync::atomic::{ AtomicU64, Ordering };
7use std::sync::{ Mutex, OnceLock };
8use std::time::{ SystemTime, UNIX_EPOCH };
9use thiserror::Error;
10use zip::result::ZipError;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum ErrorSeverity {
15 Warning,
17 Error,
19 Fatal,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum ErrorCategory {
26 FileSystem,
28 Network,
30 Parsing,
32 Validation,
34 Internal,
36}
37
38#[derive(Debug, Clone, Default)]
40pub struct ErrorContext {
41 pub file_path: Option<String>,
42 pub line_number: Option<u32>,
43 pub element_path: Option<String>,
44 pub operation: Option<String>,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49pub enum ErrorCode {
50 FileNotFound = 1001,
52 FilePermissionDenied = 1002,
53 FileCorrupted = 1003,
54 FileInvalidFormat = 1004,
55
56 IoError = 1100,
58 ZipError = 1101,
59
60 XmlParseError = 2001,
62 JsonParseError = 2002,
63 EncodingError = 2003,
64 MissingElement = 2004,
65 InvalidAttribute = 2005,
66 UnsupportedVersion = 2006,
67 InvalidNamespace = 2007,
68
69 XlsxWorksheetNotFound = 3001,
71 XlsxInvalidCellReference = 3002,
72 XlsxInvalidFormula = 3003,
73 XlsxInvalidStyleId = 3004,
74 XlsxSharedStringIndexOutOfRange = 3005,
75
76 DocxStyleNotFound = 4001,
78 DocxInvalidTableStructure = 4002,
79 DocxInvalidParagraphFormat = 4003,
80 DocxBookmarkNotFound = 4004,
81
82 PptxSlideNotFound = 5001,
84 PptxLayoutNotFound = 5002,
85 PptxInvalidShapeId = 5003,
86 PptxInvalidAnimation = 5004,
87
88 FormatError = 9001,
90 UnsupportedFormat = 9002,
91 StructureError = 9003,
92 OtherError = 9999,
93
94 FileError = 1000,
96 ParseError = 2000,
97 XlsxError = 3000,
98 DocxError = 4000,
99 PptxError = 5000,
100}
101
102#[derive(Debug, Clone)]
104pub struct RecoverySuggestion {
105 pub message: String,
106 pub action: RecoveryAction,
107}
108
109#[derive(Debug, Clone)]
110pub enum RecoveryAction {
111 Retry,
113 UseDefault,
115 SkipElement,
117 UserInput(String),
119 None,
121}
122
123#[derive(Error, Debug)]
125pub enum OfficeError {
126 #[error("IO错误: {0}")]
128 Io(#[from] io::Error),
129
130 #[error("ZIP文件错误: {0}")]
132 Zip(#[from] ZipError),
133
134 #[error("XML解析错误: {0}")]
136 Xml(#[from] XmlError),
137
138 #[error("JSON错误: {0}")]
140 Json(#[from] JsonError),
141
142 #[error("编码错误: {0}")]
144 Encoding(#[from] std::string::FromUtf8Error),
145
146 #[error("文件错误: {0}")]
148 File(#[from] FileError),
149
150 #[error("解析错误: {0}")]
152 Parse(#[from] ParseError),
153
154 #[error("Excel错误: {0}")]
156 Xlsx(#[from] XlsxError),
157
158 #[error("Word错误: {0}")]
160 Docx(#[from] DocxError),
161
162 #[error("PowerPoint错误: {0}")]
164 Pptx(#[from] PptxError),
165
166 #[error("格式错误: {0}")]
168 Format(String),
169
170 #[error("不支持的文件类型: {0}")]
172 UnsupportedFormat(String),
173
174 #[error("文档结构错误: {0}")]
176 Structure(String),
177
178 #[error("其他错误: {0}")]
180 Other(String),
181
182 #[error("{error}\n上下文: {context:?}")]
184 WithContext {
185 error: Box<OfficeError>,
186 context: ErrorContext,
187 },
188}
189
190#[derive(Error, Debug)]
192pub enum FileError {
193 #[error("文件不存在: {path}")] NotFound {
194 path: String,
195 },
196
197 #[error("文件权限不足: {path}")] PermissionDenied {
198 path: String,
199 },
200
201 #[error("文件已损坏: {path}")] Corrupted {
202 path: String,
203 },
204
205 #[error("文件格式不正确: {path}")] InvalidFormat {
206 path: String,
207 },
208}
209
210#[derive(Error, Debug)]
212pub enum ParseError {
213 #[error("缺少必需元素: {element}")] MissingElement {
214 element: String,
215 },
216
217 #[error("无效的属性值: {attribute}={value}")] InvalidAttribute {
218 attribute: String,
219 value: String,
220 },
221
222 #[error("不支持的版本: {version}")] UnsupportedVersion {
223 version: String,
224 },
225
226 #[error("无效的命名空间: {namespace}")] InvalidNamespace {
227 namespace: String,
228 },
229}
230
231#[derive(Error, Debug)]
233pub enum XlsxError {
234 #[error("工作表不存在: {name}")] WorksheetNotFound {
235 name: String,
236 },
237
238 #[error("单元格引用无效: {reference}")] InvalidCellReference {
239 reference: String,
240 },
241
242 #[error("公式语法错误: {formula}")] InvalidFormula {
243 formula: String,
244 },
245
246 #[error("样式ID无效: {style_id}")] InvalidStyleId {
247 style_id: String,
248 },
249
250 #[error("共享字符串索引超出范围: {index}")] SharedStringIndexOutOfRange {
251 index: usize,
252 },
253
254 #[error("工作表创建失败: {name}")] WorksheetCreationFailed {
255 name: String,
256 },
257}
258
259#[derive(Error, Debug)]
261pub enum DocxError {
262 #[error("样式不存在: {style_id}")] StyleNotFound {
263 style_id: String,
264 },
265
266 #[error("表格结构无效")]
267 InvalidTableStructure,
268
269 #[error("段落格式错误: {reason}")] InvalidParagraphFormat {
270 reason: String,
271 },
272
273 #[error("书签不存在: {bookmark}")] BookmarkNotFound {
274 bookmark: String,
275 },
276}
277
278#[derive(Error, Debug)]
280pub enum PptxError {
281 #[error("幻灯片不存在: {slide_id}")] SlideNotFound {
282 slide_id: String,
283 },
284
285 #[error("布局不存在: {layout_id}")] LayoutNotFound {
286 layout_id: String,
287 },
288
289 #[error("形状ID无效: {shape_id}")] InvalidShapeId {
290 shape_id: String,
291 },
292
293 #[error("动画配置错误: {reason}")] InvalidAnimation {
294 reason: String,
295 },
296}
297
298impl OfficeError {
299 pub fn file_not_found_with_context(path: String, context: ErrorContext) -> Self {
301 Self::File(FileError::NotFound { path }).with_context(context)
302 }
303
304 pub fn parse_error_with_context(element: String, context: ErrorContext) -> Self {
306 Self::Parse(ParseError::MissingElement { element }).with_context(context)
307 }
308
309 pub fn xlsx_error_with_context(name: String, context: ErrorContext) -> Self {
311 Self::Xlsx(XlsxError::WorksheetNotFound { name }).with_context(context)
312 }
313
314 pub fn record(&self) -> &Self {
316 error_monitor().record_error(self);
317 self
318 }
319
320 pub fn new_and_record(error: OfficeError) -> Self {
322 error_monitor().record_error(&error);
323 error
324 }
325 pub fn severity(&self) -> ErrorSeverity {
327 match self {
328 Self::Io(_) => ErrorSeverity::Fatal,
329 Self::Zip(_) => ErrorSeverity::Fatal,
330 Self::Xml(_) => ErrorSeverity::Error,
331 Self::Json(_) => ErrorSeverity::Error,
332 Self::Encoding(_) => ErrorSeverity::Error,
333 Self::File(FileError::NotFound { .. }) => ErrorSeverity::Fatal,
334 Self::File(FileError::PermissionDenied { .. }) => ErrorSeverity::Fatal,
335 Self::File(FileError::Corrupted { .. }) => ErrorSeverity::Fatal,
336 Self::File(FileError::InvalidFormat { .. }) => ErrorSeverity::Error,
337 Self::Parse(_) => ErrorSeverity::Error,
338 Self::Xlsx(_) => ErrorSeverity::Error,
339 Self::Docx(_) => ErrorSeverity::Error,
340 Self::Pptx(_) => ErrorSeverity::Error,
341 Self::Format(_) => ErrorSeverity::Error,
342 Self::UnsupportedFormat(_) => ErrorSeverity::Fatal,
343 Self::Structure(_) => ErrorSeverity::Warning,
344 Self::Other(_) => ErrorSeverity::Error,
345 Self::WithContext { error, .. } => error.severity(),
346 }
347 }
348
349 pub fn category(&self) -> ErrorCategory {
351 match self {
352 Self::Io(_) | Self::File(_) => ErrorCategory::FileSystem,
353 Self::Zip(_) => ErrorCategory::FileSystem,
354 Self::Xml(_) | Self::Json(_) | Self::Encoding(_) => ErrorCategory::Parsing,
355 Self::Parse(_) => ErrorCategory::Parsing,
356 Self::Xlsx(_) | Self::Docx(_) | Self::Pptx(_) => ErrorCategory::Validation,
357 Self::Format(_) | Self::Structure(_) => ErrorCategory::Validation,
358 Self::UnsupportedFormat(_) => ErrorCategory::Parsing,
359 Self::Other(_) => ErrorCategory::Internal,
360 Self::WithContext { error, .. } => error.category(),
361 }
362 }
363
364 pub fn error_code(&self) -> ErrorCode {
366 match self {
367 Self::Io(_) => ErrorCode::IoError,
368 Self::Zip(_) => ErrorCode::ZipError,
369 Self::Xml(_) => ErrorCode::XmlParseError,
370 Self::Json(_) => ErrorCode::JsonParseError,
371 Self::Encoding(_) => ErrorCode::EncodingError,
372 Self::File(FileError::NotFound { .. }) => ErrorCode::FileNotFound,
373 Self::File(FileError::PermissionDenied { .. }) => ErrorCode::FilePermissionDenied,
374 Self::File(FileError::Corrupted { .. }) => ErrorCode::FileCorrupted,
375 Self::File(FileError::InvalidFormat { .. }) => ErrorCode::FileInvalidFormat,
376 Self::Parse(ParseError::MissingElement { .. }) => ErrorCode::MissingElement,
377 Self::Parse(ParseError::InvalidAttribute { .. }) => ErrorCode::InvalidAttribute,
378 Self::Parse(ParseError::UnsupportedVersion { .. }) => ErrorCode::UnsupportedVersion,
379 Self::Parse(ParseError::InvalidNamespace { .. }) => ErrorCode::InvalidNamespace,
380 Self::Xlsx(XlsxError::WorksheetNotFound { .. }) => ErrorCode::XlsxWorksheetNotFound,
381 Self::Xlsx(XlsxError::InvalidCellReference { .. }) => {
382 ErrorCode::XlsxInvalidCellReference
383 }
384 Self::Xlsx(XlsxError::InvalidFormula { .. }) => ErrorCode::XlsxInvalidFormula,
385 Self::Xlsx(XlsxError::InvalidStyleId { .. }) => ErrorCode::XlsxInvalidStyleId,
386 Self::Xlsx(XlsxError::SharedStringIndexOutOfRange { .. }) => {
387 ErrorCode::XlsxSharedStringIndexOutOfRange
388 }
389 Self::Xlsx(XlsxError::WorksheetCreationFailed { .. }) => ErrorCode::XlsxError,
390 Self::Docx(DocxError::StyleNotFound { .. }) => ErrorCode::DocxStyleNotFound,
391 Self::Docx(DocxError::InvalidTableStructure) => ErrorCode::DocxInvalidTableStructure,
392 Self::Docx(DocxError::InvalidParagraphFormat { .. }) => {
393 ErrorCode::DocxInvalidParagraphFormat
394 }
395 Self::Docx(DocxError::BookmarkNotFound { .. }) => ErrorCode::DocxBookmarkNotFound,
396 Self::Pptx(PptxError::SlideNotFound { .. }) => ErrorCode::PptxSlideNotFound,
397 Self::Pptx(PptxError::LayoutNotFound { .. }) => ErrorCode::PptxLayoutNotFound,
398 Self::Pptx(PptxError::InvalidShapeId { .. }) => ErrorCode::PptxInvalidShapeId,
399 Self::Pptx(PptxError::InvalidAnimation { .. }) => ErrorCode::PptxInvalidAnimation,
400 Self::Format(_) => ErrorCode::FormatError,
401 Self::UnsupportedFormat(_) => ErrorCode::UnsupportedFormat,
402 Self::Structure(_) => ErrorCode::StructureError,
403 Self::Other(_) => ErrorCode::OtherError,
404 Self::WithContext { error, .. } => error.error_code(),
405 }
406 }
407
408 pub fn is_recoverable(&self) -> bool {
410 matches!(self.severity(), ErrorSeverity::Warning | ErrorSeverity::Error)
411 }
412
413 pub fn with_context(self, context: ErrorContext) -> Self {
415 let error_with_context = Self::WithContext {
416 error: Box::new(self),
417 context,
418 };
419 error_monitor().record_error(&error_with_context);
421 error_with_context
422 }
423
424 pub fn context(&self) -> Option<&ErrorContext> {
426 match self {
427 Self::WithContext { context, .. } => Some(context),
428 _ => None,
429 }
430 }
431
432 pub fn root_error(&self) -> &OfficeError {
434 match self {
435 Self::WithContext { error, .. } => error.root_error(),
436 _ => self,
437 }
438 }
439
440 pub fn recovery_suggestion(&self) -> Option<RecoverySuggestion> {
442 match self.root_error() {
443 Self::File(FileError::NotFound { path }) =>
444 Some(RecoverySuggestion {
445 message: format!("检查文件路径是否正确: {}", path),
446 action: RecoveryAction::UserInput("请提供正确的文件路径".to_string()),
447 }),
448 Self::File(FileError::PermissionDenied { path }) =>
449 Some(RecoverySuggestion {
450 message: format!("检查文件权限: {}", path),
451 action: RecoveryAction::UserInput("请确保有足够的文件访问权限".to_string()),
452 }),
453 Self::Xlsx(XlsxError::WorksheetNotFound { name }) =>
454 Some(RecoverySuggestion {
455 message: format!("工作表 '{}' 不存在,可以使用默认工作表", name),
456 action: RecoveryAction::UseDefault,
457 }),
458 Self::Xlsx(XlsxError::InvalidCellReference { reference }) =>
459 Some(RecoverySuggestion {
460 message: format!("单元格引用 '{}' 无效,跳过此单元格", reference),
461 action: RecoveryAction::SkipElement,
462 }),
463 Self::Docx(DocxError::StyleNotFound { style_id }) =>
464 Some(RecoverySuggestion {
465 message: format!("样式 '{}' 不存在,使用默认样式", style_id),
466 action: RecoveryAction::UseDefault,
467 }),
468 Self::Pptx(PptxError::SlideNotFound { slide_id }) =>
469 Some(RecoverySuggestion {
470 message: format!("幻灯片 '{}' 不存在,跳过此幻灯片", slide_id),
471 action: RecoveryAction::SkipElement,
472 }),
473 Self::Structure(_) =>
474 Some(RecoverySuggestion {
475 message: "文档结构异常,但可以继续处理".to_string(),
476 action: RecoveryAction::SkipElement,
477 }),
478 Self::Io(_) | Self::Zip(_) | Self::UnsupportedFormat(_) =>
479 Some(RecoverySuggestion {
480 message: "致命错误,无法恢复".to_string(),
481 action: RecoveryAction::None,
482 }),
483 _ =>
484 Some(RecoverySuggestion {
485 message: "可以重试操作".to_string(),
486 action: RecoveryAction::Retry,
487 }),
488 }
489 }
490
491 pub fn error_chain(&self) -> Vec<&dyn std::error::Error> {
493 let mut chain = vec![self as &dyn std::error::Error];
494 let mut source = self.source();
495 while let Some(err) = source {
496 chain.push(err);
497 source = err.source();
498 }
499 chain
500 }
501
502 pub fn root_cause(&self) -> &dyn std::error::Error {
504 self.error_chain().into_iter().last().unwrap()
505 }
506}
507
508#[derive(Debug, Clone)]
510pub struct ErrorStats {
511 pub error_code: ErrorCode,
512 pub count: u64,
513 pub first_occurrence: u64, pub last_occurrence: u64, pub severity: ErrorSeverity,
516 pub category: ErrorCategory,
517}
518
519#[derive(Debug)]
521pub struct ErrorMonitor {
522 stats: Mutex<HashMap<ErrorCode, ErrorStats>>,
523 total_errors: AtomicU64,
524}
525
526impl ErrorMonitor {
527 pub fn new() -> Self {
528 Self {
529 stats: Mutex::new(HashMap::new()),
530 total_errors: AtomicU64::new(0),
531 }
532 }
533
534 pub fn record_error(&self, error: &OfficeError) {
536 let error_code = error.error_code();
537 let severity = error.severity();
538 let category = error.category();
539 let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
540
541 self.total_errors.fetch_add(1, Ordering::Relaxed);
542
543 let mut stats = self.stats.lock().unwrap();
544 let entry = stats.entry(error_code).or_insert(ErrorStats {
545 error_code,
546 count: 0,
547 first_occurrence: now,
548 last_occurrence: now,
549 severity,
550 category,
551 });
552
553 entry.count += 1;
554 entry.last_occurrence = now;
555 }
556
557 pub fn get_stats(&self) -> Vec<ErrorStats> {
559 let stats = self.stats.lock().unwrap();
560 stats.values().cloned().collect()
561 }
562
563 pub fn total_errors(&self) -> u64 {
565 self.total_errors.load(Ordering::Relaxed)
566 }
567
568 pub fn most_common_errors(&self, limit: usize) -> Vec<ErrorStats> {
570 let mut stats = self.get_stats();
571 stats.sort_by(|a, b| b.count.cmp(&a.count));
572 stats.into_iter().take(limit).collect()
573 }
574
575 pub fn clear_stats(&self) {
577 let mut stats = self.stats.lock().unwrap();
578 stats.clear();
579 self.total_errors.store(0, Ordering::Relaxed);
580 }
581}
582
583static ERROR_MONITOR: OnceLock<ErrorMonitor> = OnceLock::new();
585
586pub fn error_monitor() -> &'static ErrorMonitor {
588 ERROR_MONITOR.get_or_init(|| ErrorMonitor::new())
589}
590
591pub type Result<T> = std::result::Result<T, OfficeError>;