1use crate::config::ConfigError;
7#[cfg(feature = "miette")]
8use miette::Diagnostic;
9use std::fmt;
10use std::io;
11use thiserror::Error;
12
13#[derive(Error, Debug)]
14#[cfg_attr(feature = "miette", derive(Diagnostic))]
15pub enum RlgError {
17 #[error("I/O error: {0}")]
18 #[cfg_attr(
19 feature = "miette",
20 diagnostic(
21 code(rlg::io_error),
22 help("Ensure the log directory exists and is writable.")
23 )
24 )]
25 IoError(#[from] io::Error),
27
28 #[error("Configuration error: {0}")]
29 #[cfg_attr(
30 feature = "miette",
31 diagnostic(
32 code(rlg::config_error),
33 help(
34 "Check your configuration file or environment variables."
35 )
36 )
37 )]
38 ConfigError(#[from] ConfigError),
40
41 #[error("Log format parse error: {0}")]
42 #[cfg_attr(
43 feature = "miette",
44 diagnostic(
45 code(rlg::format_parse_error),
46 help(
47 "Ensure the format string matches supported variants (JSON, OTLP, MCP, etc.)."
48 )
49 )
50 )]
51 FormatParseError(String),
53
54 #[error("Log level parse error: {0}")]
55 #[cfg_attr(
56 feature = "miette",
57 diagnostic(
58 code(rlg::level_parse_error),
59 help(
60 "Supported levels: ALL, TRACE, DEBUG, INFO, WARN, ERROR, FATAL."
61 )
62 )
63 )]
64 LevelParseError(String),
66
67 #[error("Unsupported log format: {0}")]
68 #[cfg_attr(
69 feature = "miette",
70 diagnostic(
71 code(rlg::unsupported_format),
72 help(
73 "Visit docs.rs/rlg for a list of supported industry formats."
74 )
75 )
76 )]
77 UnsupportedFormat(String),
79
80 #[error("Log formatting error: {0}")]
81 #[cfg_attr(
82 feature = "miette",
83 diagnostic(
84 code(rlg::formatting_error),
85 help(
86 "This may happen if attributes contain non-serializable data."
87 )
88 )
89 )]
90 FormattingError(String),
92
93 #[error("Log rotation error: {0}")]
94 #[cfg_attr(
95 feature = "miette",
96 diagnostic(
97 code(rlg::rotation_error),
98 help(
99 "Ensure RLG has permission to rename or delete old log files."
100 )
101 )
102 )]
103 RotationError(String),
105
106 #[error("Network error: {0}")]
107 #[cfg_attr(
108 feature = "miette",
109 diagnostic(
110 code(rlg::network_error),
111 help(
112 "Check your network connection or the OTLP collector endpoint."
113 )
114 )
115 )]
116 NetworkError(String),
118
119 #[error("DateTime parse error: {0}")]
120 #[cfg_attr(
121 feature = "miette",
122 diagnostic(
123 code(rlg::datetime_parse_error),
124 help("RLG expects RFC 3339 / ISO 8601 timestamps.")
125 )
126 )]
127 DateTimeParseError(String),
129
130 #[error("{0}")]
131 #[cfg_attr(feature = "miette", diagnostic(code(rlg::custom_error)))]
132 Custom(String),
134
135 #[error("Native OS sink failure: {0}")]
136 #[cfg_attr(
137 feature = "miette",
138 diagnostic(
139 code(rlg::native_sink_failure),
140 help(
141 "Check if systemd-journald is running (Linux) or if 'com.rlg.logger' subsystem is registered (macOS). Ensure RLG_FALLBACK_STDOUT is set if you want to bypass native hooks."
142 )
143 )
144 )]
145 NativeSinkError(String),
147}
148
149impl From<crate::commons::error::CommonError> for RlgError {
150 fn from(err: crate::commons::error::CommonError) -> Self {
151 Self::Custom(err.to_string())
152 }
153}
154
155impl RlgError {
156 #[must_use]
158 pub fn custom<T: fmt::Display>(msg: T) -> Self {
159 Self::Custom(msg.to_string())
160 }
161}
162
163pub type RlgResult<T> = Result<T, RlgError>;
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_error_display() {
172 let err =
173 RlgError::FormatParseError("Invalid format".to_string());
174 assert_eq!(
175 err.to_string(),
176 "Log format parse error: Invalid format"
177 );
178 }
179
180 #[test]
181 fn test_custom_error() {
182 let err = RlgError::custom("Custom error message");
183 assert_eq!(err.to_string(), "Custom error message");
184 }
185
186 #[test]
187 fn test_common_error_conversion() {
188 let common_err =
189 crate::commons::error::CommonError::custom("test");
190 let rlg_err: RlgError = common_err.into();
191 assert!(matches!(rlg_err, RlgError::Custom(_)));
192 assert!(rlg_err.to_string().contains("test"));
193 }
194
195 #[test]
196 fn test_config_error_conversion() {
197 let config_err =
198 ConfigError::ValidationError("Test error".to_string());
199 let rlg_err: RlgError = config_err.into();
200 assert!(matches!(rlg_err, RlgError::ConfigError(_)));
201 }
202
203 #[test]
204 fn test_io_error_variant() {
205 let io_err =
206 io::Error::new(io::ErrorKind::NotFound, "file missing");
207 let rlg_err: RlgError = io_err.into();
208 assert!(matches!(rlg_err, RlgError::IoError(_)));
209 assert!(rlg_err.to_string().contains("file missing"));
210 }
211
212 #[test]
213 fn test_format_parse_error_variant() {
214 let err = RlgError::FormatParseError("bad format".into());
215 assert_eq!(
216 err.to_string(),
217 "Log format parse error: bad format"
218 );
219 }
220
221 #[test]
222 fn test_level_parse_error_variant() {
223 let err = RlgError::LevelParseError("bad level".into());
224 assert_eq!(err.to_string(), "Log level parse error: bad level");
225 }
226
227 #[test]
228 fn test_unsupported_format_variant() {
229 let err = RlgError::UnsupportedFormat("XML".into());
230 assert_eq!(err.to_string(), "Unsupported log format: XML");
231 }
232
233 #[test]
234 fn test_formatting_error_variant() {
235 let err = RlgError::FormattingError("template".into());
236 assert_eq!(err.to_string(), "Log formatting error: template");
237 }
238
239 #[test]
240 fn test_rotation_error_variant() {
241 let err = RlgError::RotationError("disk full".into());
242 assert_eq!(err.to_string(), "Log rotation error: disk full");
243 }
244
245 #[test]
246 fn test_network_error_variant() {
247 let err = RlgError::NetworkError("timeout".into());
248 assert_eq!(err.to_string(), "Network error: timeout");
249 }
250
251 #[test]
252 fn test_datetime_parse_error_variant() {
253 let err = RlgError::DateTimeParseError("bad date".into());
254 assert_eq!(err.to_string(), "DateTime parse error: bad date");
255 }
256
257 #[test]
258 fn test_native_sink_error_variant() {
259 let err = RlgError::NativeSinkError("journald down".into());
260 assert_eq!(
261 err.to_string(),
262 "Native OS sink failure: journald down"
263 );
264 }
265
266 #[test]
267 fn test_error_debug_all_variants() {
268 let variants: Vec<RlgError> = vec![
269 RlgError::IoError(io::Error::other("test")),
270 RlgError::ConfigError(ConfigError::ValidationError(
271 "v".into(),
272 )),
273 RlgError::FormatParseError("f".into()),
274 RlgError::LevelParseError("l".into()),
275 RlgError::UnsupportedFormat("u".into()),
276 RlgError::FormattingError("fm".into()),
277 RlgError::RotationError("r".into()),
278 RlgError::NetworkError("n".into()),
279 RlgError::DateTimeParseError("d".into()),
280 RlgError::Custom("c".into()),
281 RlgError::NativeSinkError("ns".into()),
282 ];
283 for err in &variants {
284 let dbg = format!("{err:?}");
285 assert!(!dbg.is_empty());
286 }
287 }
288
289 #[test]
290 fn test_error_is_std_error() {
291 let err = RlgError::NetworkError("test".into());
292 let _: &dyn std::error::Error = &err;
293 }
294
295 #[test]
296 fn test_rlg_result_ok() {
297 let r: RlgResult<i32> = Ok(42);
298 assert!(matches!(r, Ok(42)));
299 }
300
301 #[test]
302 fn test_rlg_result_err() {
303 let r: RlgResult<i32> = Err(RlgError::custom("fail"));
304 assert!(r.is_err());
305 }
306}