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