1use std::fmt;
36
37#[derive(Debug, Clone, PartialEq)]
42pub enum EngineError {
43 ParseError { source: String, message: String },
48
49 ComponentNotFound(String),
53
54 RenderError(String),
56
57 ActionNotFound(String),
61
62 StateError(String),
64
65 ExpressionError(String),
67}
68
69impl fmt::Display for EngineError {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 match self {
72 EngineError::ParseError { source, message } => {
73 write!(f, "Parse error in '{}': {}", truncate(source, 60), message)
74 }
75 EngineError::ComponentNotFound(name) => {
76 write!(f, "Component not found: {}", name)
77 }
78 EngineError::RenderError(msg) => {
79 write!(f, "Render error: {}", msg)
80 }
81 EngineError::ActionNotFound(name) => {
82 write!(f, "No handler registered for action: {}", name)
83 }
84 EngineError::StateError(msg) => {
85 write!(f, "State error: {}", msg)
86 }
87 EngineError::ExpressionError(msg) => {
88 write!(f, "Expression error: {}", msg)
89 }
90 }
91 }
92}
93
94impl std::error::Error for EngineError {}
95
96impl From<String> for EngineError {
98 fn from(msg: String) -> Self {
99 EngineError::RenderError(msg)
100 }
101}
102
103fn truncate(s: &str, max_len: usize) -> &str {
105 if s.len() <= max_len {
106 s
107 } else {
108 let mut end = max_len;
110 while !s.is_char_boundary(end) && end > 0 {
111 end -= 1;
112 }
113 &s[..end]
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn test_parse_error_display() {
123 let err = EngineError::ParseError {
124 source: "Column { broken".to_string(),
125 message: "unexpected end of input".to_string(),
126 };
127 let msg = err.to_string();
128 assert!(msg.contains("Parse error"));
129 assert!(msg.contains("Column { broken"));
130 assert!(msg.contains("unexpected end of input"));
131 }
132
133 #[test]
134 fn test_component_not_found_display() {
135 let err = EngineError::ComponentNotFound("MyWidget".to_string());
136 assert_eq!(err.to_string(), "Component not found: MyWidget");
137 }
138
139 #[test]
140 fn test_action_not_found_display() {
141 let err = EngineError::ActionNotFound("submitForm".to_string());
142 assert_eq!(
143 err.to_string(),
144 "No handler registered for action: submitForm"
145 );
146 }
147
148 #[test]
149 fn test_render_error_display() {
150 let err = EngineError::RenderError("node tree corrupted".to_string());
151 assert_eq!(err.to_string(), "Render error: node tree corrupted");
152 }
153
154 #[test]
155 fn test_state_error_display() {
156 let err = EngineError::StateError("invalid JSON".to_string());
157 assert_eq!(err.to_string(), "State error: invalid JSON");
158 }
159
160 #[test]
161 fn test_expression_error_display() {
162 let err = EngineError::ExpressionError("division by zero".to_string());
163 assert_eq!(err.to_string(), "Expression error: division by zero");
164 }
165
166 #[test]
167 fn test_error_is_clone_and_eq() {
168 let err1 = EngineError::ActionNotFound("test".to_string());
169 let err2 = err1.clone();
170 assert_eq!(err1, err2);
171 }
172
173 #[test]
174 fn test_error_debug() {
175 let err = EngineError::ComponentNotFound("Foo".to_string());
176 let debug = format!("{:?}", err);
177 assert!(debug.contains("ComponentNotFound"));
178 assert!(debug.contains("Foo"));
179 }
180
181 #[test]
182 fn test_from_string() {
183 let err: EngineError = "something went wrong".to_string().into();
184 assert_eq!(
185 err,
186 EngineError::RenderError("something went wrong".to_string())
187 );
188 }
189
190 #[test]
191 fn test_truncate_long_source() {
192 let long_source = "a".repeat(200);
193 let err = EngineError::ParseError {
194 source: long_source,
195 message: "error".to_string(),
196 };
197 let display = err.to_string();
198 assert!(display.len() < 200);
200 }
201
202 #[test]
203 fn test_error_implements_std_error() {
204 let err = EngineError::StateError("test".to_string());
205 let std_err: &dyn std::error::Error = &err;
206 assert!(std_err.to_string().contains("State error"));
207 }
208}