1use std::fmt;
4use std::io;
5
6use crate::namespace::error::NamespaceError;
7use crate::node::error::NodeError;
8use crate::parser::error::ParseError;
9use crate::schema::error::SchemaError;
10use crate::schema::fetcher::error::FetchError;
11use crate::schema::xsd::error::XsdParseError;
12use crate::xpath::error::{XPathEvalError, XPathSyntaxError};
13
14#[derive(Debug, Clone, Default)]
43pub struct ErrorLocation {
44 pub line: Option<usize>,
46 pub column: Option<usize>,
48 pub byte_offset: Option<usize>,
50 pub xpath: Option<String>,
52}
53
54impl ErrorLocation {
55 pub fn new() -> Self {
57 Self::default()
58 }
59
60 pub fn from_offset(byte_offset: usize) -> Self {
62 Self {
63 byte_offset: Some(byte_offset),
64 ..Default::default()
65 }
66 }
67
68 pub fn from_offset_with_input(byte_offset: usize, input: &str) -> Self {
70 let (line, column) = Self::calculate_line_column(input, byte_offset);
71 Self {
72 line: Some(line),
73 column: Some(column),
74 byte_offset: Some(byte_offset),
75 xpath: None,
76 }
77 }
78
79 pub fn from_line_column(line: usize, column: usize) -> Self {
81 Self {
82 line: Some(line),
83 column: Some(column),
84 byte_offset: None,
85 xpath: None,
86 }
87 }
88
89 pub fn with_xpath(mut self, xpath: String) -> Self {
91 self.xpath = Some(xpath);
92 self
93 }
94
95 pub fn with_offset(mut self, offset: usize) -> Self {
97 self.byte_offset = Some(offset);
98 self
99 }
100
101 pub fn has_position(&self) -> bool {
103 self.line.is_some() || self.byte_offset.is_some()
104 }
105
106 pub fn calculate_line_column(input: &str, byte_offset: usize) -> (usize, usize) {
112 let mut line = 1;
113 let mut column = 1;
114
115 for (pos, ch) in input.char_indices() {
116 if pos >= byte_offset {
117 break;
118 }
119 if ch == '\n' {
120 line += 1;
121 column = 1;
122 } else {
123 column += 1;
124 }
125 }
126
127 (line, column)
128 }
129}
130
131impl fmt::Display for ErrorLocation {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 let mut parts = Vec::new();
134
135 if let (Some(line), Some(col)) = (self.line, self.column) {
136 parts.push(format!("line {}:{}", line, col));
137 } else if let Some(offset) = self.byte_offset {
138 parts.push(format!("position {}", offset));
139 }
140
141 if let Some(xpath) = &self.xpath {
142 parts.push(format!("at {}", xpath));
143 }
144
145 write!(f, "{}", parts.join(", "))
146 }
147}
148
149#[derive(Debug, thiserror::Error)]
151pub enum Error {
152 #[error("parse error: {0}")]
154 Parse(#[from] ParseError),
155
156 #[error("io error: {0}")]
158 Io(#[from] io::Error),
159
160 #[error("xpath syntax error: {0}")]
162 XPathSyntax(#[from] XPathSyntaxError),
163
164 #[error("xpath evaluation error: {0}")]
166 XPathEval(#[from] XPathEvalError),
167
168 #[error("schema error: {0}")]
170 Schema(#[from] SchemaError),
171
172 #[error("validation error: {message}")]
174 Validation {
175 message: String,
177 line: Option<usize>,
179 column: Option<usize>,
181 },
182
183 #[error("namespace error: {0}")]
185 Namespace(#[from] NamespaceError),
186
187 #[error("node error: {0}")]
189 Node(#[from] NodeError),
190
191 #[error("invalid operation: {0}")]
193 InvalidOperation(String),
194
195 #[error("fetch error: {0}")]
197 Fetch(#[from] FetchError),
198
199 #[error("utf8 error: {0}")]
201 Utf8(#[from] std::str::Utf8Error),
202
203 #[error("string utf8 error: {0}")]
205 FromUtf8(#[from] std::string::FromUtf8Error),
206
207 #[error("xsd parse error: {0}")]
209 XsdParse(#[from] XsdParseError),
210}
211
212impl From<quick_xml::Error> for Error {
213 fn from(err: quick_xml::Error) -> Self {
214 ParseError::Generic {
215 message: err.to_string(),
216 }
217 .into()
218 }
219}
220
221impl From<quick_xml::events::attributes::AttrError> for Error {
222 fn from(err: quick_xml::events::attributes::AttrError) -> Self {
223 ParseError::AttributeError {
224 message: err.to_string(),
225 }
226 .into()
227 }
228}
229
230pub type Result<T> = std::result::Result<T, Error>;
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
235pub enum ErrorLevel {
236 Warning,
238 #[default]
240 Error,
241 Fatal,
243}
244
245impl std::fmt::Display for ErrorLevel {
246 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247 match self {
248 ErrorLevel::Warning => write!(f, "warning"),
249 ErrorLevel::Error => write!(f, "error"),
250 ErrorLevel::Fatal => write!(f, "fatal"),
251 }
252 }
253}
254
255#[derive(Debug, Clone)]
257pub struct StructuredError {
258 pub message: String,
260 pub location: ErrorLocation,
262 pub error_type: ValidationErrorType,
264 pub level: ErrorLevel,
266 pub node_name: Option<String>,
268 pub expected: Option<String>,
270 pub found: Option<String>,
272}
273
274impl Default for StructuredError {
275 fn default() -> Self {
276 Self {
277 message: String::new(),
278 location: ErrorLocation::default(),
279 error_type: ValidationErrorType::Other,
280 level: ErrorLevel::Error,
281 node_name: None,
282 expected: None,
283 found: None,
284 }
285 }
286}
287
288impl StructuredError {
289 pub fn new(message: impl Into<String>, error_type: ValidationErrorType) -> Self {
291 Self {
292 message: message.into(),
293 error_type,
294 ..Default::default()
295 }
296 }
297
298 pub fn with_line(mut self, line: usize) -> Self {
300 self.location.line = Some(line);
301 self
302 }
303
304 pub fn with_column(mut self, column: usize) -> Self {
306 self.location.column = Some(column);
307 self
308 }
309
310 pub fn with_byte_offset(mut self, offset: usize) -> Self {
312 self.location.byte_offset = Some(offset);
313 self
314 }
315
316 pub fn with_level(mut self, level: ErrorLevel) -> Self {
318 self.level = level;
319 self
320 }
321
322 pub fn with_element_path(mut self, path: impl Into<String>) -> Self {
324 self.location.xpath = Some(path.into());
325 self
326 }
327
328 pub fn with_node_name(mut self, name: impl Into<String>) -> Self {
330 self.node_name = Some(name.into());
331 self
332 }
333
334 pub fn with_expected(mut self, expected: impl Into<String>) -> Self {
336 self.expected = Some(expected.into());
337 self
338 }
339
340 pub fn with_found(mut self, found: impl Into<String>) -> Self {
342 self.found = Some(found.into());
343 self
344 }
345
346 pub fn is_warning(&self) -> bool {
348 self.level == ErrorLevel::Warning
349 }
350
351 pub fn is_error(&self) -> bool {
353 self.level >= ErrorLevel::Error
354 }
355
356 pub fn with_location(mut self, location: &ErrorLocation) -> Self {
358 if let Some(line) = location.line {
359 self.location.line = Some(line);
360 }
361 if let Some(column) = location.column {
362 self.location.column = Some(column);
363 }
364 if let Some(offset) = location.byte_offset {
365 self.location.byte_offset = Some(offset);
366 }
367 if let Some(ref xpath) = location.xpath {
368 self.location.xpath = Some(xpath.clone());
369 }
370 self
371 }
372
373 pub fn set_location(mut self, location: ErrorLocation) -> Self {
375 self.location = location;
376 self
377 }
378
379 pub fn line(&self) -> Option<usize> {
381 self.location.line
382 }
383
384 pub fn column(&self) -> Option<usize> {
386 self.location.column
387 }
388
389 pub fn byte_offset(&self) -> Option<usize> {
391 self.location.byte_offset
392 }
393
394 pub fn element_path(&self) -> Option<&str> {
396 self.location.xpath.as_deref()
397 }
398
399 pub fn calculate_line_column(mut self, input: &str) -> Self {
403 if let Some(offset) = self.location.byte_offset {
404 let (line, column) = ErrorLocation::calculate_line_column(input, offset);
405 self.location.line = Some(line);
406 self.location.column = Some(column);
407 }
408 self
409 }
410}
411
412impl From<&StructuredError> for ErrorLocation {
413 fn from(err: &StructuredError) -> Self {
414 err.location.clone()
415 }
416}
417
418impl std::fmt::Display for StructuredError {
419 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
420 write!(f, "[{}] ", self.level)?;
422
423 if let Some(ref path) = self.location.xpath {
424 write!(f, "{}", path)?;
425 if let Some(line) = self.location.line {
426 write!(f, " (line {})", line)?;
427 }
428 write!(f, ": ")?;
429 } else if let (Some(line), Some(col)) = (self.location.line, self.location.column) {
430 write!(f, "{}:{}: ", line, col)?;
431 } else if let Some(line) = self.location.line {
432 write!(f, "line {}: ", line)?;
433 } else if let Some(offset) = self.location.byte_offset {
434 write!(f, "offset {}: ", offset)?;
435 }
436
437 write!(f, "{}", self.message)?;
438
439 if let (Some(expected), Some(found)) = (&self.expected, &self.found) {
440 write!(f, " (expected: {}, found: {})", expected, found)?;
441 }
442
443 Ok(())
444 }
445}
446
447#[derive(Debug, Clone, Copy, PartialEq, Eq)]
449pub enum ValidationErrorType {
450 UnknownElement,
452 UnknownAttribute,
454 MissingRequiredElement,
456 MissingRequiredAttribute,
458 InvalidAttributeValue,
460 InvalidContent,
462 InvalidTextContent,
464 TooManyOccurrences,
466 TooFewOccurrences,
468 ElementOutOfOrder,
470 UnexpectedElement,
472 NamespaceMismatch,
474 SchemaNotFound,
476 IdentityConstraint,
478 TypeNotFound,
480 FacetViolation,
482 ContentModelViolation,
484 UnclosedElement,
486 Other,
488}