Skip to main content

config_lib/
error.rs

1//! # Error Handling
2//!
3//! Comprehensive error system for config-lib operations.
4//! Designed for clarity, debuggability, and extensibility.
5
6use std::io;
7use thiserror::Error;
8
9/// The main result type used throughout config-lib operations.
10pub type Result<T> = std::result::Result<T, Error>;
11
12/// Comprehensive error types for all config-lib operations.
13///
14/// This error system provides maximum clarity about what went wrong,
15/// where it happened, and how to fix it. Each variant includes enough context
16/// for both developers and end users to understand and resolve issues.
17///
18/// **Stability:** `Error` is `#[non_exhaustive]` so the v1.x SemVer
19/// contract can add new error variants in MINOR releases without
20/// breaking user code. Callers must use a wildcard arm (`_ => ...`)
21/// when pattern-matching on `Error` directly. Prefer the constructor
22/// helpers ([`Error::parse`], [`Error::io`], etc.) over struct-literal
23/// construction.
24#[derive(Error, Debug)]
25#[non_exhaustive]
26pub enum Error {
27    /// Parsing errors - when the input cannot be parsed due to syntax issues
28    #[error("Parse error at line {line}, column {column}: {message}")]
29    Parse {
30        /// Human-readable error message
31        message: String,
32        /// Line number where error occurred (1-indexed)
33        line: usize,
34        /// Column number where error occurred (1-indexed)
35        column: usize,
36        /// File path where error occurred (if applicable)
37        file: Option<String>,
38    },
39
40    /// Format detection errors
41    #[error("Unknown format: {format}")]
42    UnknownFormat {
43        /// The format that couldn't be detected/parsed
44        format: String,
45    },
46
47    /// Key access errors - when requesting non-existent keys
48    #[error("Key '{key}' not found")]
49    KeyNotFound {
50        /// The key that was requested
51        key: String,
52        /// Available keys at that level (for suggestions)
53        available: Vec<String>,
54    },
55
56    /// Type conversion errors - when values cannot be converted to requested type
57    #[error("Type error: cannot convert '{value}' to {expected_type}")]
58    Type {
59        /// The value that couldn't be converted
60        value: String,
61        /// The expected type
62        expected_type: String,
63        /// The actual type found
64        actual_type: String,
65    },
66
67    /// File I/O errors - wraps std::io::Error with additional context
68    #[error("File error for '{path}': {source}")]
69    Io {
70        /// Path to the file that caused the error
71        path: String,
72        /// The underlying I/O error
73        #[source]
74        source: io::Error,
75    },
76
77    /// Schema validation errors
78    #[cfg(feature = "schema")]
79    #[error("Schema error at '{path}': {message}")]
80    Schema {
81        /// Path where schema validation failed
82        path: String,
83        /// Description of the schema violation
84        message: String,
85        /// Expected schema type/format
86        expected: Option<String>,
87    },
88
89    /// General validation errors
90    #[error("Validation error: {message}")]
91    Validation {
92        /// Description of the validation error
93        message: String,
94    },
95
96    /// Generic error for other cases
97    #[error("{message}")]
98    General {
99        /// Generic error message
100        message: String,
101    },
102
103    /// Feature not enabled errors
104    #[error("Feature '{feature}' is not enabled. Enable with features = [\"{feature}\"]")]
105    FeatureNotEnabled {
106        /// The feature that needs to be enabled
107        feature: String,
108    },
109
110    /// Concurrency errors - lock failures, thread synchronization issues
111    #[error("Concurrency error: {message}")]
112    Concurrency {
113        /// Description of the concurrency error
114        message: String,
115    },
116
117    /// NOML library errors (when using NOML format)
118    #[cfg(feature = "noml")]
119    #[error("NOML error: {source}")]
120    Noml {
121        /// The underlying NOML error
122        #[from]
123        source: noml::NomlError,
124    },
125
126    /// Internal errors - these should never happen in normal operation
127    #[error("Internal error: {message}")]
128    Internal {
129        /// Description of the internal error
130        message: String,
131        /// Optional context about where this occurred
132        context: Option<String>,
133    },
134}
135
136impl Error {
137    /// Create a parse error with position information
138    pub fn parse(message: impl Into<String>, line: usize, column: usize) -> Self {
139        Self::Parse {
140            message: message.into(),
141            line,
142            column,
143            file: None,
144        }
145    }
146
147    /// Create a parse error with file context
148    pub fn parse_with_file(
149        message: impl Into<String>,
150        line: usize,
151        column: usize,
152        file: impl Into<String>,
153    ) -> Self {
154        Self::Parse {
155            message: message.into(),
156            line,
157            column,
158            file: Some(file.into()),
159        }
160    }
161
162    /// Create a key not found error
163    pub fn key_not_found(key: impl Into<String>) -> Self {
164        Self::KeyNotFound {
165            key: key.into(),
166            available: Vec::new(),
167        }
168    }
169
170    /// Create a key not found error with suggestions
171    pub fn key_not_found_with_suggestions(key: impl Into<String>, available: Vec<String>) -> Self {
172        Self::KeyNotFound {
173            key: key.into(),
174            available,
175        }
176    }
177
178    /// Create a type conversion error
179    pub fn type_error(
180        value: impl Into<String>,
181        expected: impl Into<String>,
182        actual: impl Into<String>,
183    ) -> Self {
184        Self::Type {
185            value: value.into(),
186            expected_type: expected.into(),
187            actual_type: actual.into(),
188        }
189    }
190
191    /// Create an I/O error with file context
192    pub fn io(path: impl Into<String>, source: io::Error) -> Self {
193        Self::Io {
194            path: path.into(),
195            source,
196        }
197    }
198
199    /// Create an unknown format error
200    pub fn unknown_format(format: impl Into<String>) -> Self {
201        Self::UnknownFormat {
202            format: format.into(),
203        }
204    }
205
206    /// Create a feature not enabled error
207    pub fn feature_not_enabled(feature: impl Into<String>) -> Self {
208        Self::FeatureNotEnabled {
209            feature: feature.into(),
210        }
211    }
212
213    /// Create a concurrency error
214    pub fn concurrency(message: impl Into<String>) -> Self {
215        Self::Concurrency {
216            message: message.into(),
217        }
218    }
219
220    /// Create a serialization error
221    pub fn serialize(message: impl Into<String>) -> Self {
222        Self::General {
223            message: message.into(),
224        }
225    }
226
227    /// Create a schema validation error
228    #[cfg(feature = "schema")]
229    pub fn schema(path: impl Into<String>, message: impl Into<String>) -> Self {
230        Self::Schema {
231            path: path.into(),
232            message: message.into(),
233            expected: None,
234        }
235    }
236
237    /// Create a schema validation error with expected type
238    #[cfg(feature = "schema")]
239    pub fn schema_with_expected(
240        path: impl Into<String>,
241        message: impl Into<String>,
242        expected: impl Into<String>,
243    ) -> Self {
244        Self::Schema {
245            path: path.into(),
246            message: message.into(),
247            expected: Some(expected.into()),
248        }
249    }
250
251    /// Create a validation error
252    pub fn validation(message: impl Into<String>) -> Self {
253        Self::Validation {
254            message: message.into(),
255        }
256    }
257
258    /// Create a general error
259    pub fn general(message: impl Into<String>) -> Self {
260        Self::General {
261            message: message.into(),
262        }
263    }
264
265    /// Create an internal error
266    pub fn internal(message: impl Into<String>) -> Self {
267        Self::Internal {
268            message: message.into(),
269            context: None,
270        }
271    }
272
273    /// Create an internal error with context
274    pub fn internal_with_context(message: impl Into<String>, context: impl Into<String>) -> Self {
275        Self::Internal {
276            message: message.into(),
277            context: Some(context.into()),
278        }
279    }
280}
281
282/// Convert from std::io::Error
283impl From<io::Error> for Error {
284    fn from(source: io::Error) -> Self {
285        Self::Io {
286            path: "unknown".to_string(),
287            source,
288        }
289    }
290}