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