Skip to main content

ironsbe_schema/
error.rs

1//! Error types for schema parsing and validation.
2
3use thiserror::Error;
4
5/// Error type for schema parsing operations.
6#[derive(Debug, Error)]
7pub enum ParseError {
8    /// XML parsing error.
9    #[error("XML parsing error: {0}")]
10    Xml(#[from] quick_xml::Error),
11
12    /// Missing required attribute.
13    #[error("missing required attribute '{attribute}' on element '{element}'")]
14    MissingAttribute {
15        /// Element name.
16        element: String,
17        /// Attribute name.
18        attribute: String,
19    },
20
21    /// Invalid attribute value.
22    #[error("invalid value '{value}' for attribute '{attribute}' on element '{element}'")]
23    InvalidAttribute {
24        /// Element name.
25        element: String,
26        /// Attribute name.
27        attribute: String,
28        /// Invalid value.
29        value: String,
30    },
31
32    /// Unknown element encountered.
33    #[error("unknown element '{element}' in context '{context}'")]
34    UnknownElement {
35        /// Element name.
36        element: String,
37        /// Parent context.
38        context: String,
39    },
40
41    /// Unknown type reference.
42    #[error("unknown type '{type_name}' referenced in field '{field}'")]
43    UnknownType {
44        /// Type name.
45        type_name: String,
46        /// Field name.
47        field: String,
48    },
49
50    /// Duplicate definition.
51    #[error("duplicate {kind} definition: '{name}'")]
52    DuplicateDefinition {
53        /// Kind of definition (type, message, etc.).
54        kind: String,
55        /// Name of the duplicate.
56        name: String,
57    },
58
59    /// Invalid schema structure.
60    #[error("invalid schema structure: {message}")]
61    InvalidStructure {
62        /// Error message.
63        message: String,
64    },
65
66    /// IO error.
67    #[error("IO error: {0}")]
68    Io(#[from] std::io::Error),
69
70    /// UTF-8 decoding error.
71    #[error("UTF-8 error: {0}")]
72    Utf8(#[from] std::str::Utf8Error),
73}
74
75/// Error type for schema validation.
76#[derive(Debug, Error)]
77pub enum SchemaError {
78    /// Parsing error.
79    #[error("parse error: {0}")]
80    Parse(#[from] ParseError),
81
82    /// Type not found.
83    #[error("type '{name}' not found")]
84    TypeNotFound {
85        /// Type name.
86        name: String,
87    },
88
89    /// Message not found.
90    #[error("message '{name}' not found")]
91    MessageNotFound {
92        /// Message name.
93        name: String,
94    },
95
96    /// Invalid field offset.
97    #[error(
98        "invalid field offset: field '{field}' at offset {offset} overlaps with previous field"
99    )]
100    InvalidOffset {
101        /// Field name.
102        field: String,
103        /// Invalid offset.
104        offset: usize,
105    },
106
107    /// Block length mismatch.
108    #[error(
109        "block length mismatch for message '{message}': declared {declared}, calculated {calculated}"
110    )]
111    BlockLengthMismatch {
112        /// Message name.
113        message: String,
114        /// Declared block length.
115        declared: u16,
116        /// Calculated block length.
117        calculated: u16,
118    },
119
120    /// Circular type reference.
121    #[error("circular type reference detected: {path}")]
122    CircularReference {
123        /// Path of the circular reference.
124        path: String,
125    },
126
127    /// Invalid enum value.
128    #[error("invalid enum value '{value}' for enum '{enum_name}'")]
129    InvalidEnumValue {
130        /// Enum name.
131        enum_name: String,
132        /// Invalid value.
133        value: String,
134    },
135
136    /// Validation error.
137    #[error("validation error: {message}")]
138    Validation {
139        /// Error message.
140        message: String,
141    },
142}
143
144impl ParseError {
145    /// Creates a missing attribute error.
146    pub fn missing_attr(element: impl Into<String>, attribute: impl Into<String>) -> Self {
147        Self::MissingAttribute {
148            element: element.into(),
149            attribute: attribute.into(),
150        }
151    }
152
153    /// Creates an invalid attribute error.
154    pub fn invalid_attr(
155        element: impl Into<String>,
156        attribute: impl Into<String>,
157        value: impl Into<String>,
158    ) -> Self {
159        Self::InvalidAttribute {
160            element: element.into(),
161            attribute: attribute.into(),
162            value: value.into(),
163        }
164    }
165
166    /// Creates an unknown element error.
167    pub fn unknown_element(element: impl Into<String>, context: impl Into<String>) -> Self {
168        Self::UnknownElement {
169            element: element.into(),
170            context: context.into(),
171        }
172    }
173
174    /// Creates a duplicate definition error.
175    pub fn duplicate(kind: impl Into<String>, name: impl Into<String>) -> Self {
176        Self::DuplicateDefinition {
177            kind: kind.into(),
178            name: name.into(),
179        }
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn test_parse_error_missing_attr() {
189        let err = ParseError::missing_attr("message", "id");
190        let msg = err.to_string();
191        assert!(msg.contains("message"));
192        assert!(msg.contains("id"));
193        assert!(msg.contains("missing required attribute"));
194    }
195
196    #[test]
197    fn test_parse_error_invalid_attr() {
198        let err = ParseError::invalid_attr("field", "offset", "abc");
199        let msg = err.to_string();
200        assert!(msg.contains("field"));
201        assert!(msg.contains("offset"));
202        assert!(msg.contains("abc"));
203        assert!(msg.contains("invalid value"));
204    }
205
206    #[test]
207    fn test_parse_error_unknown_element() {
208        let err = ParseError::unknown_element("foo", "types");
209        let msg = err.to_string();
210        assert!(msg.contains("foo"));
211        assert!(msg.contains("types"));
212        assert!(msg.contains("unknown element"));
213    }
214
215    #[test]
216    fn test_parse_error_duplicate() {
217        let err = ParseError::duplicate("type", "MyType");
218        let msg = err.to_string();
219        assert!(msg.contains("type"));
220        assert!(msg.contains("MyType"));
221        assert!(msg.contains("duplicate"));
222    }
223
224    #[test]
225    fn test_parse_error_unknown_type() {
226        let err = ParseError::UnknownType {
227            type_name: "UnknownType".to_string(),
228            field: "myField".to_string(),
229        };
230        let msg = err.to_string();
231        assert!(msg.contains("UnknownType"));
232        assert!(msg.contains("myField"));
233    }
234
235    #[test]
236    fn test_parse_error_invalid_structure() {
237        let err = ParseError::InvalidStructure {
238            message: "bad structure".to_string(),
239        };
240        let msg = err.to_string();
241        assert!(msg.contains("bad structure"));
242    }
243
244    #[test]
245    fn test_schema_error_type_not_found() {
246        let err = SchemaError::TypeNotFound {
247            name: "MissingType".to_string(),
248        };
249        let msg = err.to_string();
250        assert!(msg.contains("MissingType"));
251        assert!(msg.contains("not found"));
252    }
253
254    #[test]
255    fn test_schema_error_message_not_found() {
256        let err = SchemaError::MessageNotFound {
257            name: "MissingMessage".to_string(),
258        };
259        let msg = err.to_string();
260        assert!(msg.contains("MissingMessage"));
261    }
262
263    #[test]
264    fn test_schema_error_invalid_offset() {
265        let err = SchemaError::InvalidOffset {
266            field: "price".to_string(),
267            offset: 10,
268        };
269        let msg = err.to_string();
270        assert!(msg.contains("price"));
271        assert!(msg.contains("10"));
272    }
273
274    #[test]
275    fn test_schema_error_block_length_mismatch() {
276        let err = SchemaError::BlockLengthMismatch {
277            message: "Order".to_string(),
278            declared: 48,
279            calculated: 56,
280        };
281        let msg = err.to_string();
282        assert!(msg.contains("Order"));
283        assert!(msg.contains("48"));
284        assert!(msg.contains("56"));
285    }
286
287    #[test]
288    fn test_schema_error_circular_reference() {
289        let err = SchemaError::CircularReference {
290            path: "A -> B -> A".to_string(),
291        };
292        let msg = err.to_string();
293        assert!(msg.contains("A -> B -> A"));
294        assert!(msg.contains("circular"));
295    }
296
297    #[test]
298    fn test_schema_error_invalid_enum_value() {
299        let err = SchemaError::InvalidEnumValue {
300            enum_name: "Side".to_string(),
301            value: "Unknown".to_string(),
302        };
303        let msg = err.to_string();
304        assert!(msg.contains("Side"));
305        assert!(msg.contains("Unknown"));
306    }
307
308    #[test]
309    fn test_schema_error_validation() {
310        let err = SchemaError::Validation {
311            message: "validation failed".to_string(),
312        };
313        let msg = err.to_string();
314        assert!(msg.contains("validation failed"));
315    }
316
317    #[test]
318    fn test_schema_error_from_parse_error() {
319        let parse_err = ParseError::missing_attr("msg", "id");
320        let schema_err: SchemaError = parse_err.into();
321        let msg = schema_err.to_string();
322        assert!(msg.contains("parse error"));
323    }
324
325    #[test]
326    fn test_error_debug() {
327        let err = ParseError::missing_attr("elem", "attr");
328        let debug_str = format!("{:?}", err);
329        assert!(debug_str.contains("MissingAttribute"));
330    }
331}