Skip to main content

alimentar/tui/
error.rs

1//! TUI-specific error types
2//!
3//! Provides error handling for the TUI dataset viewer without panic paths.
4
5use std::fmt;
6
7/// TUI-specific error type
8///
9/// All errors are recoverable - no panics in TUI code.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum TuiError {
12    /// Dataset is empty (no rows)
13    EmptyDataset,
14    /// Row index out of bounds
15    RowOutOfBounds {
16        /// Requested row index
17        requested: usize,
18        /// Total row count
19        total: usize,
20    },
21    /// Column index out of bounds
22    ColumnOutOfBounds {
23        /// Requested column index
24        requested: usize,
25        /// Total column count
26        total: usize,
27    },
28    /// Failed to format cell value
29    FormatError {
30        /// Row index
31        row: usize,
32        /// Column index
33        col: usize,
34        /// Reason for failure
35        reason: String,
36    },
37    /// Schema mismatch between batches
38    SchemaMismatch {
39        /// Description of mismatch
40        description: String,
41    },
42    /// Unsupported Arrow data type
43    UnsupportedType {
44        /// The unsupported type name
45        type_name: String,
46    },
47    /// Render constraint violation
48    RenderConstraint {
49        /// Description of constraint violation
50        description: String,
51    },
52    /// Invalid scroll position
53    InvalidScroll {
54        /// Requested offset
55        requested: usize,
56        /// Maximum valid offset
57        max_valid: usize,
58    },
59}
60
61impl fmt::Display for TuiError {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        match self {
64            Self::EmptyDataset => write!(f, "Dataset is empty"),
65            Self::RowOutOfBounds { requested, total } => {
66                write!(f, "Row index {requested} out of bounds (total: {total})")
67            }
68            Self::ColumnOutOfBounds { requested, total } => {
69                write!(f, "Column index {requested} out of bounds (total: {total})")
70            }
71            Self::FormatError { row, col, reason } => {
72                write!(f, "Failed to format cell ({row}, {col}): {reason}")
73            }
74            Self::SchemaMismatch { description } => {
75                write!(f, "Schema mismatch: {description}")
76            }
77            Self::UnsupportedType { type_name } => {
78                write!(f, "Unsupported Arrow type: {type_name}")
79            }
80            Self::RenderConstraint { description } => {
81                write!(f, "Render constraint violation: {description}")
82            }
83            Self::InvalidScroll {
84                requested,
85                max_valid,
86            } => {
87                write!(f, "Invalid scroll position {requested} (max: {max_valid})")
88            }
89        }
90    }
91}
92
93impl std::error::Error for TuiError {}
94
95/// Result type for TUI operations
96pub type TuiResult<T> = Result<T, TuiError>;
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn f_error_display_empty_dataset() {
104        let err = TuiError::EmptyDataset;
105        assert_eq!(err.to_string(), "Dataset is empty");
106    }
107
108    #[test]
109    fn f_error_display_row_out_of_bounds() {
110        let err = TuiError::RowOutOfBounds {
111            requested: 100,
112            total: 80,
113        };
114        assert!(err.to_string().contains("100"));
115        assert!(err.to_string().contains("80"));
116    }
117
118    #[test]
119    fn f_error_display_column_out_of_bounds() {
120        let err = TuiError::ColumnOutOfBounds {
121            requested: 15,
122            total: 11,
123        };
124        assert!(err.to_string().contains("15"));
125        assert!(err.to_string().contains("11"));
126    }
127
128    #[test]
129    fn f_error_display_format_error() {
130        let err = TuiError::FormatError {
131            row: 5,
132            col: 3,
133            reason: "null value".to_string(),
134        };
135        assert!(err.to_string().contains('5'));
136        assert!(err.to_string().contains('3'));
137        assert!(err.to_string().contains("null value"));
138    }
139
140    #[test]
141    fn f_error_is_clone() {
142        let err = TuiError::EmptyDataset;
143        let cloned = err.clone();
144        assert_eq!(err, cloned);
145    }
146
147    #[test]
148    fn f_error_is_debug() {
149        let err = TuiError::EmptyDataset;
150        let debug_str = format!("{:?}", err);
151        assert!(debug_str.contains("EmptyDataset"));
152    }
153
154    #[test]
155    fn f_error_implements_error_trait() {
156        let err: Box<dyn std::error::Error> = Box::new(TuiError::EmptyDataset);
157        assert!(err.to_string().contains("empty"));
158    }
159
160    #[test]
161    fn f_error_display_schema_mismatch() {
162        let err = TuiError::SchemaMismatch {
163            description: "column count differs".to_string(),
164        };
165        let s = err.to_string();
166        assert!(s.contains("Schema mismatch"));
167        assert!(s.contains("column count differs"));
168    }
169
170    #[test]
171    fn f_error_display_unsupported_type() {
172        let err = TuiError::UnsupportedType {
173            type_name: "FixedSizeBinary".to_string(),
174        };
175        let s = err.to_string();
176        assert!(s.contains("Unsupported Arrow type"));
177        assert!(s.contains("FixedSizeBinary"));
178    }
179
180    #[test]
181    fn f_error_display_render_constraint() {
182        let err = TuiError::RenderConstraint {
183            description: "width exceeds terminal".to_string(),
184        };
185        let s = err.to_string();
186        assert!(s.contains("Render constraint"));
187        assert!(s.contains("width exceeds terminal"));
188    }
189
190    #[test]
191    fn f_error_display_invalid_scroll() {
192        let err = TuiError::InvalidScroll {
193            requested: 100,
194            max_valid: 80,
195        };
196        let s = err.to_string();
197        assert!(s.contains("Invalid scroll position"));
198        assert!(s.contains("100"));
199        assert!(s.contains("80"));
200    }
201}