1use std::fmt;
8use std::io;
9use thiserror::Error;
10
11#[derive(Error, Debug)]
13pub enum ConfigError {
14 #[error("configuration key not found")]
16 NotFound,
17
18 #[error("failed to parse configuration: {0}")]
20 ParseError(String),
21
22 #[error("IO error: {0}")]
24 IoError(#[from] io::Error),
25
26 #[error("provider error: {provider}: {message}")]
28 ProviderError {
29 provider: String,
30 message: String,
31 },
32
33 #[error("{0}")]
35 Other(String),
36}
37
38impl ConfigError {
39 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 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 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}