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}