Skip to main content

oak_core/errors/
mod.rs

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