1use std::fmt;
4
5use crate::model::{SheetRef, Side, SourceDescription};
6
7#[non_exhaustive]
13#[derive(Debug)]
14pub enum OpenErrorKind {
15 NotFound,
16 PermissionDenied,
17 NotXlsx,
19 Corrupt,
21 Locked,
23 Other,
24}
25
26impl fmt::Display for OpenErrorKind {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 match self {
29 OpenErrorKind::NotFound => f.write_str("file not found"),
30 OpenErrorKind::PermissionDenied => f.write_str("permission denied"),
31 OpenErrorKind::NotXlsx => f.write_str("not an xlsx file"),
32 OpenErrorKind::Corrupt => f.write_str("file is corrupt"),
33 OpenErrorKind::Locked => f.write_str("file is locked"),
34 OpenErrorKind::Other => f.write_str("open failed"),
35 }
36 }
37}
38
39#[non_exhaustive]
41#[derive(Debug)]
42pub enum ReadErrorKind {
43 SheetNotFound,
44 MalformedSheet,
45 Other,
46}
47
48impl fmt::Display for ReadErrorKind {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 match self {
51 ReadErrorKind::SheetNotFound => f.write_str("sheet not found"),
52 ReadErrorKind::MalformedSheet => f.write_str("sheet is malformed"),
53 ReadErrorKind::Other => f.write_str("read failed"),
54 }
55 }
56}
57
58#[derive(Clone, Copy, PartialEq, Eq, Debug)]
64pub enum LimitKind {
65 Sheets,
66 CellsRead,
67 CellsCompared,
68 DiffsReturned,
69}
70
71impl fmt::Display for LimitKind {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 match self {
74 LimitKind::Sheets => f.write_str("max_sheets"),
75 LimitKind::CellsRead => f.write_str("max_cells_read"),
76 LimitKind::CellsCompared => f.write_str("max_cells_compared"),
77 LimitKind::DiffsReturned => f.write_str("max_diffs_returned"),
78 }
79 }
80}
81
82pub struct CalamiLineError(pub(crate) calamine::XlsxError);
90
91impl fmt::Debug for CalamiLineError {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 write!(f, "calamine error: {}", self.0)
94 }
95}
96impl fmt::Display for CalamiLineError {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 write!(f, "{}", self.0)
99 }
100}
101impl std::error::Error for CalamiLineError {}
102
103#[non_exhaustive]
112#[derive(Debug)]
113pub enum SheetsDiffError {
114 OpenWorkbook {
116 side: Side,
117 source: SourceDescription,
118 kind: OpenErrorKind,
119 inner: Option<Box<CalamiLineError>>,
121 },
122 ReadSheet {
124 side: Side,
125 sheet: SheetRef,
126 kind: ReadErrorKind,
127 inner: Option<Box<CalamiLineError>>,
128 },
129 UnsupportedFormat { side: Side, detail: String },
131 EncryptedWorkbook { side: Side },
133 InvalidOptions { detail: String },
135 Cancelled,
137 LimitExceeded { limit: LimitKind, observed: u64 },
139 Internal { detail: String },
141}
142
143impl fmt::Display for SheetsDiffError {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 match self {
146 SheetsDiffError::OpenWorkbook { side, source, kind, .. } => {
147 let name = source
148 .display_name
149 .as_deref()
150 .unwrap_or("<unknown>");
151 write!(f, "cannot open {side} workbook '{name}': {kind}")
152 }
153 SheetsDiffError::ReadSheet { side, sheet, kind, .. } => {
154 write!(f, "cannot read sheet '{}' from {side} workbook: {kind}", sheet.name)
155 }
156 SheetsDiffError::UnsupportedFormat { side, detail } => {
157 write!(f, "{side} workbook is not a supported xlsx format: {detail}")
158 }
159 SheetsDiffError::EncryptedWorkbook { side } => {
160 write!(f, "{side} workbook is password-protected")
161 }
162 SheetsDiffError::InvalidOptions { detail } => {
163 write!(f, "invalid options: {detail}")
164 }
165 SheetsDiffError::Cancelled => f.write_str("comparison was cancelled"),
166 SheetsDiffError::LimitExceeded { limit, observed } => {
167 write!(f, "limit '{limit}' exceeded (observed {observed})")
168 }
169 SheetsDiffError::Internal { detail } => {
170 write!(f, "internal error: {detail}")
171 }
172 }
173 }
174}
175
176impl std::error::Error for SheetsDiffError {
177 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
178 match self {
179 SheetsDiffError::OpenWorkbook { inner, .. } => {
180 inner.as_deref().map(|e| e as &dyn std::error::Error)
181 }
182 SheetsDiffError::ReadSheet { inner, .. } => {
183 inner.as_deref().map(|e| e as &dyn std::error::Error)
184 }
185 _ => None,
186 }
187 }
188}
189
190impl SheetsDiffError {
195 pub(crate) fn open_workbook(
196 side: Side,
197 source: SourceDescription,
198 calamine_err: calamine::XlsxError,
199 ) -> Self {
200 let kind = classify_open_error(&calamine_err);
201 SheetsDiffError::OpenWorkbook {
202 side,
203 source,
204 kind,
205 inner: Some(Box::new(CalamiLineError(calamine_err))),
206 }
207 }
208
209 pub(crate) fn read_sheet(
210 side: Side,
211 sheet: SheetRef,
212 calamine_err: calamine::XlsxError,
213 ) -> Self {
214 let kind = classify_read_error(&calamine_err);
215 SheetsDiffError::ReadSheet {
216 side,
217 sheet,
218 kind,
219 inner: Some(Box::new(CalamiLineError(calamine_err))),
220 }
221 }
222}
223
224fn classify_open_error(e: &calamine::XlsxError) -> OpenErrorKind {
225 use calamine::XlsxError;
226 match e {
227 XlsxError::Password => OpenErrorKind::NotXlsx, XlsxError::FileNotFound(_) => OpenErrorKind::NotFound,
229 XlsxError::Io(io) => match io.kind() {
230 std::io::ErrorKind::NotFound => OpenErrorKind::NotFound,
231 std::io::ErrorKind::PermissionDenied => OpenErrorKind::PermissionDenied,
232 _ => OpenErrorKind::Other,
233 },
234 XlsxError::Zip(_) => OpenErrorKind::NotXlsx,
235 _ => OpenErrorKind::Corrupt,
236 }
237}
238
239fn classify_read_error(e: &calamine::XlsxError) -> ReadErrorKind {
240 use calamine::XlsxError;
241 match e {
242 XlsxError::WorksheetNotFound(_) => ReadErrorKind::SheetNotFound,
243 _ => ReadErrorKind::MalformedSheet,
244 }
245}
246
247pub(crate) fn from_open_error(
250 side: Side,
251 source: SourceDescription,
252 e: calamine::XlsxError,
253) -> SheetsDiffError {
254 if matches!(e, calamine::XlsxError::Password) {
255 SheetsDiffError::EncryptedWorkbook { side }
256 } else {
257 SheetsDiffError::open_workbook(side, source, e)
258 }
259}