foxy/config/
error.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Error types for the configuration module.
6
7use std::fmt;
8use std::io;
9use thiserror::Error;
10
11/// Errors that can occur during configuration operations.
12#[derive(Error, Debug)]
13pub enum ConfigError {
14    /// The requested configuration key was not found.
15    #[error("configuration key not found")]
16    NotFound,
17
18    /// An error occurred while parsing or deserializing a configuration value.
19    #[error("failed to parse configuration: {0}")]
20    ParseError(String),
21
22    /// An IO error occurred (e.g., while reading a configuration file).
23    #[error("IO error: {0}")]
24    IoError(#[from] io::Error),
25
26    /// An error related to a specific configuration provider.
27    #[error("provider error: {provider}: {message}")]
28    ProviderError {
29        provider: String,
30        message: String,
31    },
32
33    /// A generic error.
34    #[error("{0}")]
35    Other(String),
36}
37
38impl ConfigError {
39    /// Create a new provider error.
40    pub fn provider_error<P: fmt::Display, M: fmt::Display>(provider: P, message: M) -> Self {
41        Self::ProviderError {
42            provider: provider.to_string(),
43            message: message.to_string(),
44        }
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use std::error::Error;
52    use std::io::{Error as IoError, ErrorKind};
53
54    #[test]
55    fn test_config_error_not_found() {
56        let error = ConfigError::NotFound;
57        assert_eq!(error.to_string(), "configuration key not found");
58    }
59
60    #[test]
61    fn test_config_error_parse_error() {
62        let error = ConfigError::ParseError("invalid JSON".to_string());
63        assert_eq!(error.to_string(), "failed to parse configuration: invalid JSON");
64    }
65
66    #[test]
67    fn test_config_error_io_error() {
68        let io_error = IoError::new(ErrorKind::NotFound, "file not found");
69        let error = ConfigError::IoError(io_error);
70        assert!(error.to_string().contains("IO error"));
71        assert!(error.to_string().contains("file not found"));
72    }
73
74    #[test]
75    fn test_config_error_io_error_from_conversion() {
76        let io_error = IoError::new(ErrorKind::PermissionDenied, "access denied");
77        let error: ConfigError = io_error.into();
78
79        match error {
80            ConfigError::IoError(ref e) => {
81                assert_eq!(e.kind(), ErrorKind::PermissionDenied);
82                assert_eq!(e.to_string(), "access denied");
83            }
84            _ => panic!("Expected IoError variant"),
85        }
86    }
87
88    #[test]
89    fn test_config_error_provider_error() {
90        let error = ConfigError::ProviderError {
91            provider: "env".to_string(),
92            message: "variable not set".to_string(),
93        };
94        assert_eq!(error.to_string(), "provider error: env: variable not set");
95    }
96
97    #[test]
98    fn test_config_error_provider_error_constructor() {
99        let error = ConfigError::provider_error("file", "invalid format");
100
101        match &error {
102            ConfigError::ProviderError { provider, message } => {
103                assert_eq!(provider, "file");
104                assert_eq!(message, "invalid format");
105            }
106            _ => panic!("Expected ProviderError variant"),
107        }
108
109        assert_eq!(error.to_string(), "provider error: file: invalid format");
110    }
111
112    #[test]
113    fn test_config_error_provider_error_with_display_types() {
114        // Test with different Display types
115        let error = ConfigError::provider_error(42, true);
116
117        match &error {
118            ConfigError::ProviderError { provider, message } => {
119                assert_eq!(provider, "42");
120                assert_eq!(message, "true");
121            }
122            _ => panic!("Expected ProviderError variant"),
123        }
124
125        assert_eq!(error.to_string(), "provider error: 42: true");
126    }
127
128    #[test]
129    fn test_config_error_other() {
130        let error = ConfigError::Other("custom error message".to_string());
131        assert_eq!(error.to_string(), "custom error message");
132    }
133
134    #[test]
135    fn test_config_error_debug() {
136        let error = ConfigError::NotFound;
137        let debug_str = format!("{:?}", error);
138        assert!(debug_str.contains("NotFound"));
139    }
140
141    #[test]
142    fn test_config_error_debug_with_data() {
143        let error = ConfigError::ParseError("test".to_string());
144        let debug_str = format!("{:?}", error);
145        assert!(debug_str.contains("ParseError"));
146        assert!(debug_str.contains("test"));
147    }
148
149    #[test]
150    fn test_config_error_is_error_trait() {
151        let error = ConfigError::NotFound;
152        let _: &dyn std::error::Error = &error;
153    }
154
155    #[test]
156    fn test_config_error_source() {
157        let io_error = IoError::new(ErrorKind::InvalidData, "bad data");
158        let error = ConfigError::IoError(io_error);
159
160        assert!(error.source().is_some());
161        let source = error.source().unwrap();
162        assert_eq!(source.to_string(), "bad data");
163    }
164
165    #[test]
166    fn test_config_error_no_source() {
167        let error = ConfigError::NotFound;
168        assert!(error.source().is_none());
169    }
170
171    #[test]
172    fn test_config_error_variants_equality() {
173        // Test that we can match on variants
174        let errors = vec![
175            ConfigError::NotFound,
176            ConfigError::ParseError("test".to_string()),
177            ConfigError::Other("other".to_string()),
178            ConfigError::ProviderError {
179                provider: "test".to_string(),
180                message: "msg".to_string(),
181            },
182        ];
183
184        for error in errors {
185            match error {
186                ConfigError::NotFound => assert!(true),
187                ConfigError::ParseError(_) => assert!(true),
188                ConfigError::IoError(_) => assert!(true),
189                ConfigError::ProviderError { .. } => assert!(true),
190                ConfigError::Other(_) => assert!(true),
191            }
192        }
193    }
194
195    #[test]
196    fn test_config_error_empty_strings() {
197        let error = ConfigError::ParseError("".to_string());
198        assert_eq!(error.to_string(), "failed to parse configuration: ");
199
200        let error = ConfigError::Other("".to_string());
201        assert_eq!(error.to_string(), "");
202
203        let error = ConfigError::ProviderError {
204            provider: "".to_string(),
205            message: "".to_string(),
206        };
207        assert_eq!(error.to_string(), "provider error: : ");
208    }
209
210    #[test]
211    fn test_config_error_special_characters() {
212        let error = ConfigError::ParseError("error with\nnewlines\tand\ttabs".to_string());
213        assert!(error.to_string().contains("newlines"));
214        assert!(error.to_string().contains("tabs"));
215
216        let error = ConfigError::provider_error("provider with spaces", "message with 🚀 emoji");
217        assert!(error.to_string().contains("provider with spaces"));
218        assert!(error.to_string().contains("🚀"));
219    }
220}