oxirs_ttl/
error.rs

1//! Error types for Turtle-family format parsing
2//!
3//! This module provides comprehensive error handling for parsing and serialization
4//! operations, including position tracking and error recovery capabilities.
5
6use std::fmt;
7use thiserror::Error;
8
9/// Position in a text document
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub struct TextPosition {
12    /// Line number (1-based)
13    pub line: usize,
14    /// Column number (1-based)
15    pub column: usize,
16    /// Byte offset from start of document
17    pub offset: usize,
18}
19
20impl Default for TextPosition {
21    fn default() -> Self {
22        Self::start()
23    }
24}
25
26impl TextPosition {
27    /// Create a new text position
28    pub fn new(line: usize, column: usize, offset: usize) -> Self {
29        Self {
30            line,
31            column,
32            offset,
33        }
34    }
35
36    /// Initial position at start of document
37    pub fn start() -> Self {
38        Self::new(1, 1, 0)
39    }
40
41    /// Advance position by one character
42    pub fn advance_char(&mut self, ch: char) {
43        if ch == '\n' {
44            self.line += 1;
45            self.column = 1;
46        } else {
47            self.column += 1;
48        }
49        self.offset += ch.len_utf8();
50    }
51
52    /// Advance position by multiple bytes
53    pub fn advance_bytes(&mut self, bytes: &[u8]) {
54        for &byte in bytes {
55            if byte == b'\n' {
56                self.line += 1;
57                self.column = 1;
58            } else if byte >= 0x20 || byte == b'\t' {
59                self.column += 1;
60            }
61            self.offset += 1;
62        }
63    }
64}
65
66impl fmt::Display for TextPosition {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        write!(f, "line {}, column {}", self.line, self.column)
69    }
70}
71
72/// Syntax error in Turtle-family format
73#[derive(Debug, Clone, Error)]
74pub enum TurtleSyntaxError {
75    /// Unexpected character
76    #[error("Unexpected character '{character}' at {position}")]
77    UnexpectedCharacter {
78        /// The unexpected character
79        character: char,
80        /// Position where error occurred
81        position: TextPosition,
82    },
83
84    /// Unexpected end of input
85    #[error("Unexpected end of input at {position}")]
86    UnexpectedEof {
87        /// Position where EOF was encountered
88        position: TextPosition,
89    },
90
91    /// Invalid IRI
92    #[error("Invalid IRI '{iri}' at {position}: {reason}")]
93    InvalidIri {
94        /// The invalid IRI
95        iri: String,
96        /// Reason for invalidity
97        reason: String,
98        /// Position of the IRI
99        position: TextPosition,
100    },
101
102    /// Invalid language tag
103    #[error("Invalid language tag '{tag}' at {position}: {reason}")]
104    InvalidLanguageTag {
105        /// The invalid language tag
106        tag: String,
107        /// Reason for invalidity
108        reason: String,
109        /// Position of the tag
110        position: TextPosition,
111    },
112
113    /// Invalid literal
114    #[error("Invalid literal '{literal}' at {position}: {reason}")]
115    InvalidLiteral {
116        /// The invalid literal
117        literal: String,
118        /// Reason for invalidity
119        reason: String,
120        /// Position of the literal
121        position: TextPosition,
122    },
123
124    /// Invalid escape sequence
125    #[error("Invalid escape sequence '\\{sequence}' at {position}")]
126    InvalidEscape {
127        /// The invalid escape sequence (without backslash)
128        sequence: String,
129        /// Position of the escape
130        position: TextPosition,
131    },
132
133    /// Invalid Unicode code point
134    #[error("Invalid Unicode code point U+{codepoint:04X} at {position}")]
135    InvalidUnicode {
136        /// The invalid code point
137        codepoint: u32,
138        /// Position of the code point
139        position: TextPosition,
140    },
141
142    /// Invalid blank node identifier
143    #[error("Invalid blank node identifier '{id}' at {position}")]
144    InvalidBlankNode {
145        /// The invalid identifier
146        id: String,
147        /// Position of the identifier
148        position: TextPosition,
149    },
150
151    /// Undefined prefix
152    #[error("Undefined prefix '{prefix}' at {position}")]
153    UndefinedPrefix {
154        /// The undefined prefix
155        prefix: String,
156        /// Position where prefix was used
157        position: TextPosition,
158    },
159
160    /// Invalid prefix declaration
161    #[error("Invalid prefix declaration for '{prefix}' at {position}: {reason}")]
162    InvalidPrefix {
163        /// The prefix being declared
164        prefix: String,
165        /// Reason for invalidity
166        reason: String,
167        /// Position of the declaration
168        position: TextPosition,
169    },
170
171    /// Invalid base IRI declaration
172    #[error("Invalid base IRI '{iri}' at {position}: {reason}")]
173    InvalidBase {
174        /// The invalid base IRI
175        iri: String,
176        /// Reason for invalidity
177        reason: String,
178        /// Position of the declaration
179        position: TextPosition,
180    },
181
182    /// Generic syntax error
183    #[error("Syntax error at {position}: {message}")]
184    Generic {
185        /// Error message
186        message: String,
187        /// Position where error occurred
188        position: TextPosition,
189    },
190}
191
192/// High-level parsing error
193#[derive(Debug, Error)]
194pub enum TurtleParseError {
195    /// Syntax error in the input
196    #[error("Syntax error: {0}")]
197    Syntax(#[from] TurtleSyntaxError),
198
199    /// I/O error while reading
200    #[error("I/O error: {0}")]
201    Io(#[from] std::io::Error),
202
203    /// RDF model error (invalid terms, etc.)
204    #[error("RDF model error: {0}")]
205    Model(#[from] oxirs_core::OxirsError),
206
207    /// Multiple errors (for batch processing)
208    #[error("Multiple errors occurred ({} errors)", .errors.len())]
209    Multiple {
210        /// Collection of errors
211        errors: Vec<TurtleParseError>,
212    },
213}
214
215/// Result type for parsing operations
216pub type TurtleResult<T> = Result<T, TurtleParseError>;
217
218/// Error that can occur during tokenization
219#[derive(Debug, Clone, Error)]
220pub enum TokenRecognizerError {
221    /// Unexpected character
222    #[error("Unexpected character: {0}")]
223    UnexpectedCharacter(char),
224
225    /// Unexpected end of input
226    #[error("Unexpected end of input")]
227    UnexpectedEof,
228
229    /// Invalid token
230    #[error("Invalid token: {0}")]
231    Invalid(String),
232}
233
234/// Error that can occur during rule recognition
235#[derive(Debug, Clone, Error)]
236pub enum RuleRecognizerError {
237    /// Unexpected token
238    #[error("Unexpected token: {0}")]
239    UnexpectedToken(String),
240
241    /// Missing required token
242    #[error("Missing required token: {0}")]
243    MissingToken(String),
244
245    /// Invalid rule application
246    #[error("Invalid rule: {0}")]
247    InvalidRule(String),
248}
249
250impl TurtleParseError {
251    /// Create a new syntax error
252    pub fn syntax(error: TurtleSyntaxError) -> Self {
253        Self::Syntax(error)
254    }
255
256    /// Create a new I/O error
257    pub fn io(error: std::io::Error) -> Self {
258        Self::Io(error)
259    }
260
261    /// Create a new model error
262    pub fn model(error: oxirs_core::OxirsError) -> Self {
263        Self::Model(error)
264    }
265
266    /// Combine multiple errors
267    pub fn multiple(errors: Vec<TurtleParseError>) -> Self {
268        Self::Multiple { errors }
269    }
270
271    /// Get the position of this error, if available
272    pub fn position(&self) -> Option<TextPosition> {
273        match self {
274            Self::Syntax(syntax_error) => Some(syntax_error.position()),
275            _ => None,
276        }
277    }
278}
279
280impl TurtleSyntaxError {
281    /// Get the position where this error occurred
282    pub fn position(&self) -> TextPosition {
283        match self {
284            Self::UnexpectedCharacter { position, .. } => *position,
285            Self::UnexpectedEof { position } => *position,
286            Self::InvalidIri { position, .. } => *position,
287            Self::InvalidLanguageTag { position, .. } => *position,
288            Self::InvalidLiteral { position, .. } => *position,
289            Self::InvalidEscape { position, .. } => *position,
290            Self::InvalidUnicode { position, .. } => *position,
291            Self::InvalidBlankNode { position, .. } => *position,
292            Self::UndefinedPrefix { position, .. } => *position,
293            Self::InvalidPrefix { position, .. } => *position,
294            Self::InvalidBase { position, .. } => *position,
295            Self::Generic { position, .. } => *position,
296        }
297    }
298}