Skip to main content

vexy_vsvg/parser/
error.rs

1// this_file: crates/vexy-vsvg/src/parser/error.rs
2
3use std::fmt;
4
5use thiserror::Error;
6
7/// Parse error types
8#[derive(Error, Debug)]
9pub enum ParseError {
10    #[error("XML parsing error: {0}")]
11    XmlError(#[from] quick_xml::Error),
12
13    #[error("Attribute parsing error: {0}")]
14    AttrError(String),
15
16    #[error("Invalid UTF-8: {0}")]
17    Utf8Error(#[from] std::str::Utf8Error),
18
19    #[error("Document structure error: {0}")]
20    StructureError(String),
21
22    #[error("Unexpected end of document")]
23    UnexpectedEnd,
24
25    #[error("Invalid entity reference: {0}")]
26    EntityError(String),
27
28    #[error("Invalid namespace declaration: {0}")]
29    NamespaceError(String),
30
31    #[error("Malformed DOCTYPE: {0}")]
32    DoctypeError(String),
33
34    #[error("Unsupported XML feature: {0}")]
35    UnsupportedFeature(String),
36
37    #[error("Security violation: {0}")]
38    SecurityError(String),
39
40    #[error("File I/O error: {0}")]
41    FileIoError(#[from] std::io::Error),
42
43    #[error("{0}")]
44    DetailedError(Box<DetailedParseError>),
45}
46
47/// Detailed parse error with context
48#[derive(Debug, Clone)]
49pub struct DetailedParseError {
50    /// File path (if available)
51    pub file_path: Option<String>,
52    /// Line number (1-based)
53    pub line: usize,
54    /// Column number (1-based)
55    pub column: usize,
56    /// Byte offset in the source
57    pub byte_offset: usize,
58    /// Error message
59    pub message: String,
60    /// Error severity level
61    pub severity: ErrorSeverity,
62    /// Error category for better tooling integration
63    pub category: ErrorCategory,
64    /// Source code context
65    pub context: Option<ErrorContext>,
66    /// Suggested fixes (if any)
67    pub suggestions: Vec<String>,
68}
69
70/// Error severity levels
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum ErrorSeverity {
73    Error,
74    Warning,
75    Info,
76}
77
78/// Error categories for better categorization
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum ErrorCategory {
81    Syntax,
82    Structure,
83    Namespace,
84    Entity,
85    Encoding,
86    Security,
87    Performance,
88}
89
90/// Error context with source code snippet
91#[derive(Debug, Clone)]
92pub struct ErrorContext {
93    /// Lines of source code around the error
94    pub lines: Vec<String>,
95    /// Index of the error line in the lines vector
96    pub error_line_index: usize,
97    /// Column position in the error line
98    pub error_column: usize,
99}
100
101impl fmt::Display for DetailedParseError {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        // Format: severity: file.svg:line:column: category: error message
104        write!(f, "{:?}: ", self.severity)?;
105
106        if let Some(ref path) = self.file_path {
107            write!(f, "{path}:")?;
108        }
109        write!(
110            f,
111            "{}:{}: {:?}: {}",
112            self.line, self.column, self.category, self.message
113        )?;
114
115        // Add source context if available
116        if let Some(ref ctx) = self.context {
117            writeln!(f)?;
118            writeln!(f)?;
119
120            // Display lines with line numbers
121            let start_line = self.line.saturating_sub(ctx.error_line_index);
122            for (i, line) in ctx.lines.iter().enumerate() {
123                let line_num = start_line + i;
124                let prefix = if i == ctx.error_line_index { ">" } else { " " };
125                writeln!(f, "{prefix} {line_num:3} | {line}")?;
126
127                // Add error pointer on the error line
128                if i == ctx.error_line_index {
129                    let spaces = " ".repeat(ctx.error_column + 6);
130                    writeln!(f, "{spaces} ^")?;
131                }
132            }
133        }
134
135        // Add suggestions if available
136        if !self.suggestions.is_empty() {
137            writeln!(f)?;
138            writeln!(f, "Suggestions:")?;
139            for suggestion in &self.suggestions {
140                writeln!(f, "  - {suggestion}")?;
141            }
142        }
143
144        Ok(())
145    }
146}
147
148/// Parse result type
149pub type ParseResult<T> = Result<T, ParseError>;