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    /// Concurrency errors - lock failures, thread synchronization issues
103    #[error("Concurrency error: {message}")]
104    Concurrency {
105        /// Description of the concurrency error
106        message: String,
107    },
108
109    /// NOML library errors (when using NOML format)
110    #[cfg(feature = "noml")]
111    #[error("NOML error: {source}")]
112    Noml {
113        /// The underlying NOML error
114        #[from]
115        source: noml::NomlError,
116    },
117
118    /// Internal errors - these should never happen in normal operation
119    #[error("Internal error: {message}")]
120    Internal {
121        /// Description of the internal error
122        message: String,
123        /// Optional context about where this occurred
124        context: Option<String>,
125    },
126}
127
128impl Error {
129    /// Create a parse error with position information
130    pub fn parse(message: impl Into<String>, line: usize, column: usize) -> Self {
131        Self::Parse {
132            message: message.into(),
133            line,
134            column,
135            file: None,
136        }
137    }
138
139    /// Create a parse error with file context
140    pub fn parse_with_file(
141        message: impl Into<String>,
142        line: usize,
143        column: usize,
144        file: impl Into<String>,
145    ) -> Self {
146        Self::Parse {
147            message: message.into(),
148            line,
149            column,
150            file: Some(file.into()),
151        }
152    }
153
154    /// Create a key not found error
155    pub fn key_not_found(key: impl Into<String>) -> Self {
156        Self::KeyNotFound {
157            key: key.into(),
158            available: Vec::new(),
159        }
160    }
161
162    /// Create a key not found error with suggestions
163    pub fn key_not_found_with_suggestions(key: impl Into<String>, available: Vec<String>) -> Self {
164        Self::KeyNotFound {
165            key: key.into(),
166            available,
167        }
168    }
169
170    /// Create a type conversion error
171    pub fn type_error(
172        value: impl Into<String>,
173        expected: impl Into<String>,
174        actual: impl Into<String>,
175    ) -> Self {
176        Self::Type {
177            value: value.into(),
178            expected_type: expected.into(),
179            actual_type: actual.into(),
180        }
181    }
182
183    /// Create an I/O error with file context
184    pub fn io(path: impl Into<String>, source: io::Error) -> Self {
185        Self::Io {
186            path: path.into(),
187            source,
188        }
189    }
190
191    /// Create an unknown format error
192    pub fn unknown_format(format: impl Into<String>) -> Self {
193        Self::UnknownFormat {
194            format: format.into(),
195        }
196    }
197
198    /// Create a feature not enabled error
199    pub fn feature_not_enabled(feature: impl Into<String>) -> Self {
200        Self::FeatureNotEnabled {
201            feature: feature.into(),
202        }
203    }
204
205    /// Create a concurrency error
206    pub fn concurrency(message: impl Into<String>) -> Self {
207        Self::Concurrency {
208            message: message.into(),
209        }
210    }
211
212    /// Create a serialization error
213    pub fn serialize(message: impl Into<String>) -> Self {
214        Self::General {
215            message: message.into(),
216        }
217    }
218
219    /// Create a schema validation error
220    #[cfg(feature = "schema")]
221    pub fn schema(path: impl Into<String>, message: impl Into<String>) -> Self {
222        Self::Schema {
223            path: path.into(),
224            message: message.into(),
225            expected: None,
226        }
227    }
228
229    /// Create a schema validation error with expected type
230    #[cfg(feature = "schema")]
231    pub fn schema_with_expected(
232        path: impl Into<String>,
233        message: impl Into<String>,
234        expected: impl Into<String>,
235    ) -> Self {
236        Self::Schema {
237            path: path.into(),
238            message: message.into(),
239            expected: Some(expected.into()),
240        }
241    }
242
243    /// Create a validation error
244    pub fn validation(message: impl Into<String>) -> Self {
245        Self::Validation {
246            message: message.into(),
247        }
248    }
249
250    /// Create a general error
251    pub fn general(message: impl Into<String>) -> Self {
252        Self::General {
253            message: message.into(),
254        }
255    }
256
257    /// Create an internal error
258    pub fn internal(message: impl Into<String>) -> Self {
259        Self::Internal {
260            message: message.into(),
261            context: None,
262        }
263    }
264
265    /// Create an internal error with context
266    pub fn internal_with_context(message: impl Into<String>, context: impl Into<String>) -> Self {
267        Self::Internal {
268            message: message.into(),
269            context: Some(context.into()),
270        }
271    }
272}
273
274/// Convert from std::io::Error
275impl From<io::Error> for Error {
276    fn from(source: io::Error) -> Self {
277        Self::Io {
278            path: "unknown".to_string(),
279            source,
280        }
281    }
282}