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#[derive(Error, Debug)]
18pub enum Error {
19    /// Parsing errors - when the input cannot be parsed due to syntax issues
20    #[error("Parse error at line {line}, column {column}: {message}")]
21    Parse {
22        /// Human-readable error message
23        message: String,
24        /// Line number where error occurred (1-indexed)
25        line: usize,
26        /// Column number where error occurred (1-indexed)
27        column: usize,
28        /// File path where error occurred (if applicable)
29        file: Option<String>,
30    },
31
32    /// Format detection errors
33    #[error("Unknown format: {format}")]
34    UnknownFormat {
35        /// The format that couldn't be detected/parsed
36        format: String,
37    },
38
39    /// Key access errors - when requesting non-existent keys
40    #[error("Key '{key}' not found")]
41    KeyNotFound {
42        /// The key that was requested
43        key: String,
44        /// Available keys at that level (for suggestions)
45        available: Vec<String>,
46    },
47
48    /// Type conversion errors - when values cannot be converted to requested type
49    #[error("Type error: cannot convert '{value}' to {expected_type}")]
50    Type {
51        /// The value that couldn't be converted
52        value: String,
53        /// The expected type
54        expected_type: String,
55        /// The actual type found
56        actual_type: String,
57    },
58
59    /// File I/O errors - wraps std::io::Error with additional context
60    #[error("File error for '{path}': {source}")]
61    Io {
62        /// Path to the file that caused the error
63        path: String,
64        /// The underlying I/O error
65        #[source]
66        source: io::Error,
67    },
68
69    /// Schema validation errors
70    #[cfg(feature = "schema")]
71    #[error("Schema error at '{path}': {message}")]
72    Schema {
73        /// Path where schema validation failed
74        path: String,
75        /// Description of the schema violation
76        message: String,
77        /// Expected schema type/format
78        expected: Option<String>,
79    },
80
81    /// General validation errors
82    #[error("Validation error: {message}")]
83    Validation {
84        /// Description of the validation error
85        message: String,
86    },
87
88    /// Generic error for other cases
89    #[error("{message}")]
90    General {
91        /// Generic error message
92        message: String,
93    },
94
95    /// Feature not enabled errors
96    #[error("Feature '{feature}' is not enabled. Enable with features = [\"{feature}\"]")]
97    FeatureNotEnabled {
98        /// The feature that needs to be enabled
99        feature: String,
100    },
101
102    /// NOML library errors (when using NOML format)
103    #[cfg(feature = "noml")]
104    #[error("NOML error: {source}")]
105    Noml {
106        /// The underlying NOML error
107        #[from]
108        source: noml::NomlError,
109    },
110
111    /// Internal errors - these should never happen in normal operation
112    #[error("Internal error: {message}")]
113    Internal {
114        /// Description of the internal error
115        message: String,
116        /// Optional context about where this occurred
117        context: Option<String>,
118    },
119}
120
121impl Error {
122    /// Create a parse error with position information
123    pub fn parse(message: impl Into<String>, line: usize, column: usize) -> Self {
124        Self::Parse {
125            message: message.into(),
126            line,
127            column,
128            file: None,
129        }
130    }
131
132    /// Create a parse error with file context
133    pub fn parse_with_file(
134        message: impl Into<String>,
135        line: usize,
136        column: usize,
137        file: impl Into<String>,
138    ) -> Self {
139        Self::Parse {
140            message: message.into(),
141            line,
142            column,
143            file: Some(file.into()),
144        }
145    }
146
147    /// Create a key not found error
148    pub fn key_not_found(key: impl Into<String>) -> Self {
149        Self::KeyNotFound {
150            key: key.into(),
151            available: Vec::new(),
152        }
153    }
154
155    /// Create a key not found error with suggestions
156    pub fn key_not_found_with_suggestions(
157        key: impl Into<String>,
158        available: Vec<String>,
159    ) -> Self {
160        Self::KeyNotFound {
161            key: key.into(),
162            available,
163        }
164    }
165
166    /// Create a type conversion error
167    pub fn type_error(
168        value: impl Into<String>,
169        expected: impl Into<String>,
170        actual: impl Into<String>,
171    ) -> Self {
172        Self::Type {
173            value: value.into(),
174            expected_type: expected.into(),
175            actual_type: actual.into(),
176        }
177    }
178
179    /// Create an I/O error with file context
180    pub fn io(path: impl Into<String>, source: io::Error) -> Self {
181        Self::Io {
182            path: path.into(),
183            source,
184        }
185    }
186
187    /// Create an unknown format error
188    pub fn unknown_format(format: impl Into<String>) -> Self {
189        Self::UnknownFormat {
190            format: format.into(),
191        }
192    }
193
194    /// Create a feature not enabled error
195    pub fn feature_not_enabled(feature: impl Into<String>) -> Self {
196        Self::FeatureNotEnabled {
197            feature: feature.into(),
198        }
199    }
200
201    /// Create a serialization error
202    pub fn serialize(message: impl Into<String>) -> Self {
203        Self::General {
204            message: message.into(),
205        }
206    }
207
208    /// Create a schema validation error
209    #[cfg(feature = "schema")]
210    pub fn schema(path: impl Into<String>, message: impl Into<String>) -> Self {
211        Self::Schema {
212            path: path.into(),
213            message: message.into(),
214            expected: None,
215        }
216    }
217
218    /// Create a schema validation error with expected type
219    #[cfg(feature = "schema")]
220    pub fn schema_with_expected(
221        path: impl Into<String>,
222        message: impl Into<String>,
223        expected: impl Into<String>,
224    ) -> Self {
225        Self::Schema {
226            path: path.into(),
227            message: message.into(),
228            expected: Some(expected.into()),
229        }
230    }
231
232    /// Create a validation error
233    pub fn validation(message: impl Into<String>) -> Self {
234        Self::Validation {
235            message: message.into(),
236        }
237    }
238
239    /// Create a general error
240    pub fn general(message: impl Into<String>) -> Self {
241        Self::General {
242            message: message.into(),
243        }
244    }
245
246    /// Create an internal error
247    pub fn internal(message: impl Into<String>) -> Self {
248        Self::Internal {
249            message: message.into(),
250            context: None,
251        }
252    }
253
254    /// Create an internal error with context
255    pub fn internal_with_context(
256        message: impl Into<String>,
257        context: impl Into<String>,
258    ) -> Self {
259        Self::Internal {
260            message: message.into(),
261            context: Some(context.into()),
262        }
263    }
264}
265
266/// Convert from std::io::Error
267impl From<io::Error> for Error {
268    fn from(source: io::Error) -> Self {
269        Self::Io {
270            path: "unknown".to_string(),
271            source,
272        }
273    }
274}