presentar_terminal/
error.rs1use presentar_core::BrickVerification;
4use thiserror::Error;
5
6#[derive(Debug, Error)]
8pub enum TuiError {
9 #[error("IO error: {0}")]
11 Io(#[from] std::io::Error),
12
13 #[error("Brick verification failed: {0}")]
15 VerificationFailed(VerificationError),
16
17 #[error("Invalid brick: {0}")]
19 InvalidBrick(String),
20
21 #[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 #[error("Terminal not available")]
31 TerminalNotAvailable,
32}
33
34#[derive(Debug)]
36pub struct VerificationError {
37 pub verification: BrickVerification,
39 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}