Skip to main content

oak_core/errors/
mod.rs

1use crate::source::SourceId;
2
3mod display;
4mod from_std;
5mod source;
6
7/// Result type for lexical analysis operations.
8///
9/// This type alias represents the result of tokenization operations,
10/// where successful operations return a value of type `T` and failed
11/// operations return an [`OakError`].
12pub type LexResult<T> = Result<T, OakError>;
13
14/// Result type for parsing operations.
15///
16/// This type alias represents the result of parsing operations,
17/// where successful operations return a value of type `T` and failed
18/// operations return an [`OakError`].
19pub type ParseResult<T> = Result<T, OakError>;
20
21/// Container for parsing results with associated diagnostics.
22///
23/// This struct holds both the primary result of a parsing operation
24/// and any diagnostic language that were encountered during parsing.
25/// This allows for error recovery where parsing can continue even
26/// after encountering language, collecting all issues for later analysis.
27
28#[derive(Debug, Clone)]
29pub struct OakDiagnostics<T> {
30    /// The primary result of the parsing operation.
31    /// May contain either a successful value or a fatal error.
32    pub result: Result<T, OakError>,
33    /// A collection of non-fatal errors or warnings encountered during the operation.
34    pub diagnostics: Vec<OakError>,
35}
36
37/// The main error type for the Oak Core parsing framework.
38///
39/// `OakError` represents all possible language that can occur during
40/// lexical analysis and parsing operations. It provides detailed
41/// error information including error kind and/// precise source location.
42#[derive(Clone)]
43pub struct OakError {
44    kind: Box<OakErrorKind>,
45}
46
47impl std::fmt::Debug for OakError {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        std::fmt::Display::fmt(self, f)
50    }
51}
52
53#[cfg(feature = "serde")]
54impl serde::ser::Error for OakError {
55    fn custom<T: std::fmt::Display>(msg: T) -> Self {
56        OakError::serde_error(msg.to_string())
57    }
58}
59
60#[cfg(feature = "serde")]
61impl serde::de::Error for OakError {
62    fn custom<T: std::fmt::Display>(msg: T) -> Self {
63        OakError::deserialize_error(msg.to_string())
64    }
65}
66
67/// Enumeration of all possible error kinds in the Oak Core framework.
68///
69/// This enum categorizes different types of language that can occur
70/// during parsing operations, each with specific associated data
71/// relevant to that error type.
72#[derive(Debug)]
73pub enum OakErrorKind {
74    /// I/O error that occurred while reading source files.
75    IoError {
76        /// The underlying I/O error.
77        error: std::io::Error,
78        /// Optional source ID of the file that caused the error.
79        source_id: Option<SourceId>,
80    },
81    /// Syntax error encountered during parsing.
82    SyntaxError {
83        /// Human-readable error message describing the kind issue.
84        message: String,
85        /// The byte offset where the error occurred.
86        offset: usize,
87        /// Optional source ID of the file that caused the error.
88        source_id: Option<SourceId>,
89    },
90    /// Unexpected character encountered during lexical analysis.
91    UnexpectedCharacter {
92        /// The character that was not expected at this position.
93        character: char,
94        /// The byte offset where the unexpected character was found.
95        offset: usize,
96        /// Optional source ID of the file that caused the error.
97        source_id: Option<SourceId>,
98    },
99
100    /// Unexpected token encountered during parsing.
101    UnexpectedToken {
102        /// The token that was not expected.
103        token: String,
104        /// The byte offset where the unexpected token was found.
105        offset: usize,
106        /// Optional source ID of the file that caused the error.
107        source_id: Option<SourceId>,
108    },
109
110    /// Unexpected end of file encountered during parsing.
111    UnexpectedEof {
112        /// The byte offset where the EOF was encountered.
113        offset: usize,
114        /// Optional source ID of the file that caused the error.
115        source_id: Option<SourceId>,
116    },
117
118    /// Custom error for user-defined error conditions.
119    CustomError {
120        /// The error message describing the custom error condition.
121        message: String,
122    },
123
124    /// Invalid theme error for highlighting.
125    InvalidTheme {
126        /// The error message.
127        message: String,
128    },
129
130    /// Unsupported format error for exporting.
131    UnsupportedFormat {
132        /// The unsupported format.
133        format: String,
134    },
135
136    /// Color parsing error for themes.
137    ColorParseError {
138        /// The invalid color string.
139        color: String,
140    },
141
142    /// Formatting error.
143    FormatError {
144        /// The error message.
145        message: String,
146    },
147
148    /// Semantic error.
149    SemanticError {
150        /// The error message.
151        message: String,
152    },
153
154    /// Protocol error (e.g., MCP, LSP).
155    ProtocolError {
156        /// The error message.
157        message: String,
158    },
159
160    /// Expected a specific token.
161    ExpectedToken {
162        /// The token that was expected.
163        expected: String,
164        /// The byte offset where the error occurred.
165        offset: usize,
166        /// Optional source ID of the file that caused the error.
167        source_id: Option<SourceId>,
168    },
169
170    /// Expected a name (identifier).
171    ExpectedName {
172        /// The kind of name that was expected (e.g., "function name").
173        name_kind: String,
174        /// The byte offset where the error occurred.
175        offset: usize,
176        /// Optional source ID of the file that caused the error.
177        source_id: Option<SourceId>,
178    },
179
180    /// Trailing comma is not allowed.
181    TrailingCommaNotAllowed {
182        /// The byte offset where the error occurred.
183        offset: usize,
184        /// Optional source ID of the file that caused the error.
185        source_id: Option<SourceId>,
186    },
187
188    /// Test failure error.
189    TestFailure {
190        /// The file that failed the test.
191        path: std::path::PathBuf,
192        /// The expected output.
193        expected: String,
194        /// The actual output.
195        actual: String,
196    },
197
198    /// Test expected result file was missing or regenerated.
199    TestRegenerated {
200        /// The file that was regenerated.
201        path: std::path::PathBuf,
202    },
203
204    /// Serde serialization error.
205    SerdeError {
206        /// The error message.
207        message: String,
208    },
209
210    /// Serde deserialization error.
211    DeserializeError {
212        /// The error message.
213        message: String,
214    },
215
216    /// XML writing error.
217    XmlError {
218        /// The error message.
219        message: String,
220    },
221
222    /// Zip archive error.
223    ZipError {
224        /// The error message.
225        message: String,
226    },
227
228    /// Parsing error (generic).
229    ParseError {
230        /// The error message.
231        message: String,
232    },
233
234    /// Internal error.
235    InternalError {
236        /// The error message.
237        message: String,
238    },
239}
240
241impl OakErrorKind {
242    /// Gets the i18n key for this error kind.
243    pub fn key(&self) -> &'static str {
244        match self {
245            OakErrorKind::IoError { .. } => "error.io",
246            OakErrorKind::SyntaxError { .. } => "error.syntax",
247            OakErrorKind::UnexpectedCharacter { .. } => "error.unexpected_character",
248            OakErrorKind::UnexpectedToken { .. } => "error.unexpected_token",
249            OakErrorKind::UnexpectedEof { .. } => "error.unexpected_eof",
250            OakErrorKind::CustomError { .. } => "error.custom",
251            OakErrorKind::InvalidTheme { .. } => "error.invalid_theme",
252            OakErrorKind::UnsupportedFormat { .. } => "error.unsupported_format",
253            OakErrorKind::ColorParseError { .. } => "error.color_parse",
254            OakErrorKind::FormatError { .. } => "error.format",
255            OakErrorKind::SemanticError { .. } => "error.semantic",
256            OakErrorKind::ProtocolError { .. } => "error.protocol",
257            OakErrorKind::ExpectedToken { .. } => "error.expected_token",
258            OakErrorKind::ExpectedName { .. } => "error.expected_name",
259            OakErrorKind::TrailingCommaNotAllowed { .. } => "error.trailing_comma_not_allowed",
260            OakErrorKind::TestFailure { .. } => "error.test_failure",
261            OakErrorKind::TestRegenerated { .. } => "error.test_regenerated",
262            OakErrorKind::SerdeError { .. } => "error.serde",
263            OakErrorKind::DeserializeError { .. } => "error.deserialize",
264            OakErrorKind::XmlError { .. } => "error.xml",
265            OakErrorKind::ZipError { .. } => "error.zip",
266            OakErrorKind::ParseError { .. } => "error.parse",
267            OakErrorKind::InternalError { .. } => "error.internal",
268        }
269    }
270}
271
272impl OakError {
273    /// Gets the kind of this error.
274    pub fn kind(&self) -> &OakErrorKind {
275        &self.kind
276    }
277
278    /// Creates a test failure error.
279    pub fn test_failure(path: std::path::PathBuf, expected: String, actual: String) -> Self {
280        OakErrorKind::TestFailure { path, expected, actual }.into()
281    }
282
283    /// Creates a test regenerated error.
284    pub fn test_regenerated(path: std::path::PathBuf) -> Self {
285        OakErrorKind::TestRegenerated { path }.into()
286    }
287
288    /// Creates an I/O error with optional Source ID.
289    ///
290    /// # Arguments
291    ///
292    /// * `error` - The underlying I/O error
293    /// * `source_id` - Source ID of the file that caused the error
294    ///
295    /// # Examples
296    ///
297    /// ```rust
298    /// # use oak_core::OakError;
299    /// # use std::io;
300    ///
301    /// let io_err = io::Error::new(io::ErrorKind::NotFound, "File not found");
302    /// let error = OakError::io_error(io_err, 1);
303    /// ```
304
305    pub fn io_error(error: std::io::Error, source_id: SourceId) -> Self {
306        OakErrorKind::IoError { error, source_id: Some(source_id) }.into()
307    }
308
309    /// Creates a kind error with a message and location.
310    ///
311    /// # Arguments
312    ///
313    /// * `message` - Description of the kind error
314    /// * `offset` - The byte offset where the error occurred
315    /// * `source_id` - Optional source ID of the file that caused the error
316    ///
317    /// # Examples
318    ///
319    /// ```rust
320    /// # use oak_core::OakError;
321    ///
322    /// let error = OakError::syntax_error("Unexpected token", 5, None);
323    /// ```
324    pub fn syntax_error(message: impl Into<String>, offset: usize, source_id: Option<SourceId>) -> Self {
325        OakErrorKind::SyntaxError { message: message.into(), offset, source_id }.into()
326    }
327
328    /// Creates an unexpected character error.
329    ///
330    /// # Arguments
331    ///
332    /// * `character` - The unexpected character
333    /// * `offset` - The byte offset where the character was found
334    /// * `source_id` - Optional source ID of the file that caused the error
335    ///
336    /// # Examples
337    ///
338    /// ```rust
339    /// # use oak_core::OakError;
340    ///
341    /// let error = OakError::unexpected_character('$', 0, None);
342    /// ```
343    pub fn unexpected_character(character: char, offset: usize, source_id: Option<SourceId>) -> Self {
344        OakErrorKind::UnexpectedCharacter { character, offset, source_id }.into()
345    }
346
347    /// Creates an unexpected token error.
348    pub fn unexpected_token(token: impl Into<String>, offset: usize, source_id: Option<SourceId>) -> Self {
349        OakErrorKind::UnexpectedToken { token: token.into(), offset, source_id }.into()
350    }
351
352    /// Creates an unexpected end of file error.
353    pub fn unexpected_eof(offset: usize, source_id: Option<SourceId>) -> Self {
354        OakErrorKind::UnexpectedEof { offset, source_id }.into()
355    }
356
357    /// Creates an expected token error.
358    pub fn expected_token(expected: impl Into<String>, offset: usize, source_id: Option<SourceId>) -> Self {
359        OakErrorKind::ExpectedToken { expected: expected.into(), offset, source_id }.into()
360    }
361
362    /// Creates an expected name error.
363    pub fn expected_name(name_kind: impl Into<String>, offset: usize, source_id: Option<SourceId>) -> Self {
364        OakErrorKind::ExpectedName { name_kind: name_kind.into(), offset, source_id }.into()
365    }
366
367    /// Creates a trailing comma not allowed error.
368    pub fn trailing_comma_not_allowed(offset: usize, source_id: Option<SourceId>) -> Self {
369        OakErrorKind::TrailingCommaNotAllowed { offset, source_id }.into()
370    }
371
372    /// Creates a custom error for user-defined error conditions.with a message.
373    ///
374    /// # Arguments
375    ///
376    /// * `message` - The error message describing what went wrong
377    ///
378    /// # Examples
379    ///
380    /// ```rust
381    /// # use oak_core::OakError;
382    ///
383    /// let error = OakError::custom_error("Invalid configuration");
384    /// ```
385    pub fn custom_error(message: impl Into<String>) -> Self {
386        OakErrorKind::CustomError { message: message.into() }.into()
387    }
388
389    /// Creates an invalid theme error.
390    pub fn invalid_theme(message: impl Into<String>) -> Self {
391        OakErrorKind::InvalidTheme { message: message.into() }.into()
392    }
393
394    /// Creates an unsupported format error.
395    pub fn unsupported_format(format: impl Into<String>) -> Self {
396        OakErrorKind::UnsupportedFormat { format: format.into() }.into()
397    }
398
399    /// Creates a color parsing error.
400    pub fn color_parse_error(color: impl Into<String>) -> Self {
401        OakErrorKind::ColorParseError { color: color.into() }.into()
402    }
403
404    /// Creates a formatting error.
405    pub fn format_error(message: impl Into<String>) -> Self {
406        OakErrorKind::FormatError { message: message.into() }.into()
407    }
408
409    /// Creates a semantic error.
410    pub fn semantic_error(message: impl Into<String>) -> Self {
411        OakErrorKind::SemanticError { message: message.into() }.into()
412    }
413
414    /// Creates a protocol error.
415    pub fn protocol_error(message: impl Into<String>) -> Self {
416        OakErrorKind::ProtocolError { message: message.into() }.into()
417    }
418
419    /// Creates a serde serialization error.
420    pub fn serde_error(message: impl Into<String>) -> Self {
421        OakErrorKind::SerdeError { message: message.into() }.into()
422    }
423
424    /// Creates a serde deserialization error.
425    pub fn deserialize_error(message: impl Into<String>) -> Self {
426        OakErrorKind::DeserializeError { message: message.into() }.into()
427    }
428
429    /// Creates an XML error.
430    pub fn xml_error(message: impl Into<String>) -> Self {
431        OakErrorKind::XmlError { message: message.into() }.into()
432    }
433
434    /// Creates a zip error.
435    pub fn zip_error(message: impl Into<String>) -> Self {
436        OakErrorKind::ZipError { message: message.into() }.into()
437    }
438
439    /// Creates a parse error.
440    pub fn parse_error(message: impl Into<String>) -> Self {
441        OakErrorKind::ParseError { message: message.into() }.into()
442    }
443
444    /// Creates an internal error.
445    pub fn internal_error(message: impl Into<String>) -> Self {
446        OakErrorKind::InternalError { message: message.into() }.into()
447    }
448
449    /// Attach a source ID to the error context.
450    pub fn with_source_id(mut self, source_id: SourceId) -> Self {
451        match self.kind.as_mut() {
452            OakErrorKind::IoError { source_id: u, .. } => *u = Some(source_id),
453            OakErrorKind::SyntaxError { source_id: u, .. } => *u = Some(source_id),
454            OakErrorKind::UnexpectedCharacter { source_id: u, .. } => *u = Some(source_id),
455            OakErrorKind::UnexpectedToken { source_id: u, .. } => *u = Some(source_id),
456            OakErrorKind::ExpectedToken { source_id: u, .. } => *u = Some(source_id),
457            OakErrorKind::ExpectedName { source_id: u, .. } => *u = Some(source_id),
458            OakErrorKind::TrailingCommaNotAllowed { source_id: u, .. } => *u = Some(source_id),
459            _ => {}
460        }
461        self
462    }
463}
464
465impl Clone for OakErrorKind {
466    fn clone(&self) -> Self {
467        match self {
468            OakErrorKind::IoError { error, source_id } => {
469                // Since std::io::Error doesn't support Clone, we create a new error
470                let new_error = std::io::Error::new(error.kind(), error.to_string());
471                OakErrorKind::IoError { error: new_error, source_id: *source_id }
472            }
473            OakErrorKind::SyntaxError { message, offset, source_id } => OakErrorKind::SyntaxError { message: message.clone(), offset: *offset, source_id: *source_id },
474            OakErrorKind::UnexpectedCharacter { character, offset, source_id } => OakErrorKind::UnexpectedCharacter { character: *character, offset: *offset, source_id: *source_id },
475            OakErrorKind::UnexpectedToken { token, offset, source_id } => OakErrorKind::UnexpectedToken { token: token.clone(), offset: *offset, source_id: *source_id },
476            OakErrorKind::UnexpectedEof { offset, source_id } => OakErrorKind::UnexpectedEof { offset: *offset, source_id: *source_id },
477            OakErrorKind::ExpectedToken { expected, offset, source_id } => OakErrorKind::ExpectedToken { expected: expected.clone(), offset: *offset, source_id: *source_id },
478            OakErrorKind::ExpectedName { name_kind, offset, source_id } => OakErrorKind::ExpectedName { name_kind: name_kind.clone(), offset: *offset, source_id: *source_id },
479            OakErrorKind::TrailingCommaNotAllowed { offset, source_id } => OakErrorKind::TrailingCommaNotAllowed { offset: *offset, source_id: *source_id },
480            OakErrorKind::CustomError { message } => OakErrorKind::CustomError { message: message.clone() },
481            OakErrorKind::InvalidTheme { message } => OakErrorKind::InvalidTheme { message: message.clone() },
482            OakErrorKind::UnsupportedFormat { format } => OakErrorKind::UnsupportedFormat { format: format.clone() },
483            OakErrorKind::ColorParseError { color } => OakErrorKind::ColorParseError { color: color.clone() },
484            OakErrorKind::FormatError { message } => OakErrorKind::FormatError { message: message.clone() },
485            OakErrorKind::SemanticError { message } => OakErrorKind::SemanticError { message: message.clone() },
486            OakErrorKind::ProtocolError { message } => OakErrorKind::ProtocolError { message: message.clone() },
487            OakErrorKind::TestFailure { path, expected, actual } => OakErrorKind::TestFailure { path: path.clone(), expected: expected.clone(), actual: actual.clone() },
488            OakErrorKind::TestRegenerated { path } => OakErrorKind::TestRegenerated { path: path.clone() },
489            OakErrorKind::SerdeError { message } => OakErrorKind::SerdeError { message: message.clone() },
490            OakErrorKind::DeserializeError { message } => OakErrorKind::DeserializeError { message: message.clone() },
491            OakErrorKind::XmlError { message } => OakErrorKind::XmlError { message: message.clone() },
492            OakErrorKind::ZipError { message } => OakErrorKind::ZipError { message: message.clone() },
493            OakErrorKind::ParseError { message } => OakErrorKind::ParseError { message: message.clone() },
494            OakErrorKind::InternalError { message } => OakErrorKind::InternalError { message: message.clone() },
495        }
496    }
497}