1use thiserror::Error;
4
5#[derive(Error, Debug)]
7pub enum Error {
8 #[error("XML parsing error: {0}")]
11 XmlParseError(#[from] quick_xml::DeError),
12
13 #[error("XML serialization error: {0}")]
15 XmlSerializeError(#[from] quick_xml::SeError),
16
17 #[error("IO error: {0}")]
20 IoError(#[from] std::io::Error),
21
22 #[error("File not found: {path}")]
25 FileNotFound { path: String },
26
27 #[error("Directory not found: {path}")]
29 DirectoryNotFound { path: String },
30
31 #[error("Cannot read file {path}: {reason}")]
33 FileReadError { path: String, reason: String },
34
35 #[error("Cannot write file {path}: {reason}")]
37 FileWriteError { path: String, reason: String },
38
39 #[error("Entity '{entity}' not found")]
42 EntityNotFound {
43 entity: String,
44 available: Vec<String>,
45 },
46
47 #[error("Catalog entry '{entry}' not found in catalog '{catalog}'")]
49 CatalogEntryNotFound { catalog: String, entry: String },
50
51 #[error("Catalog '{catalog}' not found")]
53 CatalogNotFound {
54 catalog: String,
55 available: Vec<String>,
56 },
57
58 #[error("Validation error in field '{field}': {message}")]
61 ValidationError { field: String, message: String },
62
63 #[error("Missing required field: {field}")]
65 MissingRequiredField { field: String },
66
67 #[error("Invalid value for field '{field}': {value}. {hint}")]
69 InvalidValue {
70 field: String,
71 value: String,
72 hint: String,
73 },
74
75 #[error("Value out of range for field '{field}': {value}. Expected {min} to {max}")]
77 OutOfRange {
78 field: String,
79 value: String,
80 min: String,
81 max: String,
82 },
83
84 #[error("Type mismatch for field '{field}': expected {expected}, got {actual}")]
86 TypeMismatch {
87 field: String,
88 expected: String,
89 actual: String,
90 },
91
92 #[error("Parameter '{param}' error: {message}")]
95 ParameterError { param: String, message: String },
96
97 #[error("Parameter '{param}' not found")]
99 ParameterNotFound {
100 param: String,
101 available: Vec<String>,
102 },
103
104 #[error("Circular dependency detected: {cycle}")]
106 CircularDependency { cycle: String },
107
108 #[error("Invalid XML structure: {message}")]
111 InvalidXmlStructure { message: String },
112
113 #[error("Malformed XML: expected {expected}, found {found} at {location}")]
115 MalformedXml {
116 expected: String,
117 found: String,
118 location: String,
119 },
120
121 #[error("Catalog error: {0}")]
124 CatalogError(String),
125
126 #[error("Choice group error: {message}")]
128 ChoiceGroupError { message: String },
129
130 #[error("Failed to parse '{input}': {reason}")]
133 ParseError { input: String, reason: String },
134
135 #[error("Expression evaluation failed: {expression} - {reason}")]
137 ExpressionError { expression: String, reason: String },
138
139 #[error("Constraint violation: {constraint}")]
142 ConstraintViolation { constraint: String },
143
144 #[error("Inconsistent state: {message}")]
146 InconsistentState { message: String },
147}
148
149impl Error {
150 pub fn file_not_found(path: &str) -> Self {
154 Error::FileNotFound {
155 path: path.to_string(),
156 }
157 }
158
159 pub fn directory_not_found(path: &str) -> Self {
161 Error::DirectoryNotFound {
162 path: path.to_string(),
163 }
164 }
165
166 pub fn file_read_error(path: &str, reason: &str) -> Self {
168 Error::FileReadError {
169 path: path.to_string(),
170 reason: reason.to_string(),
171 }
172 }
173
174 pub fn file_write_error(path: &str, reason: &str) -> Self {
176 Error::FileWriteError {
177 path: path.to_string(),
178 reason: reason.to_string(),
179 }
180 }
181
182 pub fn entity_not_found(entity: &str, available: &[String]) -> Self {
186 Error::EntityNotFound {
187 entity: entity.to_string(),
188 available: available.to_vec(),
189 }
190 }
191
192 pub fn catalog_entry_not_found(catalog: &str, entry: &str) -> Self {
194 Error::CatalogEntryNotFound {
195 catalog: catalog.to_string(),
196 entry: entry.to_string(),
197 }
198 }
199
200 pub fn catalog_not_found(catalog: &str, available: &[String]) -> Self {
202 Error::CatalogNotFound {
203 catalog: catalog.to_string(),
204 available: available.to_vec(),
205 }
206 }
207
208 pub fn validation_error(field: &str, message: &str) -> Self {
212 Error::ValidationError {
213 field: field.to_string(),
214 message: message.to_string(),
215 }
216 }
217
218 pub fn missing_field(field: &str) -> Self {
220 Error::MissingRequiredField {
221 field: field.to_string(),
222 }
223 }
224
225 pub fn invalid_value(field: &str, value: &str, hint: &str) -> Self {
227 Error::InvalidValue {
228 field: field.to_string(),
229 value: value.to_string(),
230 hint: hint.to_string(),
231 }
232 }
233
234 pub fn out_of_range(field: &str, value: &str, min: &str, max: &str) -> Self {
236 Error::OutOfRange {
237 field: field.to_string(),
238 value: value.to_string(),
239 min: min.to_string(),
240 max: max.to_string(),
241 }
242 }
243
244 pub fn type_mismatch(field: &str, expected: &str, actual: &str) -> Self {
246 Error::TypeMismatch {
247 field: field.to_string(),
248 expected: expected.to_string(),
249 actual: actual.to_string(),
250 }
251 }
252
253 pub fn parameter_error(param: &str, message: &str) -> Self {
257 Error::ParameterError {
258 param: param.to_string(),
259 message: message.to_string(),
260 }
261 }
262
263 pub fn parameter_not_found(param: &str, available: &[String]) -> Self {
265 Error::ParameterNotFound {
266 param: param.to_string(),
267 available: available.to_vec(),
268 }
269 }
270
271 pub fn invalid_xml(message: &str) -> Self {
275 Error::InvalidXmlStructure {
276 message: message.to_string(),
277 }
278 }
279
280 pub fn malformed_xml(expected: &str, found: &str, location: &str) -> Self {
282 Error::MalformedXml {
283 expected: expected.to_string(),
284 found: found.to_string(),
285 location: location.to_string(),
286 }
287 }
288
289 pub fn parsing_error(msg: &str, line: usize, col: usize) -> Self {
291 Error::ValidationError {
292 field: format!("line {}, column {}", line, col),
293 message: msg.to_string(),
294 }
295 }
296
297 pub fn circular_dependency(cycle: &str) -> Self {
301 Error::CircularDependency {
302 cycle: cycle.to_string(),
303 }
304 }
305
306 pub fn parse_error(input: &str, reason: &str) -> Self {
308 Error::ParseError {
309 input: input.to_string(),
310 reason: reason.to_string(),
311 }
312 }
313
314 pub fn expression_error(expression: &str, reason: &str) -> Self {
316 Error::ExpressionError {
317 expression: expression.to_string(),
318 reason: reason.to_string(),
319 }
320 }
321
322 pub fn constraint_violation(constraint: &str) -> Self {
324 Error::ConstraintViolation {
325 constraint: constraint.to_string(),
326 }
327 }
328
329 pub fn catalog_error(message: &str) -> Self {
331 Error::CatalogError(message.to_string())
332 }
333
334 pub fn choice_group_error(message: &str) -> Self {
336 Error::ChoiceGroupError {
337 message: message.to_string(),
338 }
339 }
340
341 pub fn with_context(mut self, context: &str) -> Self {
343 match &mut self {
344 Error::ValidationError {
345 ref mut message, ..
346 } => {
347 *message = format!("{}: {}", context, message);
348 }
349 Error::CatalogError(ref mut msg) => {
350 *msg = format!("{}: {}", context, msg);
351 }
352 Error::ChoiceGroupError { ref mut message } => {
353 *message = format!("{}: {}", context, message);
354 }
355 Error::ParameterError {
356 ref mut message, ..
357 } => {
358 *message = format!("{}: {}", context, message);
359 }
360 Error::FileReadError { ref mut reason, .. } => {
361 *reason = format!("{}: {}", context, reason);
362 }
363 Error::FileWriteError { ref mut reason, .. } => {
364 *reason = format!("{}: {}", context, reason);
365 }
366 Error::ParseError { ref mut reason, .. } => {
367 *reason = format!("{}: {}", context, reason);
368 }
369 Error::ExpressionError { ref mut reason, .. } => {
370 *reason = format!("{}: {}", context, reason);
371 }
372 Error::InvalidValue { ref mut hint, .. } => {
373 *hint = format!("{}: {}", context, hint);
374 }
375 Error::OutOfRange { ref mut value, .. } => {
376 *value = format!("{}: {}", context, value);
377 }
378 _ => {}
379 }
380 self
381 }
382}
383
384pub type Result<T> = std::result::Result<T, Error>;
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390
391 #[test]
392 fn test_error_creation() {
393 let err = Error::file_not_found("/path/to/file.xosc");
394 assert!(matches!(err, Error::FileNotFound { path } if path == "/path/to/file.xosc"));
395 }
396
397 #[test]
398 fn test_entity_not_found() {
399 let err = Error::entity_not_found("ego", &["target".to_string()]);
400 match err {
401 Error::EntityNotFound { entity, available } => {
402 assert_eq!(entity, "ego");
403 assert_eq!(available, vec!["target"]);
404 }
405 _ => panic!("Wrong error type"),
406 }
407 }
408
409 #[test]
410 fn test_catalog_not_found() {
411 let err = Error::catalog_not_found("vehicles", &["controllers".to_string()]);
412 match err {
413 Error::CatalogNotFound { catalog, available } => {
414 assert_eq!(catalog, "vehicles");
415 assert_eq!(available, vec!["controllers"]);
416 }
417 _ => panic!("Wrong error type"),
418 }
419 }
420
421 #[test]
422 fn test_parameter_not_found() {
423 let err = Error::parameter_not_found("speed", &["distance".to_string()]);
424 match err {
425 Error::ParameterNotFound { param, available } => {
426 assert_eq!(param, "speed");
427 assert_eq!(available, vec!["distance"]);
428 }
429 _ => panic!("Wrong error type"),
430 }
431 }
432
433 #[test]
434 fn test_validation_error() {
435 let err = Error::validation_error("speed", "must be positive");
436 match err {
437 Error::ValidationError { field, message } => {
438 assert_eq!(field, "speed");
439 assert_eq!(message, "must be positive");
440 }
441 _ => panic!("Wrong error type"),
442 }
443 }
444
445 #[test]
446 fn test_missing_field() {
447 let err = Error::missing_field("name");
448 assert!(matches!(err, Error::MissingRequiredField { field } if field == "name"));
449 }
450
451 #[test]
452 fn test_invalid_value() {
453 let err = Error::invalid_value("speed", "-5", "must be positive");
454 match err {
455 Error::InvalidValue { field, value, hint } => {
456 assert_eq!(field, "speed");
457 assert_eq!(value, "-5");
458 assert_eq!(hint, "must be positive");
459 }
460 _ => panic!("Wrong error type"),
461 }
462 }
463
464 #[test]
465 fn test_out_of_range() {
466 let err = Error::out_of_range("speed", "150", "0", "120");
467 match err {
468 Error::OutOfRange {
469 field,
470 value,
471 min,
472 max,
473 } => {
474 assert_eq!(field, "speed");
475 assert_eq!(value, "150");
476 assert_eq!(min, "0");
477 assert_eq!(max, "120");
478 }
479 _ => panic!("Wrong error type"),
480 }
481 }
482
483 #[test]
484 fn test_type_mismatch() {
485 let err = Error::type_mismatch("speed", "number", "string");
486 match err {
487 Error::TypeMismatch {
488 field,
489 expected,
490 actual,
491 } => {
492 assert_eq!(field, "speed");
493 assert_eq!(expected, "number");
494 assert_eq!(actual, "string");
495 }
496 _ => panic!("Wrong error type"),
497 }
498 }
499
500 #[test]
501 fn test_parameter_error() {
502 let err = Error::parameter_error("speed", "division by zero");
503 match err {
504 Error::ParameterError { param, message } => {
505 assert_eq!(param, "speed");
506 assert_eq!(message, "division by zero");
507 }
508 _ => panic!("Wrong error type"),
509 }
510 }
511
512 #[test]
513 fn test_circular_dependency() {
514 let err = Error::circular_dependency("A -> B -> C -> A");
515 assert!(matches!(err, Error::CircularDependency { .. }));
516 }
517
518 #[test]
519 fn test_invalid_xml() {
520 let err = Error::invalid_xml("Document is empty");
521 assert!(matches!(err, Error::InvalidXmlStructure { .. }));
522 }
523
524 #[test]
525 fn test_malformed_xml() {
526 let err = Error::malformed_xml(">", "<", "line 1");
527 match err {
528 Error::MalformedXml {
529 expected,
530 found,
531 location,
532 } => {
533 assert_eq!(expected, ">");
534 assert_eq!(found, "<");
535 assert_eq!(location, "line 1");
536 }
537 _ => panic!("Wrong error type"),
538 }
539 }
540
541 #[test]
542 fn test_parse_error() {
543 let err = Error::parse_error("abc", "not a number");
544 match err {
545 Error::ParseError { input, reason } => {
546 assert_eq!(input, "abc");
547 assert_eq!(reason, "not a number");
548 }
549 _ => panic!("Wrong error type"),
550 }
551 }
552
553 #[test]
554 fn test_expression_error() {
555 let err = Error::expression_error("1/0", "division by zero");
556 match err {
557 Error::ExpressionError { expression, reason } => {
558 assert_eq!(expression, "1/0");
559 assert_eq!(reason, "division by zero");
560 }
561 _ => panic!("Wrong error type"),
562 }
563 }
564
565 #[test]
566 fn test_constraint_violation() {
567 let err = Error::constraint_violation("speed cannot be negative");
568 assert!(matches!(err, Error::ConstraintViolation { .. }));
569 }
570
571 #[test]
572 fn test_with_context() {
573 let err = Error::validation_error("speed", "invalid").with_context("while parsing vehicle");
574 match err {
575 Error::ValidationError { message, .. } => {
576 assert!(message.contains("while parsing vehicle"));
577 }
578 _ => panic!("Wrong error type"),
579 }
580 }
581
582 #[test]
583 fn test_error_display() {
584 let err = Error::entity_not_found("ego", &["target".to_string()]);
585 let msg = format!("{}", err);
586 assert!(msg.contains("ego"));
587 }
588
589 #[test]
590 fn test_catalog_entry_not_found() {
591 let err = Error::catalog_entry_not_found("vehicles", "car1");
592 match err {
593 Error::CatalogEntryNotFound { catalog, entry } => {
594 assert_eq!(catalog, "vehicles");
595 assert_eq!(entry, "car1");
596 }
597 _ => panic!("Wrong error type"),
598 }
599 }
600}