1use thiserror::Error;
4
5#[derive(Debug, Error)]
7pub enum ParseError {
8 #[error("XML parsing error: {0}")]
10 Xml(#[from] quick_xml::Error),
11
12 #[error("missing required attribute '{attribute}' on element '{element}'")]
14 MissingAttribute {
15 element: String,
17 attribute: String,
19 },
20
21 #[error("invalid value '{value}' for attribute '{attribute}' on element '{element}'")]
23 InvalidAttribute {
24 element: String,
26 attribute: String,
28 value: String,
30 },
31
32 #[error("unknown element '{element}' in context '{context}'")]
34 UnknownElement {
35 element: String,
37 context: String,
39 },
40
41 #[error("unknown type '{type_name}' referenced in field '{field}'")]
43 UnknownType {
44 type_name: String,
46 field: String,
48 },
49
50 #[error("duplicate {kind} definition: '{name}'")]
52 DuplicateDefinition {
53 kind: String,
55 name: String,
57 },
58
59 #[error("invalid schema structure: {message}")]
61 InvalidStructure {
62 message: String,
64 },
65
66 #[error("IO error: {0}")]
68 Io(#[from] std::io::Error),
69
70 #[error("UTF-8 error: {0}")]
72 Utf8(#[from] std::str::Utf8Error),
73}
74
75#[derive(Debug, Error)]
77pub enum SchemaError {
78 #[error("parse error: {0}")]
80 Parse(#[from] ParseError),
81
82 #[error("type '{name}' not found")]
84 TypeNotFound {
85 name: String,
87 },
88
89 #[error("message '{name}' not found")]
91 MessageNotFound {
92 name: String,
94 },
95
96 #[error(
98 "invalid field offset: field '{field}' at offset {offset} overlaps with previous field"
99 )]
100 InvalidOffset {
101 field: String,
103 offset: usize,
105 },
106
107 #[error(
109 "block length mismatch for message '{message}': declared {declared}, calculated {calculated}"
110 )]
111 BlockLengthMismatch {
112 message: String,
114 declared: u16,
116 calculated: u16,
118 },
119
120 #[error("circular type reference detected: {path}")]
122 CircularReference {
123 path: String,
125 },
126
127 #[error("invalid enum value '{value}' for enum '{enum_name}'")]
129 InvalidEnumValue {
130 enum_name: String,
132 value: String,
134 },
135
136 #[error("validation error: {message}")]
138 Validation {
139 message: String,
141 },
142}
143
144impl ParseError {
145 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 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 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 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}