Skip to main content

presentar_terminal/
error.rs

1//! Error types for presentar-terminal.
2
3use presentar_core::BrickVerification;
4use thiserror::Error;
5
6/// Errors that can occur in the TUI application.
7#[derive(Debug, Error)]
8pub enum TuiError {
9    /// IO error from terminal operations.
10    #[error("IO error: {0}")]
11    Io(#[from] std::io::Error),
12
13    /// Brick verification failed (Jidoka gate).
14    #[error("Brick verification failed: {0}")]
15    VerificationFailed(VerificationError),
16
17    /// Invalid Brick configuration.
18    #[error("Invalid brick: {0}")]
19    InvalidBrick(String),
20
21    /// Budget exceeded during rendering.
22    #[error("Budget exceeded: {phase} took {elapsed_ms}ms (budget: {budget_ms}ms)")]
23    BudgetExceeded {
24        phase: String,
25        elapsed_ms: u64,
26        budget_ms: u64,
27    },
28
29    /// Terminal not available.
30    #[error("Terminal not available")]
31    TerminalNotAvailable,
32}
33
34/// Verification error with details.
35#[derive(Debug)]
36pub struct VerificationError {
37    /// The verification result.
38    pub verification: BrickVerification,
39    /// Human-readable summary.
40    pub summary: String,
41}
42
43impl std::fmt::Display for VerificationError {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        write!(f, "{}", self.summary)
46    }
47}
48
49impl From<BrickVerification> for VerificationError {
50    fn from(v: BrickVerification) -> Self {
51        let summary = if v.is_valid() {
52            "Verification passed".to_string()
53        } else {
54            format!(
55                "Verification failed: {} assertion(s) failed",
56                v.failed.len()
57            )
58        };
59        Self {
60            verification: v,
61            summary,
62        }
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use presentar_core::BrickAssertion;
70    use std::time::Duration;
71
72    #[test]
73    fn test_tui_error_io() {
74        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
75        let tui_err: TuiError = io_err.into();
76        assert!(matches!(tui_err, TuiError::Io(_)));
77        assert!(tui_err.to_string().contains("IO error"));
78    }
79
80    #[test]
81    fn test_tui_error_invalid_brick() {
82        let err = TuiError::InvalidBrick("test error".to_string());
83        assert!(err.to_string().contains("Invalid brick"));
84        assert!(err.to_string().contains("test error"));
85    }
86
87    #[test]
88    fn test_tui_error_budget_exceeded() {
89        let err = TuiError::BudgetExceeded {
90            phase: "render".to_string(),
91            elapsed_ms: 50,
92            budget_ms: 16,
93        };
94        let msg = err.to_string();
95        assert!(msg.contains("Budget exceeded"));
96        assert!(msg.contains("render"));
97        assert!(msg.contains("50ms"));
98        assert!(msg.contains("16ms"));
99    }
100
101    #[test]
102    fn test_tui_error_terminal_not_available() {
103        let err = TuiError::TerminalNotAvailable;
104        assert_eq!(err.to_string(), "Terminal not available");
105    }
106
107    #[test]
108    fn test_verification_error_display() {
109        let verification = BrickVerification {
110            passed: vec![],
111            failed: vec![(BrickAssertion::max_latency_ms(16), "too slow".to_string())],
112            verification_time: Duration::from_micros(10),
113        };
114        let err = VerificationError::from(verification);
115        assert!(err.to_string().contains("1 assertion(s) failed"));
116    }
117
118    #[test]
119    fn test_verification_error_passed() {
120        let verification = BrickVerification {
121            passed: vec![BrickAssertion::max_latency_ms(16)],
122            failed: vec![],
123            verification_time: Duration::from_micros(10),
124        };
125        let err = VerificationError::from(verification);
126        assert_eq!(err.to_string(), "Verification passed");
127    }
128
129    #[test]
130    fn test_tui_error_verification_failed() {
131        let verification = BrickVerification {
132            passed: vec![],
133            failed: vec![(BrickAssertion::max_latency_ms(16), "too slow".to_string())],
134            verification_time: Duration::from_micros(10),
135        };
136        let err = TuiError::VerificationFailed(VerificationError::from(verification));
137        assert!(err.to_string().contains("Brick verification failed"));
138    }
139
140    #[test]
141    fn test_verification_error_debug() {
142        let verification = BrickVerification {
143            passed: vec![],
144            failed: vec![(BrickAssertion::max_latency_ms(16), "too slow".to_string())],
145            verification_time: Duration::from_micros(10),
146        };
147        let err = VerificationError::from(verification);
148        let debug_str = format!("{:?}", err);
149        assert!(debug_str.contains("VerificationError"));
150    }
151
152    #[test]
153    fn test_verification_error_multiple_failures() {
154        let verification = BrickVerification {
155            passed: vec![BrickAssertion::max_latency_ms(100)],
156            failed: vec![
157                (BrickAssertion::max_latency_ms(16), "too slow".to_string()),
158                (
159                    BrickAssertion::max_latency_ms(8),
160                    "way too slow".to_string(),
161                ),
162            ],
163            verification_time: Duration::from_micros(50),
164        };
165        let err = VerificationError::from(verification);
166        assert!(err.to_string().contains("2 assertion(s) failed"));
167        assert!(err.verification.passed.len() == 1);
168        assert!(err.verification.failed.len() == 2);
169    }
170
171    #[test]
172    fn test_tui_error_debug() {
173        let err = TuiError::TerminalNotAvailable;
174        let debug_str = format!("{:?}", err);
175        assert!(debug_str.contains("TerminalNotAvailable"));
176    }
177
178    #[test]
179    fn test_tui_error_io_from() {
180        let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
181        let tui_err = TuiError::from(io_err);
182        assert!(tui_err.to_string().contains("IO error"));
183        assert!(tui_err.to_string().contains("access denied"));
184    }
185
186    #[test]
187    fn test_budget_exceeded_all_fields() {
188        let err = TuiError::BudgetExceeded {
189            phase: "layout".to_string(),
190            elapsed_ms: 100,
191            budget_ms: 50,
192        };
193        let msg = err.to_string();
194        assert!(msg.contains("Budget exceeded"));
195        assert!(msg.contains("layout"));
196        assert!(msg.contains("100ms"));
197        assert!(msg.contains("50ms"));
198    }
199
200    #[test]
201    fn test_invalid_brick_with_details() {
202        let err = TuiError::InvalidBrick("Missing required field: title".to_string());
203        assert!(err.to_string().contains("Invalid brick"));
204        assert!(err.to_string().contains("Missing required field"));
205    }
206}