1use thiserror::Error;
2
3pub type Result<T> = std::result::Result<T, OxdocError>;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum OxdocErrorCode {
7 Io,
8 CorruptedZip,
9 MissingPart,
10 UnsupportedEncryptedPart,
11 PartTooLarge,
12 SuspiciousZipEntry,
13 SuspiciousRelationshipTarget,
14 MissingCoreRelations,
15 MalformedXmlNode,
16 InvalidArgument,
17}
18
19impl OxdocErrorCode {
20 pub fn as_str(self) -> &'static str {
21 match self {
22 OxdocErrorCode::Io => "E001",
23 OxdocErrorCode::CorruptedZip => "E002",
24 OxdocErrorCode::MissingPart => "E003",
25 OxdocErrorCode::UnsupportedEncryptedPart => "E004",
26 OxdocErrorCode::PartTooLarge => "E005",
27 OxdocErrorCode::SuspiciousZipEntry => "E006",
28 OxdocErrorCode::SuspiciousRelationshipTarget => "E007",
29 OxdocErrorCode::MissingCoreRelations => "E008",
30 OxdocErrorCode::MalformedXmlNode => "E009",
31 OxdocErrorCode::InvalidArgument => "E010",
32 }
33 }
34}
35
36#[derive(Debug, Error)]
37pub enum OxdocError {
38 #[error("I/O error: {0}")]
39 Io(#[from] std::io::Error),
40
41 #[error("corrupted or unsupported ZIP container: {0}")]
42 CorruptedZip(#[from] zip::result::ZipError),
43
44 #[error("missing required OOXML part: {0}")]
45 MissingPart(String),
46
47 #[error("unsupported encrypted OOXML part: {0}")]
48 UnsupportedEncryptedPart(String),
49
50 #[error("OOXML part is too large: {path} is {size} bytes; limit is {limit} bytes")]
51 PartTooLarge { path: String, size: u64, limit: u64 },
52
53 #[error("suspicious OOXML ZIP entry: {path}: {reason}")]
54 SuspiciousZipEntry { path: String, reason: String },
55
56 #[error("suspicious OOXML relationship target in {path}: {target}: {reason}")]
57 SuspiciousRelationshipTarget {
58 path: String,
59 target: String,
60 reason: String,
61 },
62
63 #[error("missing office document relationship")]
64 MissingCoreRelations,
65
66 #[error("malformed XML in {path}: {source}")]
67 MalformedXmlNode {
68 path: String,
69 #[source]
70 source: quick_xml::Error,
71 },
72
73 #[error("invalid argument: {0}")]
74 InvalidArgument(String),
75}
76
77impl OxdocError {
78 pub fn code(&self) -> OxdocErrorCode {
79 match self {
80 OxdocError::Io(_) => OxdocErrorCode::Io,
81 OxdocError::CorruptedZip(_) => OxdocErrorCode::CorruptedZip,
82 OxdocError::MissingPart(_) => OxdocErrorCode::MissingPart,
83 OxdocError::UnsupportedEncryptedPart(_) => OxdocErrorCode::UnsupportedEncryptedPart,
84 OxdocError::PartTooLarge { .. } => OxdocErrorCode::PartTooLarge,
85 OxdocError::SuspiciousZipEntry { .. } => OxdocErrorCode::SuspiciousZipEntry,
86 OxdocError::SuspiciousRelationshipTarget { .. } => {
87 OxdocErrorCode::SuspiciousRelationshipTarget
88 }
89 OxdocError::MissingCoreRelations => OxdocErrorCode::MissingCoreRelations,
90 OxdocError::MalformedXmlNode { .. } => OxdocErrorCode::MalformedXmlNode,
91 OxdocError::InvalidArgument(_) => OxdocErrorCode::InvalidArgument,
92 }
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::{OxdocError, OxdocErrorCode};
99
100 #[test]
101 fn maps_errors_to_stable_codes() {
102 let cases = [
103 (
104 OxdocError::Io(std::io::Error::new(std::io::ErrorKind::NotFound, "missing")),
105 OxdocErrorCode::Io,
106 "E001",
107 ),
108 (
109 OxdocError::CorruptedZip(zip::result::ZipError::FileNotFound),
110 OxdocErrorCode::CorruptedZip,
111 "E002",
112 ),
113 (
114 OxdocError::MissingPart("word/document.xml".to_owned()),
115 OxdocErrorCode::MissingPart,
116 "E003",
117 ),
118 (
119 OxdocError::UnsupportedEncryptedPart("word/document.xml".to_owned()),
120 OxdocErrorCode::UnsupportedEncryptedPart,
121 "E004",
122 ),
123 (
124 OxdocError::PartTooLarge {
125 path: "word/document.xml".to_owned(),
126 size: 2,
127 limit: 1,
128 },
129 OxdocErrorCode::PartTooLarge,
130 "E005",
131 ),
132 (
133 OxdocError::SuspiciousZipEntry {
134 path: "word/document.xml".to_owned(),
135 reason: "directory".to_owned(),
136 },
137 OxdocErrorCode::SuspiciousZipEntry,
138 "E006",
139 ),
140 (
141 OxdocError::SuspiciousRelationshipTarget {
142 path: "_rels/.rels".to_owned(),
143 target: "https://example.invalid/document.xml".to_owned(),
144 reason: "external".to_owned(),
145 },
146 OxdocErrorCode::SuspiciousRelationshipTarget,
147 "E007",
148 ),
149 (
150 OxdocError::MissingCoreRelations,
151 OxdocErrorCode::MissingCoreRelations,
152 "E008",
153 ),
154 (
155 OxdocError::MalformedXmlNode {
156 path: "_rels/.rels".to_owned(),
157 source: quick_xml::Error::Syntax(quick_xml::errors::SyntaxError::UnclosedTag),
158 },
159 OxdocErrorCode::MalformedXmlNode,
160 "E009",
161 ),
162 (
163 OxdocError::InvalidArgument("bad delimiter".to_owned()),
164 OxdocErrorCode::InvalidArgument,
165 "E010",
166 ),
167 ];
168
169 for (error, code, label) in cases {
170 assert_eq!(error.code(), code);
171 assert_eq!(error.code().as_str(), label);
172 }
173 }
174}