use quick_xml::Error as XmlError;
use serde_json::Error as JsonError;
use std::error::Error;
use std::io;
use thiserror::Error;
use zip::result::ZipError;
pub use category::ErrorCategory;
pub use category::ErrorSeverity;
pub use format::file::FileError;
pub use format::parse::ParseError;
pub use format::xlsx::XlsxError;
pub use format::docx::DocxError;
pub use format::pptx::PptxError;
use crate::code::RecoverySuggestion;
use crate::error::code::*; use crate::error::monitor::{ ErrorMonitor, ErrorStats };
use crate::error::code::RecoveryAction;
use crate::error::context::ErrorContext;
use crate::error::monitor::error_monitor;
pub mod mod_backup;
pub mod category;
pub mod format;
pub mod code;
pub mod context;
pub mod monitor;
#[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,
},
}
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::System(SystemErrorCode::IoError),
Self::Zip(_) => ErrorCode::System(SystemErrorCode::ZipError),
Self::File(FileError::NotFound { .. }) => ErrorCode::System(SystemErrorCode::NotFound),
Self::File(FileError::PermissionDenied { .. }) =>
ErrorCode::System(SystemErrorCode::PermissionDenied),
Self::File(FileError::Corrupted { .. }) =>
ErrorCode::System(SystemErrorCode::Corrupted),
Self::File(FileError::InvalidFormat { .. }) =>
ErrorCode::Format(FormatErrorCode::InvalidFormat),
Self::Xml(_) => ErrorCode::Parse(ParseErrorCode::Xml),
Self::Json(_) => ErrorCode::Parse(ParseErrorCode::Json),
Self::Encoding(_) => ErrorCode::Parse(ParseErrorCode::Encoding),
Self::Parse(parse_err) =>
match parse_err {
ParseError::MissingElement { .. } =>
ErrorCode::Parse(ParseErrorCode::MissingElement),
ParseError::InvalidAttribute { .. } =>
ErrorCode::Parse(ParseErrorCode::InvalidAttribute),
ParseError::UnsupportedVersion { .. } =>
ErrorCode::Parse(ParseErrorCode::UnsupportedVersion),
ParseError::InvalidNamespace { .. } =>
ErrorCode::Parse(ParseErrorCode::InvalidNamespace),
}
Self::Xlsx(xlsx_err) =>
match xlsx_err {
XlsxError::WorksheetNotFound { .. } =>
ErrorCode::Document(DocumentErrorCode::XlsxWorksheetNotFound),
XlsxError::InvalidCellReference { .. } =>
ErrorCode::Document(DocumentErrorCode::XlsxInvalidCellReference),
XlsxError::InvalidFormula { .. } =>
ErrorCode::Document(DocumentErrorCode::XlsxInvalidFormula),
XlsxError::InvalidStyleId { .. } =>
ErrorCode::Document(DocumentErrorCode::XlsxInvalidStyle),
XlsxError::SharedStringIndexOutOfRange { .. } =>
ErrorCode::Format(FormatErrorCode::OutOfRange),
XlsxError::WorksheetCreationFailed { .. } =>
ErrorCode::Document(DocumentErrorCode::XlsxWorksheetNotFound),
}
Self::Docx(docx_err) =>
match docx_err {
DocxError::StyleNotFound { .. } =>
ErrorCode::Document(DocumentErrorCode::DocxStyleNotFound),
DocxError::InvalidTableStructure =>
ErrorCode::Document(DocumentErrorCode::DocxInvalidTableStructure),
DocxError::InvalidParagraphFormat { .. } =>
ErrorCode::Document(DocumentErrorCode::DocxInvalidParagraph),
DocxError::BookmarkNotFound { .. } =>
ErrorCode::Document(DocumentErrorCode::DocxBookmarkNotFound),
}
Self::Pptx(_) => ErrorCode::Document(DocumentErrorCode::PptxInvalidPresentation),
Self::Format(_) => ErrorCode::Format(FormatErrorCode::InvalidFormat),
Self::UnsupportedFormat(_) => ErrorCode::Format(FormatErrorCode::UnsupportedFormat),
Self::Structure(_) => ErrorCode::Common(CommonErrorCode::InvalidOperation),
Self::Other(_) => ErrorCode::Common(CommonErrorCode::UnknownError),
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(3),
}),
}
}
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()
}
pub fn try_recover(&self, strategy: RecoveryStrategy) -> Result<()> {
match strategy.operation {
RecoveryAction::Retry(times) => {
for _ in 0..times {
if self.is_recoverable() {
return Ok(());
}
}
Err(Self::Other("重试失败".to_string()))
}
RecoveryAction::Fallback(fallback) => {
println!("执行降级方案: {}", fallback);
todo!("Implement fallback logic");
Ok(())
}
RecoveryAction::Ignore => Ok(()),
_ => { Ok(()) }
}
}
}
pub type Result<T> = std::result::Result<T, OfficeError>;