Skip to main content

openscenario_rs/
error.rs

1//! Error types and error handling for the OpenSCENARIO library
2
3use thiserror::Error;
4
5/// Main error type for the OpenSCENARIO library
6#[derive(Error, Debug)]
7pub enum Error {
8    // XML/Serialization
9    /// XML deserialization failures
10    #[error("XML parsing error: {0}")]
11    XmlParseError(#[from] quick_xml::DeError),
12
13    /// XML serialization failures
14    #[error("XML serialization error: {0}")]
15    XmlSerializeError(#[from] quick_xml::SeError),
16
17    // I/O
18    /// File I/O failures
19    #[error("IO error: {0}")]
20    IoError(#[from] std::io::Error),
21
22    // File System Errors
23    /// File not found at specified path
24    #[error("File not found: {path}")]
25    FileNotFound { path: String },
26
27    /// Directory not found at specified path
28    #[error("Directory not found: {path}")]
29    DirectoryNotFound { path: String },
30
31    /// Cannot read file
32    #[error("Cannot read file {path}: {reason}")]
33    FileReadError { path: String, reason: String },
34
35    /// Cannot write file
36    #[error("Cannot write file {path}: {reason}")]
37    FileWriteError { path: String, reason: String },
38
39    // Reference Errors
40    /// Entity reference not found
41    #[error("Entity '{entity}' not found")]
42    EntityNotFound {
43        entity: String,
44        available: Vec<String>,
45    },
46
47    /// Catalog entry not found
48    #[error("Catalog entry '{entry}' not found in catalog '{catalog}'")]
49    CatalogEntryNotFound { catalog: String, entry: String },
50
51    /// Catalog not found
52    #[error("Catalog '{catalog}' not found")]
53    CatalogNotFound {
54        catalog: String,
55        available: Vec<String>,
56    },
57
58    // Validation Errors
59    /// Schema validation failures
60    #[error("Validation error in field '{field}': {message}")]
61    ValidationError { field: String, message: String },
62
63    /// Missing required field
64    #[error("Missing required field: {field}")]
65    MissingRequiredField { field: String },
66
67    /// Invalid value for field
68    #[error("Invalid value for field '{field}': {value}. {hint}")]
69    InvalidValue {
70        field: String,
71        value: String,
72        hint: String,
73    },
74
75    /// Value out of expected range
76    #[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    /// Type mismatch
85    #[error("Type mismatch for field '{field}': expected {expected}, got {actual}")]
86    TypeMismatch {
87        field: String,
88        expected: String,
89        actual: String,
90    },
91
92    // Parameter Errors
93    /// Parameter resolution failures
94    #[error("Parameter '{param}' error: {message}")]
95    ParameterError { param: String, message: String },
96
97    /// Parameter not found
98    #[error("Parameter '{param}' not found")]
99    ParameterNotFound {
100        param: String,
101        available: Vec<String>,
102    },
103
104    /// Circular dependency detected
105    #[error("Circular dependency detected: {cycle}")]
106    CircularDependency { cycle: String },
107
108    // XML/Structure Errors
109    /// Invalid XML structure
110    #[error("Invalid XML structure: {message}")]
111    InvalidXmlStructure { message: String },
112
113    /// Malformed XML with location context
114    #[error("Malformed XML: expected {expected}, found {found} at {location}")]
115    MalformedXml {
116        expected: String,
117        found: String,
118        location: String,
119    },
120
121    // Catalog Errors (remaining generic cases)
122    /// Generic catalog system error
123    #[error("Catalog error: {0}")]
124    CatalogError(String),
125
126    /// XSD Choice Group parsing errors
127    #[error("Choice group error: {message}")]
128    ChoiceGroupError { message: String },
129
130    // Parsing/Expression Errors
131    /// Failed to parse input
132    #[error("Failed to parse '{input}': {reason}")]
133    ParseError { input: String, reason: String },
134
135    /// Expression evaluation failed
136    #[error("Expression evaluation failed: {expression} - {reason}")]
137    ExpressionError { expression: String, reason: String },
138
139    // Constraint Violations
140    /// Constraint violation
141    #[error("Constraint violation: {constraint}")]
142    ConstraintViolation { constraint: String },
143
144    /// Inconsistent state
145    #[error("Inconsistent state: {message}")]
146    InconsistentState { message: String },
147}
148
149impl Error {
150    // File System Errors
151
152    /// Create a file not found error
153    pub fn file_not_found(path: &str) -> Self {
154        Error::FileNotFound {
155            path: path.to_string(),
156        }
157    }
158
159    /// Create a directory not found error
160    pub fn directory_not_found(path: &str) -> Self {
161        Error::DirectoryNotFound {
162            path: path.to_string(),
163        }
164    }
165
166    /// Create a file read error
167    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    /// Create a file write error
175    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    // Reference Errors
183
184    /// Create an entity not found error
185    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    /// Create a catalog entry not found error
193    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    /// Create a catalog not found error
201    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    // Validation Errors
209
210    /// Create a validation error
211    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    /// Create a missing required field error
219    pub fn missing_field(field: &str) -> Self {
220        Error::MissingRequiredField {
221            field: field.to_string(),
222        }
223    }
224
225    /// Create an invalid value error
226    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    /// Create an out of range error
235    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    /// Create a type mismatch error
245    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    // Parameter Errors
254
255    /// Create a parameter error
256    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    /// Create a parameter not found error
264    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    // XML/Structure Errors
272
273    /// Create an invalid XML structure error
274    pub fn invalid_xml(message: &str) -> Self {
275        Error::InvalidXmlStructure {
276            message: message.to_string(),
277        }
278    }
279
280    /// Create a malformed XML error with location
281    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    /// Create a parsing error with location information
290    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    // Other Errors
298
299    /// Create a circular dependency error
300    pub fn circular_dependency(cycle: &str) -> Self {
301        Error::CircularDependency {
302            cycle: cycle.to_string(),
303        }
304    }
305
306    /// Create a parse error
307    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    /// Create an expression error
315    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    /// Create a constraint violation error
323    pub fn constraint_violation(constraint: &str) -> Self {
324        Error::ConstraintViolation {
325            constraint: constraint.to_string(),
326        }
327    }
328
329    /// Create a catalog error
330    pub fn catalog_error(message: &str) -> Self {
331        Error::CatalogError(message.to_string())
332    }
333
334    /// Create a choice group error
335    pub fn choice_group_error(message: &str) -> Self {
336        Error::ChoiceGroupError {
337            message: message.to_string(),
338        }
339    }
340
341    /// Add context to an error
342    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
384/// Result type alias for the OpenSCENARIO library
385pub 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}