mon_core/
error.rs

1//! # Error Handling in `mon-core`
2//!
3//! This module defines the error types used throughout the `mon-core` library. The error
4//! handling strategy is built around the [`miette`] crate for rich, diagnostic-style
5//! error reporting, and [`thiserror`] for ergonomic error type definitions.
6//!
7//! ## Architectural Overview
8//!
9//! The error system is hierarchical:
10//!
11//! 1.  **[`MonError`]**: This is the top-level, public-facing error enum. It wraps more
12//!     specific error types from different stages of the compilation pipeline.
13//!     Any function in the public API will typically return a `Result<T, MonError>`.
14//!
15//! 2.  **Phase-Specific Errors:**
16//!     - [`ParserError`]: Errors that occur during the lexing or parsing phase, such as
17//!       syntax errors (e.g., an unexpected token).
18//!     - [`ResolverError`]: Errors that occur during the semantic analysis phase. This includes
19//!       failures like an import not being found, an anchor being undefined, or a circular dependency.
20//!
21//! 3.  **[`ValidationError`]**: A specialized subset of resolver errors that occur specifically
22//!     during type validation. This includes type mismatches, missing or extra fields in structs,
23//!     and undefined enum variants.
24//!
25//! ## Use Cases
26//!
27//! When you use the `mon-core` library, you will primarily interact with `MonError`. You can
28//! match on its variants (`Parser` or `Resolver`) to determine the source of the failure.
29//!
30//! The rich diagnostic information provided by `miette` allows for printing user-friendly,
31//! colorful error reports that point directly to the problematic code in the source file.
32//!
33//! ## Example: Handling an Error
34//!
35//! ```rust
36//! use mon_core::api::analyze;
37//!
38//! // This source code has a syntax error (a missing comma).
39//! let source = "{ key1: \"value1\" key2: \"value2\" }";
40//!
41//! let result = analyze(source, "bad.mon");
42//!
43//! match result {
44//!     Ok(_) => println!("This should not have succeeded!"),
45//!     Err(err) => {
46//!         // You can render the beautiful, diagnostic error report.
47//!         // (Note: The actual output is a graphical report with colors and source snippets)
48//!         println!("{:?}", err);
49//!
50//!         // You can also programmatically inspect the error.
51//!         match err {
52//!             mon_core::error::MonError::Parser(p_err) => {
53//!                 println!("A parser error occurred!");
54//!             },
55//!             _ => {}
56//!         }
57//!     }
58//! }
59//! ```
60use miette::{Diagnostic, NamedSource, SourceSpan};
61use std::sync::Arc;
62use thiserror::Error;
63
64/// The primary error type for the `mon-core` library.
65#[derive(Error, Debug, Diagnostic, Clone)]
66pub enum MonError {
67    /// An error that occurred during the parsing phase.
68    #[error(transparent)]
69    #[diagnostic(transparent)]
70    #[source_code]
71    Parser(Box<ParserError>),
72
73    /// An error that occurred during the resolution or validation phase.
74    #[error(transparent)]
75    #[diagnostic(transparent)]
76    #[source_code]
77    Resolver(Box<ResolverError>),
78}
79
80impl From<ParserError> for MonError {
81    fn from(err: ParserError) -> Self {
82        MonError::Parser(Box::new(err))
83    }
84}
85
86impl From<ResolverError> for MonError {
87    fn from(err: ResolverError) -> Self {
88        MonError::Resolver(Box::new(err))
89    }
90}
91
92/// An error that occurred during the parsing phase.
93#[derive(Error, Debug, Diagnostic, Clone)]
94#[error("Parser Error")]
95pub enum ParserError {
96    /// An unexpected token was found.
97    #[error("Unexpected token")]
98    #[diagnostic(
99        code(parser::unexpected_token),
100        help("The parser found a token it did not expect in this position.")
101    )]
102    UnexpectedToken {
103        #[source_code]
104        src: Arc<NamedSource<String>>,
105        #[label("Expected {expected}, but found this")]
106        span: SourceSpan,
107        expected: String,
108    },
109
110    /// The end of the file was reached unexpectedly.
111    #[error("Unexpected end of file")]
112    #[diagnostic(
113        code(parser::unexpected_eof),
114        help("The file ended unexpectedly. The parser expected more tokens.")
115    )]
116    UnexpectedEof {
117        #[source_code]
118        src: Arc<NamedSource<String>>,
119        #[label("File ended unexpectedly here")]
120        span: SourceSpan,
121    },
122
123    /// A specific token was expected but not found.
124    #[error("Missing expected token")]
125    #[diagnostic(
126        code(parser::missing_expected_token),
127        help("The parser expected a specific token that was not found.")
128    )]
129    MissingExpectedToken {
130        #[source_code]
131        src: Arc<NamedSource<String>>,
132        #[label("Expected {expected} here")]
133        span: SourceSpan,
134        expected: String,
135    },
136}
137/// An error that occurred during the resolution or validation phase.
138#[derive(Error, Debug, Diagnostic, Clone)]
139#[error("Resolver Error")]
140pub enum ResolverError {
141    /// An imported module could not be found.
142    #[error("Module not found at path: {path}")]
143    #[diagnostic(
144        code(resolver::module_not_found),
145        help("Check that the path is correct and the file exists.")
146    )]
147    ModuleNotFound {
148        path: String,
149        #[source_code]
150        src: Arc<NamedSource<String>>,
151        #[label("...referenced here")]
152        span: SourceSpan,
153    },
154
155    /// An anchor referenced by an alias or spread could not be found.
156    #[error("Anchor '&{name}' not found")]
157    #[diagnostic(
158        code(resolver::anchor_not_found),
159        help("Ensure the anchor is defined with '&{name}: ...' in the correct scope.")
160    )]
161    AnchorNotFound {
162        name: String,
163        #[source_code]
164        src: Arc<NamedSource<String>>,
165        #[label("This anchor was not found")]
166        span: SourceSpan,
167    },
168
169    /// The spread operator (`...*`) was used on a value that is not an object.
170    #[error("Cannot spread a non-object value")]
171    #[diagnostic(
172        code(resolver::spread_on_non_object),
173        help("The '...*' operator can only be used on an object anchor inside another object.")
174    )]
175    SpreadOnNonObject {
176        name: String,
177        #[source_code]
178        src: Arc<NamedSource<String>>,
179        #[label("'{name}' does not point to an object")]
180        span: SourceSpan,
181    },
182
183    /// The spread operator (`...*`) was used on a value that is not an array.
184    #[error("Cannot spread a non-array value")]
185    #[diagnostic(
186        code(resolver::spread_on_non_array),
187        help("The '...*' operator can only be used on an array anchor inside another array.")
188    )]
189    SpreadOnNonArray {
190        name: String,
191        #[source_code]
192        src: Arc<NamedSource<String>>,
193        #[label("'{name}' does not point to an array")]
194        span: SourceSpan,
195    },
196
197    /// A circular dependency was detected in module imports.
198    #[error("Circular dependency detected")]
199    #[diagnostic(
200        code(resolver::circular_dependency),
201        help("The following import chain forms a loop: {cycle}")
202    )]
203    CircularDependency {
204        cycle: String,
205        #[source_code]
206        src: Arc<NamedSource<String>>,
207        #[label("Importing this module creates a cycle")]
208        span: SourceSpan,
209    },
210
211    /// An error occurred during data validation against a schema.
212    #[error(transparent)]
213    #[diagnostic(transparent)]
214    Validation(#[from] ValidationError),
215
216    /// A parser error that occurred while resolving an imported module.
217    #[error(transparent)]
218    #[diagnostic(transparent)]
219    WrappedParserError(Box<ParserError>),
220}
221
222impl From<ParserError> for ResolverError {
223    fn from(err: ParserError) -> Self {
224        ResolverError::WrappedParserError(Box::new(err))
225    }
226}
227
228/// An error that occurred during data validation against a schema.
229#[derive(Error, Debug, Diagnostic, Clone)]
230#[error("Validation Error")]
231pub enum ValidationError {
232    /// A field's value did not match its declared type.
233    #[error("Type mismatch for field '{field_name}'. Expected type {expected_type} but got {found_type}.")]
234    #[diagnostic(
235        code(validation::type_mismatch),
236        help(
237            "Ensure the value's type matches the expected type in the struct or enum definition."
238        )
239    )]
240    TypeMismatch {
241        field_name: String,
242        expected_type: String,
243        found_type: String,
244        #[source_code]
245        src: Arc<NamedSource<String>>,
246        #[label("Type mismatch here")]
247        span: SourceSpan,
248    },
249
250    /// A required field was missing from a struct.
251    #[error("Missing required field '{field_name}' for struct '{struct_name}'.")]
252    #[diagnostic(
253        code(validation::missing_field),
254        help("Add the missing field or make it optional in the struct definition.")
255    )]
256    MissingField {
257        field_name: String,
258        struct_name: String,
259        #[source_code]
260        src: Arc<NamedSource<String>>,
261        #[label("Field '{field_name}' is missing here")]
262        span: SourceSpan,
263    },
264
265    /// An unexpected field was found in a struct.
266    #[error("Found unexpected field '{field_name}' not defined in struct '{struct_name}'.")]
267    #[diagnostic(
268        code(validation::unexpected_field),
269        help("Remove the unexpected field or add it to the struct definition.")
270    )]
271    UnexpectedField {
272        field_name: String,
273        struct_name: String,
274        #[source_code]
275        src: Arc<NamedSource<String>>,
276        #[label("Unexpected field here")]
277        span: SourceSpan,
278    },
279
280    /// A type name was used that has not been defined or imported.
281    #[error("Undefined type '{type_name}'.")]
282    #[diagnostic(
283        code(validation::undefined_type),
284        help("Ensure the type is in scope or imported correctly.")
285    )]
286    UndefinedType {
287        type_name: String,
288        #[source_code]
289        src: Arc<NamedSource<String>>,
290        #[label("Undefined type used here")]
291        span: SourceSpan,
292    },
293
294    /// A variant was used that is not defined in the corresponding enum.
295    #[error("Variant '{variant_name}' is not defined in enum '{enum_name}'.")]
296    #[diagnostic(
297        code(validation::undefined_enum_variant),
298        help("Ensure the enum variant exists in the enum definition.")
299    )]
300    UndefinedEnumVariant {
301        variant_name: String,
302        enum_name: String,
303        #[source_code]
304        src: Arc<NamedSource<String>>,
305        #[label("Undefined enum variant used here")]
306        span: SourceSpan,
307    },
308
309    /// A complex collection type was used that is not yet supported by the validator.
310    #[error("Complex collection type validation not yet implemented for field '{field_name}'.")]
311    #[diagnostic(
312        code(validation::unimplemented_collection_validation),
313        help("This type of collection validation is not yet supported.")
314    )]
315    UnimplementedCollectionValidation {
316        field_name: String,
317        #[source_code]
318        src: Arc<NamedSource<String>>,
319        #[label("Complex collection type here")]
320        span: SourceSpan,
321    },
322}
323
324impl From<MonError> for ResolverError {
325    fn from(err: MonError) -> Self {
326        match err {
327            MonError::Parser(p_err) => ResolverError::WrappedParserError(p_err),
328            MonError::Resolver(r_err) => *r_err,
329        }
330    }
331}