Skip to main content

pptx_rs/
error.rs

1//! 错误类型与 `Result` 别名。
2//!
3//! 本模块是整个 `pptx-rs` crate 的错误处理中枢。它做三件事:
4//!
5//! 1. **统一错误类型** [`enum@Error`]:使用 `thiserror` 派生,将底层 `std::io::Error` /
6//!    `zip::result::ZipError` / 自定义字符串错误统一成一个枚举,方便上层 `?` 传播。
7//! 2. **统一 Result 别名** [`Result<T>`]:所有公共 API(除特别声明)均返回该别名,
8//!    调用方不必书写冗长的 `Result<T, pptx_rs::Error>`。
9//! 3. **便捷构造器**:在 `Error` 上提供 `opc(...)` / `oxml(...)` / `not_implemented(...)`
10//!    等关联函数,使错误抛出更可读,并隐含语义归类。
11//!
12//! # 设计原则
13//!
14//! - **零 `panic!`**:库路径上禁止 `unwrap` / `expect` / `panic!`。所有失败一律
15//!   转化为 [`enum@Error`] 的一个变体,由调用方决定如何处理。
16//! - **错误消息规范**:消息小写开头、句末无标点(与 Rust 标准库惯例一致);
17//!   需要上下文时使用 `format!` 拼接具体元素名 / 路径,例如
18//!   `"relationships parse: missing Id"`。
19//! - **可扩展**:新增错误类别时优先扩展 `enum Error` 变体,而非全部塞进
20//!   [`Error::Other`]。后者仅用于临时过渡。
21//!
22//! # 与 python-pptx 的对应
23//!
24//! `python-pptx` 抛出 `python_pptx.PptxException` 及若干子异常(`PackageNotFoundError` /
25//! `XPathOverflowError` 等)。本库以单一枚举 + 字符串消息统一表达,对调用方而言
26//! 仅需 match 顶层 `Error` 即可。
27//!
28//! # 示例
29//!
30//! ```no_run
31//! use pptx_rs::{Error, Result};
32//!
33//! fn read_slide() -> Result<()> {
34//!     let p = std::fs::File::open("missing.pptx")?;  // io::Error 自动转 Error::Io
35//!     Ok(())
36//! }
37//!
38//! fn parse_attr() -> Result<()> {
39//!     Err(Error::oxml("missing required <p:ph> element"))
40//! }
41//! ```
42
43use std::io;
44use thiserror::Error;
45
46/// 库统一 `Result` 别名。
47///
48/// 简化签名书写,所有公共 API(除特别声明)均使用该别名,等价于
49/// `std::result::Result<T, pptx_rs::Error>`。
50///
51/// # 示例
52///
53/// ```no_run
54/// use pptx_rs::Result;
55///
56/// fn read_something() -> Result<String> { Ok(String::new()) }
57/// ```
58pub type Result<T> = std::result::Result<T, Error>;
59
60/// 库错误。所有外部接口(除特别声明外)均返回 [`Result<T>`]。
61///
62/// 变体按"来源"分类,调用方可以基于 `match` 决定重试 / 跳过 / 报告策略。
63/// 错误消息遵循 **小写开头、句末无标点** 的 Rust 标准库风格。
64#[derive(Debug, Error)]
65pub enum Error {
66    /// I/O 错误:文件不存在、读取失败、写入失败、权限拒绝等。
67    ///
68    /// 由 `#[from] io::Error` 自动派生,调用方可用 `?` 直接把任意
69    /// `std::io::Error` 提升为本变体。
70    #[error("I/O error: {0}")]
71    Io(#[from] io::Error),
72
73    /// zip 压缩包错误:CRC 校验失败、条目不存在、解压错误等。
74    ///
75    /// 由 `#[from] zip::result::ZipError` 派生,封装 `zip` crate 的全部错误。
76    #[error("zip error: {0}")]
77    Zip(#[from] zip::result::ZipError),
78
79    /// XML 解析或序列化错误。
80    ///
81    /// 字符串内容应包含 **出错元素名 + 上下文路径**,例如
82    /// `"slide layout1.xml parse: unexpected end of <p:sld> at line 42"`。
83    #[error("xml error: {0}")]
84    Xml(String),
85
86    /// OPC 包结构错误。
87    ///
88    /// 典型场景:
89    /// - 缺少必要的 part(如 `word/document.xml` 缺失);
90    /// - 关系链断裂(`r:id` 指向不存在的 target);
91    /// - `[Content_Types].xml` 中缺失 Override / Default。
92    #[error("OPC error: {0}")]
93    Opc(String),
94
95    /// OOXML 模型错误。
96    ///
97    /// 典型场景:
98    /// - 缺失必要字段(如 `slideLayout` 必须有 `cSld`);
99    /// - 命名空间不匹配;
100    /// - 序列化时违反 OOXML 元素顺序约束(CT_* schema 严格顺序)。
101    #[error("OOXML error: {0}")]
102    Oxml(String),
103
104    /// 元素未找到:按 Id / Name / Type 查找时未命中。
105    #[error("not found: {0}")]
106    NotFound(String),
107
108    /// 索引越界:访问 `Slides` / `Shapes` 等集合时 idx >= len。
109    #[error("index out of range: {0}")]
110    IndexOutOfRange(usize),
111
112    /// 不支持的功能(路线图中)。
113    ///
114    /// 携带 `&'static str` 描述功能名,便于编译期收集未实现项清单。
115    #[error("not implemented: {0}")]
116    NotImplemented(&'static str),
117
118    /// 加密/解密错误。
119    ///
120    /// 典型场景:
121    /// - 密码不匹配;
122    /// - 加密算法不支持;
123    /// - AES 密钥/IV 长度不正确;
124    /// - 加密文件格式损坏。
125    #[error("encryption error: {0}")]
126    Encryption(String),
127
128    /// .ppt(PowerPoint 97-2003 二进制格式)处理错误。
129    ///
130    /// 典型场景:
131    /// - OLE2/CFB 容器结构损坏;
132    /// - PPT record 树解析失败(record header / recLen 异常);
133    /// - 找不到必要 stream(PowerPoint Document / Current User);
134    /// - PersistDirectoryAtom 解析失败;
135    /// - 水印注入失败(找不到 MainMaster / PPDrawing / SpgrContainer);
136    /// - persist 对象重排失败。
137    ///
138    /// 与 [`Error::Encryption`] 的区别:本变体专指 .ppt 二进制格式的
139    /// 结构性错误;而 `Encryption` 侧重密钥/算法层面的失败。
140    #[error("ppt97 error: {0}")]
141    Ppt97(String),
142
143    /// 其它错误。
144    ///
145    /// **仅** 用于临时过渡;正式错误请扩展 `enum Error` 的具体变体。
146    #[error("{0}")]
147    Other(String),
148}
149
150impl Error {
151    /// 便捷构造:OPC 错误。
152    ///
153    /// 接受任何 `Into<String>` 的消息,避免调用方手动写 `Error::Opc(s.into())`。
154    pub fn opc<S: Into<String>>(msg: S) -> Self {
155        Error::Opc(msg.into())
156    }
157
158    /// 便捷构造:OOXML 错误。
159    pub fn oxml<S: Into<String>>(msg: S) -> Self {
160        Error::Oxml(msg.into())
161    }
162
163    /// 便捷构造:未实现。
164    ///
165    /// 配合 `unimplemented!` 风格的早期失败语义,用于在路线图功能
166    /// 调用点快速失败。
167    pub fn not_implemented(feature: &'static str) -> Self {
168        Error::NotImplemented(feature)
169    }
170
171    /// 便捷构造:加密/解密错误。
172    pub fn encryption<S: Into<String>>(msg: S) -> Self {
173        Error::Encryption(msg.into())
174    }
175
176    /// 便捷构造:.ppt 97-2003 二进制格式处理错误。
177    ///
178    /// 用于 OLE2 容器、PPT record 树、persist 对象等结构性失败。
179    pub fn ppt97<S: Into<String>>(msg: S) -> Self {
180        Error::Ppt97(msg.into())
181    }
182}
183
184/// 从 [`ooxml_core::Error`] 到本 crate `Error` 的逐变体映射。
185///
186/// ooxml-core 是格式无关的公共基座,不含 PPT 97-2003 二进制格式专有的
187/// `Ppt97` 变体。pptx-rs 通过本 impl 把 ooxml-core 的错误提升为本地错误,
188/// 让 `?` 能在 `use pptx_rs::Result` 的代码中直接传播 ooxml-core 的错误。
189impl From<ooxml_core::Error> for Error {
190    fn from(e: ooxml_core::Error) -> Self {
191        match e {
192            ooxml_core::Error::Io(v) => Error::Io(v),
193            ooxml_core::Error::Zip(v) => Error::Zip(v),
194            ooxml_core::Error::Xml(s) => Error::Xml(s),
195            ooxml_core::Error::Opc(s) => Error::Opc(s),
196            ooxml_core::Error::Oxml(s) => Error::Oxml(s),
197            ooxml_core::Error::NotFound(s) => Error::NotFound(s),
198            ooxml_core::Error::IndexOutOfRange(i) => Error::IndexOutOfRange(i),
199            ooxml_core::Error::NotImplemented(f) => Error::NotImplemented(f),
200            ooxml_core::Error::Encryption(s) => Error::Encryption(s),
201            ooxml_core::Error::Other(s) => Error::Other(s),
202        }
203    }
204}