use quick_xml::Error as XmlError;
use serde_json::Error as JsonError;
use std::collections::HashMap;
use std::error::Error;
use std::io;
use std::sync::atomic::{ AtomicU64, Ordering };
use std::sync::{ Mutex, OnceLock };
use std::time::{ SystemTime, UNIX_EPOCH };
use thiserror::Error;
use zip::result::ZipError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorSeverity {
Warning,
Error,
Fatal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorCategory {
FileSystem,
Network,
Parsing,
Validation,
Internal,
}
#[derive(Debug, Clone, Default)]
pub struct ErrorContext {
pub file_path: Option<String>,
pub line_number: Option<u32>,
pub element_path: Option<String>,
pub operation: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ErrorCode {
FileNotFound = 1001,
FilePermissionDenied = 1002,
FileCorrupted = 1003,
FileInvalidFormat = 1004,
IoError = 1100,
ZipError = 1101,
XmlParseError = 2001,
JsonParseError = 2002,
EncodingError = 2003,
MissingElement = 2004,
InvalidAttribute = 2005,
UnsupportedVersion = 2006,
InvalidNamespace = 2007,
XlsxWorksheetNotFound = 3001,
XlsxInvalidCellReference = 3002,
XlsxInvalidFormula = 3003,
XlsxInvalidStyleId = 3004,
XlsxSharedStringIndexOutOfRange = 3005,
DocxStyleNotFound = 4001,
DocxInvalidTableStructure = 4002,
DocxInvalidParagraphFormat = 4003,
DocxBookmarkNotFound = 4004,
PptxSlideNotFound = 5001,
PptxLayoutNotFound = 5002,
PptxInvalidShapeId = 5003,
PptxInvalidAnimation = 5004,
FormatError = 9001,
UnsupportedFormat = 9002,
StructureError = 9003,
OtherError = 9999,
FileError = 1000,
ParseError = 2000,
XlsxError = 3000,
DocxError = 4000,
PptxError = 5000,
}
#[derive(Debug, Clone)]
pub struct RecoverySuggestion {
pub message: String,
pub action: RecoveryAction,
}
#[derive(Debug, Clone)]
pub enum RecoveryAction {
Retry,
UseDefault,
SkipElement,
UserInput(String),
None,
}
#[derive(Error, Debug)]
pub enum OfficeError {
#[error("IO错误: {0}")]
Io(#[from] io::Error),
#[error("ZIP文件错误: {0}")]
Zip(#[from] ZipError),
#[error("XML解析错误: {0}")]
Xml(#[from] XmlError),
#[error("JSON错误: {0}")]
Json(#[from] JsonError),
#[error("编码错误: {0}")]
Encoding(#[from] std::string::FromUtf8Error),
#[error("文件错误: {0}")]
File(#[from] FileError),
#[error("解析错误: {0}")]
Parse(#[from] ParseError),
#[error("Excel错误: {0}")]
Xlsx(#[from] XlsxError),
#[error("Word错误: {0}")]
Docx(#[from] DocxError),
#[error("PowerPoint错误: {0}")]
Pptx(#[from] PptxError),
#[error("格式错误: {0}")]
Format(String),
#[error("不支持的文件类型: {0}")]
UnsupportedFormat(String),
#[error("文档结构错误: {0}")]
Structure(String),
#[error("其他错误: {0}")]
Other(String),
#[error("{error}\n上下文: {context:?}")]
WithContext {
error: Box<OfficeError>,
context: ErrorContext,
},
}
#[derive(Error, Debug)]
pub enum FileError {
#[error("文件不存在: {path}")] NotFound {
path: String,
},
#[error("文件权限不足: {path}")] PermissionDenied {
path: String,
},
#[error("文件已损坏: {path}")] Corrupted {
path: String,
},
#[error("文件格式不正确: {path}")] InvalidFormat {
path: String,
},
}
#[derive(Error, Debug)]
pub enum ParseError {
#[error("缺少必需元素: {element}")] MissingElement {
element: String,
},
#[error("无效的属性值: {attribute}={value}")] InvalidAttribute {
attribute: String,
value: String,
},
#[error("不支持的版本: {version}")] UnsupportedVersion {
version: String,
},
#[error("无效的命名空间: {namespace}")] InvalidNamespace {
namespace: String,
},
}
#[derive(Error, Debug)]
pub enum XlsxError {
#[error("工作表不存在: {name}")] WorksheetNotFound {
name: String,
},
#[error("单元格引用无效: {reference}")] InvalidCellReference {
reference: String,
},
#[error("公式语法错误: {formula}")] InvalidFormula {
formula: String,
},
#[error("样式ID无效: {style_id}")] InvalidStyleId {
style_id: String,
},
#[error("共享字符串索引超出范围: {index}")] SharedStringIndexOutOfRange {
index: usize,
},
#[error("工作表创建失败: {name}")] WorksheetCreationFailed {
name: String,
},
}
#[derive(Error, Debug)]
pub enum DocxError {
#[error("样式不存在: {style_id}")] StyleNotFound {
style_id: String,
},
#[error("表格结构无效")]
InvalidTableStructure,
#[error("段落格式错误: {reason}")] InvalidParagraphFormat {
reason: String,
},
#[error("书签不存在: {bookmark}")] BookmarkNotFound {
bookmark: String,
},
}
#[derive(Error, Debug)]
pub enum PptxError {
#[error("幻灯片不存在: {slide_id}")] SlideNotFound {
slide_id: String,
},
#[error("布局不存在: {layout_id}")] LayoutNotFound {
layout_id: String,
},
#[error("形状ID无效: {shape_id}")] InvalidShapeId {
shape_id: String,
},
#[error("动画配置错误: {reason}")] InvalidAnimation {
reason: String,
},
}
impl OfficeError {
pub fn file_not_found_with_context(path: String, context: ErrorContext) -> Self {
Self::File(FileError::NotFound { path }).with_context(context)
}
pub fn parse_error_with_context(element: String, context: ErrorContext) -> Self {
Self::Parse(ParseError::MissingElement { element }).with_context(context)
}
pub fn xlsx_error_with_context(name: String, context: ErrorContext) -> Self {
Self::Xlsx(XlsxError::WorksheetNotFound { name }).with_context(context)
}
pub fn record(&self) -> &Self {
error_monitor().record_error(self);
self
}
pub fn new_and_record(error: OfficeError) -> Self {
error_monitor().record_error(&error);
error
}
pub fn severity(&self) -> ErrorSeverity {
match self {
Self::Io(_) => ErrorSeverity::Fatal,
Self::Zip(_) => ErrorSeverity::Fatal,
Self::Xml(_) => ErrorSeverity::Error,
Self::Json(_) => ErrorSeverity::Error,
Self::Encoding(_) => ErrorSeverity::Error,
Self::File(FileError::NotFound { .. }) => ErrorSeverity::Fatal,
Self::File(FileError::PermissionDenied { .. }) => ErrorSeverity::Fatal,
Self::File(FileError::Corrupted { .. }) => ErrorSeverity::Fatal,
Self::File(FileError::InvalidFormat { .. }) => ErrorSeverity::Error,
Self::Parse(_) => ErrorSeverity::Error,
Self::Xlsx(_) => ErrorSeverity::Error,
Self::Docx(_) => ErrorSeverity::Error,
Self::Pptx(_) => ErrorSeverity::Error,
Self::Format(_) => ErrorSeverity::Error,
Self::UnsupportedFormat(_) => ErrorSeverity::Fatal,
Self::Structure(_) => ErrorSeverity::Warning,
Self::Other(_) => ErrorSeverity::Error,
Self::WithContext { error, .. } => error.severity(),
}
}
pub fn category(&self) -> ErrorCategory {
match self {
Self::Io(_) | Self::File(_) => ErrorCategory::FileSystem,
Self::Zip(_) => ErrorCategory::FileSystem,
Self::Xml(_) | Self::Json(_) | Self::Encoding(_) => ErrorCategory::Parsing,
Self::Parse(_) => ErrorCategory::Parsing,
Self::Xlsx(_) | Self::Docx(_) | Self::Pptx(_) => ErrorCategory::Validation,
Self::Format(_) | Self::Structure(_) => ErrorCategory::Validation,
Self::UnsupportedFormat(_) => ErrorCategory::Parsing,
Self::Other(_) => ErrorCategory::Internal,
Self::WithContext { error, .. } => error.category(),
}
}
pub fn error_code(&self) -> ErrorCode {
match self {
Self::Io(_) => ErrorCode::IoError,
Self::Zip(_) => ErrorCode::ZipError,
Self::Xml(_) => ErrorCode::XmlParseError,
Self::Json(_) => ErrorCode::JsonParseError,
Self::Encoding(_) => ErrorCode::EncodingError,
Self::File(FileError::NotFound { .. }) => ErrorCode::FileNotFound,
Self::File(FileError::PermissionDenied { .. }) => ErrorCode::FilePermissionDenied,
Self::File(FileError::Corrupted { .. }) => ErrorCode::FileCorrupted,
Self::File(FileError::InvalidFormat { .. }) => ErrorCode::FileInvalidFormat,
Self::Parse(ParseError::MissingElement { .. }) => ErrorCode::MissingElement,
Self::Parse(ParseError::InvalidAttribute { .. }) => ErrorCode::InvalidAttribute,
Self::Parse(ParseError::UnsupportedVersion { .. }) => ErrorCode::UnsupportedVersion,
Self::Parse(ParseError::InvalidNamespace { .. }) => ErrorCode::InvalidNamespace,
Self::Xlsx(XlsxError::WorksheetNotFound { .. }) => ErrorCode::XlsxWorksheetNotFound,
Self::Xlsx(XlsxError::InvalidCellReference { .. }) => {
ErrorCode::XlsxInvalidCellReference
}
Self::Xlsx(XlsxError::InvalidFormula { .. }) => ErrorCode::XlsxInvalidFormula,
Self::Xlsx(XlsxError::InvalidStyleId { .. }) => ErrorCode::XlsxInvalidStyleId,
Self::Xlsx(XlsxError::SharedStringIndexOutOfRange { .. }) => {
ErrorCode::XlsxSharedStringIndexOutOfRange
}
Self::Xlsx(XlsxError::WorksheetCreationFailed { .. }) => ErrorCode::XlsxError,
Self::Docx(DocxError::StyleNotFound { .. }) => ErrorCode::DocxStyleNotFound,
Self::Docx(DocxError::InvalidTableStructure) => ErrorCode::DocxInvalidTableStructure,
Self::Docx(DocxError::InvalidParagraphFormat { .. }) => {
ErrorCode::DocxInvalidParagraphFormat
}
Self::Docx(DocxError::BookmarkNotFound { .. }) => ErrorCode::DocxBookmarkNotFound,
Self::Pptx(PptxError::SlideNotFound { .. }) => ErrorCode::PptxSlideNotFound,
Self::Pptx(PptxError::LayoutNotFound { .. }) => ErrorCode::PptxLayoutNotFound,
Self::Pptx(PptxError::InvalidShapeId { .. }) => ErrorCode::PptxInvalidShapeId,
Self::Pptx(PptxError::InvalidAnimation { .. }) => ErrorCode::PptxInvalidAnimation,
Self::Format(_) => ErrorCode::FormatError,
Self::UnsupportedFormat(_) => ErrorCode::UnsupportedFormat,
Self::Structure(_) => ErrorCode::StructureError,
Self::Other(_) => ErrorCode::OtherError,
Self::WithContext { error, .. } => error.error_code(),
}
}
pub fn is_recoverable(&self) -> bool {
matches!(self.severity(), ErrorSeverity::Warning | ErrorSeverity::Error)
}
pub fn with_context(self, context: ErrorContext) -> Self {
let error_with_context = Self::WithContext {
error: Box::new(self),
context,
};
error_monitor().record_error(&error_with_context);
error_with_context
}
pub fn context(&self) -> Option<&ErrorContext> {
match self {
Self::WithContext { context, .. } => Some(context),
_ => None,
}
}
pub fn root_error(&self) -> &OfficeError {
match self {
Self::WithContext { error, .. } => error.root_error(),
_ => self,
}
}
pub fn recovery_suggestion(&self) -> Option<RecoverySuggestion> {
match self.root_error() {
Self::File(FileError::NotFound { path }) =>
Some(RecoverySuggestion {
message: format!("检查文件路径是否正确: {}", path),
action: RecoveryAction::UserInput("请提供正确的文件路径".to_string()),
}),
Self::File(FileError::PermissionDenied { path }) =>
Some(RecoverySuggestion {
message: format!("检查文件权限: {}", path),
action: RecoveryAction::UserInput("请确保有足够的文件访问权限".to_string()),
}),
Self::Xlsx(XlsxError::WorksheetNotFound { name }) =>
Some(RecoverySuggestion {
message: format!("工作表 '{}' 不存在,可以使用默认工作表", name),
action: RecoveryAction::UseDefault,
}),
Self::Xlsx(XlsxError::InvalidCellReference { reference }) =>
Some(RecoverySuggestion {
message: format!("单元格引用 '{}' 无效,跳过此单元格", reference),
action: RecoveryAction::SkipElement,
}),
Self::Docx(DocxError::StyleNotFound { style_id }) =>
Some(RecoverySuggestion {
message: format!("样式 '{}' 不存在,使用默认样式", style_id),
action: RecoveryAction::UseDefault,
}),
Self::Pptx(PptxError::SlideNotFound { slide_id }) =>
Some(RecoverySuggestion {
message: format!("幻灯片 '{}' 不存在,跳过此幻灯片", slide_id),
action: RecoveryAction::SkipElement,
}),
Self::Structure(_) =>
Some(RecoverySuggestion {
message: "文档结构异常,但可以继续处理".to_string(),
action: RecoveryAction::SkipElement,
}),
Self::Io(_) | Self::Zip(_) | Self::UnsupportedFormat(_) =>
Some(RecoverySuggestion {
message: "致命错误,无法恢复".to_string(),
action: RecoveryAction::None,
}),
_ =>
Some(RecoverySuggestion {
message: "可以重试操作".to_string(),
action: RecoveryAction::Retry,
}),
}
}
pub fn error_chain(&self) -> Vec<&dyn std::error::Error> {
let mut chain = vec![self as &dyn std::error::Error];
let mut source = self.source();
while let Some(err) = source {
chain.push(err);
source = err.source();
}
chain
}
pub fn root_cause(&self) -> &dyn std::error::Error {
self.error_chain().into_iter().last().unwrap()
}
}
#[derive(Debug, Clone)]
pub struct ErrorStats {
pub error_code: ErrorCode,
pub count: u64,
pub first_occurrence: u64, pub last_occurrence: u64, pub severity: ErrorSeverity,
pub category: ErrorCategory,
}
#[derive(Debug)]
pub struct ErrorMonitor {
stats: Mutex<HashMap<ErrorCode, ErrorStats>>,
total_errors: AtomicU64,
}
impl ErrorMonitor {
pub fn new() -> Self {
Self {
stats: Mutex::new(HashMap::new()),
total_errors: AtomicU64::new(0),
}
}
pub fn record_error(&self, error: &OfficeError) {
let error_code = error.error_code();
let severity = error.severity();
let category = error.category();
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
self.total_errors.fetch_add(1, Ordering::Relaxed);
let mut stats = self.stats.lock().unwrap();
let entry = stats.entry(error_code).or_insert(ErrorStats {
error_code,
count: 0,
first_occurrence: now,
last_occurrence: now,
severity,
category,
});
entry.count += 1;
entry.last_occurrence = now;
}
pub fn get_stats(&self) -> Vec<ErrorStats> {
let stats = self.stats.lock().unwrap();
stats.values().cloned().collect()
}
pub fn total_errors(&self) -> u64 {
self.total_errors.load(Ordering::Relaxed)
}
pub fn most_common_errors(&self, limit: usize) -> Vec<ErrorStats> {
let mut stats = self.get_stats();
stats.sort_by(|a, b| b.count.cmp(&a.count));
stats.into_iter().take(limit).collect()
}
pub fn clear_stats(&self) {
let mut stats = self.stats.lock().unwrap();
stats.clear();
self.total_errors.store(0, Ordering::Relaxed);
}
}
static ERROR_MONITOR: OnceLock<ErrorMonitor> = OnceLock::new();
pub fn error_monitor() -> &'static ErrorMonitor {
ERROR_MONITOR.get_or_init(|| ErrorMonitor::new())
}
pub type Result<T> = std::result::Result<T, OfficeError>;