Skip to main content

oak_core/errors/
mod.rs

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