oak_core/errors/
mod.rs

1use url::Url;
2
3mod display;
4#[cfg(feature = "serde_json")]
5mod from_serde_json;
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, Clone)]
31pub struct OakDiagnostics<T> {
32    /// The primary result of the parsing operation.
33    /// May contain either a successful value or a fatal error.
34    pub result: Result<T, OakError>,
35    /// A collection of non-fatal errors or warnings encountered during the operation.
36    pub diagnostics: Vec<OakError>,
37}
38
39/// The main error type for the Oak Core parsing framework.
40///
41/// `OakError` represents all possible language that can occur during
42/// lexical analysis and parsing operations. It provides detailed
43/// error information including error kind and/// precise source location.
44#[derive(Clone)]
45pub struct OakError {
46    kind: Box<OakErrorKind>,
47}
48
49impl std::fmt::Debug for OakError {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        std::fmt::Display::fmt(self, f)
52    }
53}
54
55/// Enumeration of all possible error kinds in the Oak Core framework.
56///
57/// This enum categorizes different types of language that can occur
58/// during parsing operations, each with specific associated data
59/// relevant to that error type.
60#[derive(Debug)]
61pub enum OakErrorKind {
62    /// I/O error that occurred while reading source files.
63    IoError {
64        /// The underlying I/O error.
65        error: std::io::Error,
66        /// Optional URL of the file that caused the error.
67        url: Option<Url>,
68    },
69    /// Syntax error encountered during parsing.
70    SyntaxError {
71        /// Human-readable error message describing the kind issue.
72        message: String,
73        /// The byte offset where the error occurred.
74        offset: usize,
75        /// Optional URL of the file that caused the error.
76        url: Option<Url>,
77    },
78    /// Unexpected character encountered during lexical analysis.
79    UnexpectedCharacter {
80        /// The character that was not expected at this position.
81        character: char,
82        /// The byte offset where the unexpected character was found.
83        offset: usize,
84        /// Optional URL of the file that caused the error.
85        url: Option<Url>,
86    },
87
88    /// Unexpected token encountered during parsing.
89    UnexpectedToken {
90        /// The token that was not expected.
91        token: String,
92        /// The byte offset where the unexpected token was found.
93        offset: usize,
94        /// Optional URL of the file that caused the error.
95        url: Option<Url>,
96    },
97
98    /// Unexpected end of file encountered during parsing.
99    UnexpectedEof {
100        /// The byte offset where the EOF was encountered.
101        offset: usize,
102        /// Optional URL of the file that caused the error.
103        url: Option<Url>,
104    },
105
106    /// Custom error for user-defined error conditions.
107    CustomError {
108        /// The error message describing the custom error condition.
109        message: String,
110    },
111
112    /// Invalid theme error for highlighting.
113    InvalidTheme {
114        /// The error message.
115        message: String,
116    },
117
118    /// Unsupported format error for exporting.
119    UnsupportedFormat {
120        /// The unsupported format.
121        format: String,
122    },
123
124    /// Color parsing error for themes.
125    ColorParseError {
126        /// The invalid color string.
127        color: String,
128    },
129
130    /// Formatting error.
131    FormatError {
132        /// The error message.
133        message: String,
134    },
135
136    /// Semantic error.
137    SemanticError {
138        /// The error message.
139        message: String,
140    },
141
142    /// Protocol error (e.g., MCP, LSP).
143    ProtocolError {
144        /// The error message.
145        message: String,
146    },
147
148    /// Expected a specific token.
149    ExpectedToken {
150        /// The token that was expected.
151        expected: String,
152        /// The byte offset where the error occurred.
153        offset: usize,
154        /// Optional URL of the file that caused the error.
155        url: Option<Url>,
156    },
157
158    /// Expected a name (identifier).
159    ExpectedName {
160        /// The kind of name that was expected (e.g., "function name").
161        name_kind: String,
162        /// The byte offset where the error occurred.
163        offset: usize,
164        /// Optional URL of the file that caused the error.
165        url: Option<Url>,
166    },
167
168    /// Trailing comma is not allowed.
169    TrailingCommaNotAllowed {
170        /// The byte offset where the error occurred.
171        offset: usize,
172        /// Optional URL of the file that caused the error.
173        url: Option<Url>,
174    },
175
176    /// Test failure error.
177    TestFailure {
178        /// The file that failed the test.
179        path: std::path::PathBuf,
180        /// The expected output.
181        expected: String,
182        /// The actual output.
183        actual: String,
184    },
185
186    /// Test expected result file was missing or regenerated.
187    TestRegenerated {
188        /// The file that was regenerated.
189        path: std::path::PathBuf,
190    },
191}
192
193impl OakErrorKind {
194    /// Gets the i18n key for this error kind.
195    pub fn key(&self) -> &'static str {
196        match self {
197            OakErrorKind::IoError { .. } => "error.io",
198            OakErrorKind::SyntaxError { .. } => "error.syntax",
199            OakErrorKind::UnexpectedCharacter { .. } => "error.unexpected_character",
200            OakErrorKind::UnexpectedToken { .. } => "error.unexpected_token",
201            OakErrorKind::UnexpectedEof { .. } => "error.unexpected_eof",
202            OakErrorKind::CustomError { .. } => "error.custom",
203            OakErrorKind::InvalidTheme { .. } => "error.invalid_theme",
204            OakErrorKind::UnsupportedFormat { .. } => "error.unsupported_format",
205            OakErrorKind::ColorParseError { .. } => "error.color_parse",
206            OakErrorKind::FormatError { .. } => "error.format",
207            OakErrorKind::SemanticError { .. } => "error.semantic",
208            OakErrorKind::ProtocolError { .. } => "error.protocol",
209            OakErrorKind::ExpectedToken { .. } => "error.expected_token",
210            OakErrorKind::ExpectedName { .. } => "error.expected_name",
211            OakErrorKind::TrailingCommaNotAllowed { .. } => "error.trailing_comma_not_allowed",
212            OakErrorKind::TestFailure { .. } => "error.test_failure",
213            OakErrorKind::TestRegenerated { .. } => "error.test_regenerated",
214        }
215    }
216}
217
218impl OakError {
219    /// Creates a test failure error.
220    pub fn test_failure(path: std::path::PathBuf, expected: String, actual: String) -> Self {
221        OakErrorKind::TestFailure { path, expected, actual }.into()
222    }
223
224    /// Creates a test regenerated error.
225    pub fn test_regenerated(path: std::path::PathBuf) -> Self {
226        OakErrorKind::TestRegenerated { path }.into()
227    }
228
229    /// Creates an I/O error with optional file URL.
230    ///
231    /// # Arguments
232    ///
233    /// * `error` - The underlying I/O error
234    /// * `url` - URL of the file that caused the error
235    ///
236    /// # Examples
237    ///
238    /// ```rust
239    /// # use oak_core::OakError;
240    /// # use std::io;
241    ///
242    /// let io_err = io::Error::new(io::ErrorKind::NotFound, "File not found");
243    /// let error = OakError::io_error(io_err, url::Url::parse("file:///main.rs").unwrap());
244    /// ```
245
246    pub fn io_error(error: std::io::Error, url: Url) -> Self {
247        OakErrorKind::IoError { error, url: Some(url) }.into()
248    }
249
250    /// Creates a kind error with a message and location.
251    ///
252    /// # Arguments
253    ///
254    /// * `message` - Description of the kind error
255    /// * `offset` - The byte offset where the error occurred
256    /// * `url` - Optional URL of the file that caused the error
257    ///
258    /// # Examples
259    ///
260    /// ```rust
261    /// # use oak_core::OakError;
262    ///
263    /// let error = OakError::syntax_error("Unexpected token", 5, None);
264    /// ```
265    pub fn syntax_error(message: impl Into<String>, offset: usize, url: Option<Url>) -> Self {
266        OakErrorKind::SyntaxError { message: message.into(), offset, url }.into()
267    }
268
269    /// Creates an unexpected character error.
270    ///
271    /// # Arguments
272    ///
273    /// * `character` - The unexpected character
274    /// * `offset` - The byte offset where the character was found
275    /// * `url` - Optional URL of the file that caused the error
276    ///
277    /// # Examples
278    ///
279    /// ```rust
280    /// # use oak_core::OakError;
281    ///
282    /// let error = OakError::unexpected_character('$', 0, None);
283    /// ```
284    pub fn unexpected_character(character: char, offset: usize, url: Option<Url>) -> Self {
285        OakErrorKind::UnexpectedCharacter { character, offset, url }.into()
286    }
287
288    /// Creates an unexpected token error.
289    pub fn unexpected_token(token: impl Into<String>, offset: usize, url: Option<Url>) -> Self {
290        OakErrorKind::UnexpectedToken { token: token.into(), offset, url }.into()
291    }
292
293    /// Creates an unexpected end of file error.
294    pub fn unexpected_eof(offset: usize, url: Option<Url>) -> Self {
295        OakErrorKind::UnexpectedEof { offset, url }.into()
296    }
297
298    /// Creates an expected token error.
299    pub fn expected_token(expected: impl Into<String>, offset: usize, url: Option<Url>) -> Self {
300        OakErrorKind::ExpectedToken { expected: expected.into(), offset, url }.into()
301    }
302
303    /// Creates an expected name error.
304    pub fn expected_name(name_kind: impl Into<String>, offset: usize, url: Option<Url>) -> Self {
305        OakErrorKind::ExpectedName { name_kind: name_kind.into(), offset, url }.into()
306    }
307
308    /// Creates a trailing comma not allowed error.
309    pub fn trailing_comma_not_allowed(offset: usize, url: Option<Url>) -> Self {
310        OakErrorKind::TrailingCommaNotAllowed { offset, url }.into()
311    }
312
313    /// Creates a custom error for user-defined error conditions.with a message.
314    ///
315    /// # Arguments
316    ///
317    /// * `message` - The error message describing what went wrong
318    ///
319    /// # Examples
320    ///
321    /// ```rust
322    /// # use oak_core::OakError;
323    ///
324    /// let error = OakError::custom_error("Invalid configuration");
325    /// ```
326    pub fn custom_error(message: impl Into<String>) -> Self {
327        OakErrorKind::CustomError { message: message.into() }.into()
328    }
329
330    /// Creates an invalid theme error.
331    pub fn invalid_theme(message: impl Into<String>) -> Self {
332        OakErrorKind::InvalidTheme { message: message.into() }.into()
333    }
334
335    /// Creates an unsupported format error.
336    pub fn unsupported_format(format: impl Into<String>) -> Self {
337        OakErrorKind::UnsupportedFormat { format: format.into() }.into()
338    }
339
340    /// Creates a color parsing error.
341    pub fn color_parse_error(color: impl Into<String>) -> Self {
342        OakErrorKind::ColorParseError { color: color.into() }.into()
343    }
344
345    /// Creates a formatting error.
346    pub fn format_error(message: impl Into<String>) -> Self {
347        OakErrorKind::FormatError { message: message.into() }.into()
348    }
349
350    /// Creates a semantic error.
351    pub fn semantic_error(message: impl Into<String>) -> Self {
352        OakErrorKind::SemanticError { message: message.into() }.into()
353    }
354
355    /// Creates a protocol error.
356    pub fn protocol_error(message: impl Into<String>) -> Self {
357        OakErrorKind::ProtocolError { message: message.into() }.into()
358    }
359
360    /// Returns a reference to the error kind.
361    ///
362    /// # Returns
363    ///
364    /// A reference to the [`OakErrorKind`] enum that categorizes this error.
365    pub fn kind(&self) -> &OakErrorKind {
366        &self.kind
367    }
368
369    /// Attach a URL to the error context.
370    pub fn with_url(mut self, url: Url) -> Self {
371        match self.kind.as_mut() {
372            OakErrorKind::IoError { url: u, .. } => *u = Some(url),
373            OakErrorKind::SyntaxError { url: u, .. } => *u = Some(url),
374            OakErrorKind::UnexpectedCharacter { url: u, .. } => *u = Some(url),
375            OakErrorKind::UnexpectedToken { url: u, .. } => *u = Some(url),
376            OakErrorKind::ExpectedToken { url: u, .. } => *u = Some(url),
377            OakErrorKind::ExpectedName { url: u, .. } => *u = Some(url),
378            OakErrorKind::TrailingCommaNotAllowed { url: u, .. } => *u = Some(url),
379            _ => {}
380        }
381        self
382    }
383}
384
385impl Clone for OakErrorKind {
386    fn clone(&self) -> Self {
387        match self {
388            OakErrorKind::IoError { error, url } => {
389                // Since std::io::Error doesn't support Clone, we create a new error
390                let new_error = std::io::Error::new(error.kind(), error.to_string());
391                OakErrorKind::IoError { error: new_error, url: url.clone() }
392            }
393            OakErrorKind::SyntaxError { message, offset, url } => OakErrorKind::SyntaxError { message: message.clone(), offset: *offset, url: url.clone() },
394            OakErrorKind::UnexpectedCharacter { character, offset, url } => OakErrorKind::UnexpectedCharacter { character: *character, offset: *offset, url: url.clone() },
395            OakErrorKind::UnexpectedToken { token, offset, url } => OakErrorKind::UnexpectedToken { token: token.clone(), offset: *offset, url: url.clone() },
396            OakErrorKind::UnexpectedEof { offset, url } => OakErrorKind::UnexpectedEof { offset: *offset, url: url.clone() },
397            OakErrorKind::ExpectedToken { expected, offset, url } => OakErrorKind::ExpectedToken { expected: expected.clone(), offset: *offset, url: url.clone() },
398            OakErrorKind::ExpectedName { name_kind, offset, url } => OakErrorKind::ExpectedName { name_kind: name_kind.clone(), offset: *offset, url: url.clone() },
399            OakErrorKind::TrailingCommaNotAllowed { offset, url } => OakErrorKind::TrailingCommaNotAllowed { offset: *offset, url: url.clone() },
400            OakErrorKind::CustomError { message } => OakErrorKind::CustomError { message: message.clone() },
401            OakErrorKind::InvalidTheme { message } => OakErrorKind::InvalidTheme { message: message.clone() },
402            OakErrorKind::UnsupportedFormat { format } => OakErrorKind::UnsupportedFormat { format: format.clone() },
403            OakErrorKind::ColorParseError { color } => OakErrorKind::ColorParseError { color: color.clone() },
404            OakErrorKind::FormatError { message } => OakErrorKind::FormatError { message: message.clone() },
405            OakErrorKind::SemanticError { message } => OakErrorKind::SemanticError { message: message.clone() },
406            OakErrorKind::ProtocolError { message } => OakErrorKind::ProtocolError { message: message.clone() },
407            OakErrorKind::TestFailure { path, expected, actual } => OakErrorKind::TestFailure { path: path.clone(), expected: expected.clone(), actual: actual.clone() },
408            OakErrorKind::TestRegenerated { path } => OakErrorKind::TestRegenerated { path: path.clone() },
409        }
410    }
411}