Skip to main content

oxidize_pdf/advanced_tables/
error.rs

1//! Error types for advanced table building and validation
2
3use thiserror::Error;
4
5/// Errors that can occur during table building and validation
6#[derive(Debug, Error, Clone, PartialEq)]
7pub enum TableError {
8    /// Table must have at least one column
9    #[error("Table must have at least one column")]
10    NoColumns,
11
12    /// Row has incorrect number of cells
13    #[error("Row {row} has {found} cells but expected {expected} columns")]
14    ColumnMismatch {
15        /// Row index (0-based)
16        row: usize,
17        /// Number of cells found in the row
18        found: usize,
19        /// Number of columns expected
20        expected: usize,
21    },
22
23    /// Header cell extends beyond table width
24    #[error(
25        "Header cell at level {level} extends beyond table width ({start} + {span} > {total})"
26    )]
27    HeaderOutOfBounds {
28        /// Header level (0-based)
29        level: usize,
30        /// Starting column index
31        start: usize,
32        /// Column span
33        span: usize,
34        /// Total table columns
35        total: usize,
36    },
37
38    /// Header cells overlap
39    #[error("Overlapping header cells at level {level} column {column}")]
40    HeaderOverlap {
41        /// Header level where overlap occurs
42        level: usize,
43        /// Column where overlap is detected
44        column: usize,
45    },
46
47    /// Invalid column width
48    #[error("Invalid column width {width} at column {column}: must be positive")]
49    InvalidColumnWidth {
50        /// Column index
51        column: usize,
52        /// Invalid width value
53        width: f64,
54    },
55
56    /// Invalid row height
57    #[error("Invalid row height {height} at row {row}: must be positive")]
58    InvalidRowHeight {
59        /// Row index
60        row: usize,
61        /// Invalid height value
62        height: f64,
63    },
64
65    /// Cell span is invalid
66    #[error("Invalid {span_type} span {span} at row {row} col {col}: must be at least 1")]
67    InvalidCellSpan {
68        /// Type of span (colspan/rowspan)
69        span_type: String,
70        /// Row index
71        row: usize,
72        /// Column index
73        col: usize,
74        /// Invalid span value
75        span: usize,
76    },
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn test_no_columns_error() {
85        let err = TableError::NoColumns;
86        assert_eq!(format!("{}", err), "Table must have at least one column");
87    }
88
89    #[test]
90    fn test_column_mismatch_error() {
91        let err = TableError::ColumnMismatch {
92            row: 2,
93            found: 3,
94            expected: 5,
95        };
96        let msg = format!("{}", err);
97        assert!(msg.contains("Row 2"));
98        assert!(msg.contains("3 cells"));
99        assert!(msg.contains("5 columns"));
100    }
101
102    #[test]
103    fn test_header_out_of_bounds_error() {
104        let err = TableError::HeaderOutOfBounds {
105            level: 1,
106            start: 3,
107            span: 4,
108            total: 5,
109        };
110        let msg = format!("{}", err);
111        assert!(msg.contains("level 1"));
112        assert!(msg.contains("3 + 4 > 5"));
113    }
114
115    #[test]
116    fn test_header_overlap_error() {
117        let err = TableError::HeaderOverlap {
118            level: 0,
119            column: 2,
120        };
121        let msg = format!("{}", err);
122        assert!(msg.contains("Overlapping"));
123        assert!(msg.contains("level 0"));
124        assert!(msg.contains("column 2"));
125    }
126
127    #[test]
128    fn test_invalid_column_width_error() {
129        let err = TableError::InvalidColumnWidth {
130            column: 3,
131            width: -10.5,
132        };
133        let msg = format!("{}", err);
134        assert!(msg.contains("Invalid column width"));
135        assert!(msg.contains("-10.5"));
136        assert!(msg.contains("column 3"));
137        assert!(msg.contains("must be positive"));
138    }
139
140    #[test]
141    fn test_invalid_column_width_zero() {
142        let err = TableError::InvalidColumnWidth {
143            column: 0,
144            width: 0.0,
145        };
146        let msg = format!("{}", err);
147        assert!(msg.contains("0"));
148    }
149
150    #[test]
151    fn test_invalid_row_height_error() {
152        let err = TableError::InvalidRowHeight {
153            row: 5,
154            height: -2.0,
155        };
156        let msg = format!("{}", err);
157        assert!(msg.contains("Invalid row height"));
158        assert!(msg.contains("-2"));
159        assert!(msg.contains("row 5"));
160        assert!(msg.contains("must be positive"));
161    }
162
163    #[test]
164    fn test_invalid_cell_span_colspan() {
165        let err = TableError::InvalidCellSpan {
166            span_type: "colspan".to_string(),
167            row: 1,
168            col: 2,
169            span: 0,
170        };
171        let msg = format!("{}", err);
172        assert!(msg.contains("Invalid colspan span"));
173        assert!(msg.contains("row 1"));
174        assert!(msg.contains("col 2"));
175        assert!(msg.contains("must be at least 1"));
176    }
177
178    #[test]
179    fn test_invalid_cell_span_rowspan() {
180        let err = TableError::InvalidCellSpan {
181            span_type: "rowspan".to_string(),
182            row: 3,
183            col: 4,
184            span: 0,
185        };
186        let msg = format!("{}", err);
187        assert!(msg.contains("Invalid rowspan span"));
188    }
189
190    #[test]
191    fn test_error_clone() {
192        let err1 = TableError::NoColumns;
193        let err2 = err1.clone();
194        assert_eq!(err1, err2);
195    }
196
197    #[test]
198    fn test_error_partial_eq() {
199        let err1 = TableError::ColumnMismatch {
200            row: 1,
201            found: 2,
202            expected: 3,
203        };
204        let err2 = TableError::ColumnMismatch {
205            row: 1,
206            found: 2,
207            expected: 3,
208        };
209        let err3 = TableError::ColumnMismatch {
210            row: 1,
211            found: 2,
212            expected: 4,
213        };
214
215        assert_eq!(err1, err2);
216        assert_ne!(err1, err3);
217    }
218
219    #[test]
220    fn test_error_debug() {
221        let err = TableError::NoColumns;
222        let debug_str = format!("{:?}", err);
223        assert!(debug_str.contains("NoColumns"));
224    }
225
226    #[test]
227    fn test_all_variants_debug() {
228        let variants: Vec<TableError> = vec![
229            TableError::NoColumns,
230            TableError::ColumnMismatch {
231                row: 0,
232                found: 1,
233                expected: 2,
234            },
235            TableError::HeaderOutOfBounds {
236                level: 0,
237                start: 0,
238                span: 1,
239                total: 1,
240            },
241            TableError::HeaderOverlap {
242                level: 0,
243                column: 0,
244            },
245            TableError::InvalidColumnWidth {
246                column: 0,
247                width: -1.0,
248            },
249            TableError::InvalidRowHeight {
250                row: 0,
251                height: -1.0,
252            },
253            TableError::InvalidCellSpan {
254                span_type: "test".to_string(),
255                row: 0,
256                col: 0,
257                span: 0,
258            },
259        ];
260
261        for err in variants {
262            let debug_str = format!("{:?}", err);
263            assert!(!debug_str.is_empty());
264        }
265    }
266
267    #[test]
268    fn test_error_is_send_sync() {
269        fn assert_send_sync<T: Send + Sync>() {}
270        assert_send_sync::<TableError>();
271    }
272
273    #[test]
274    fn test_error_is_std_error() {
275        fn assert_error<T: std::error::Error>(_: &T) {}
276        let err = TableError::NoColumns;
277        assert_error(&err);
278    }
279
280    #[test]
281    fn test_large_indices() {
282        let err = TableError::ColumnMismatch {
283            row: usize::MAX,
284            found: usize::MAX - 1,
285            expected: 100,
286        };
287        let msg = format!("{}", err);
288        assert!(!msg.is_empty());
289    }
290
291    #[test]
292    fn test_special_float_values() {
293        // Test with infinity
294        let err = TableError::InvalidColumnWidth {
295            column: 0,
296            width: f64::INFINITY,
297        };
298        let msg = format!("{}", err);
299        assert!(msg.contains("inf"));
300
301        // Test with NaN
302        let err = TableError::InvalidRowHeight {
303            row: 0,
304            height: f64::NAN,
305        };
306        let msg = format!("{}", err);
307        assert!(msg.contains("NaN") || msg.contains("nan"));
308    }
309}