log_reader/
error.rs

1//! Error types for the log reader library.
2
3use thiserror::Error;
4
5/// The main error type for log reader operations.
6#[derive(Error, Debug)]
7pub enum Error {
8    /// I/O errors when reading files or watching for changes.
9    #[error("I/O error: {0}")]
10    Io(#[from] std::io::Error),
11
12    /// File watching errors from the notify crate.
13    #[error("File watcher error: {0}")]
14    Watcher(#[from] notify::Error),
15
16    /// UTF-8 decoding errors when reading file content.
17    #[error("UTF-8 decoding error: {0}")]
18    Utf8(#[from] std::string::FromUtf8Error),
19
20    /// File path errors.
21    #[error("Invalid file path: {message}")]
22    InvalidPath { message: String },
23
24    /// File has been removed or is no longer accessible.
25    #[error("File no longer exists: {path}")]
26    FileNotFound { path: String },
27
28    /// Stream has been closed or dropped.
29    #[error("Stream closed")]
30    StreamClosed,
31}
32
33/// A convenient Result type for log reader operations.
34pub type Result<T> = std::result::Result<T, Error>;
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39    use std::io::{Error as IoError, ErrorKind};
40
41    #[test]
42    fn test_io_error_conversion() {
43        let io_error = IoError::new(ErrorKind::NotFound, "File not found");
44        let error: Error = io_error.into();
45
46        match error {
47            Error::Io(_) => {}
48            _ => panic!("Expected Error::Io variant"),
49        }
50
51        assert!(error.to_string().contains("I/O error"));
52        assert!(error.to_string().contains("File not found"));
53    }
54
55    #[test]
56    fn test_watcher_error_conversion() {
57        let notify_error = notify::Error::generic("Test watcher error");
58        let error: Error = notify_error.into();
59
60        match error {
61            Error::Watcher(_) => {}
62            _ => panic!("Expected Error::Watcher variant"),
63        }
64
65        assert!(error.to_string().contains("File watcher error"));
66        assert!(error.to_string().contains("Test watcher error"));
67    }
68
69    #[test]
70    fn test_utf8_error_conversion() {
71        let utf8_error = String::from_utf8(vec![0, 159, 146, 150]).unwrap_err();
72        let error: Error = utf8_error.into();
73
74        match error {
75            Error::Utf8(_) => {}
76            _ => panic!("Expected Error::Utf8 variant"),
77        }
78
79        assert!(error.to_string().contains("UTF-8 decoding error"));
80    }
81
82    #[test]
83    fn test_invalid_path_error() {
84        let error = Error::InvalidPath {
85            message: "Path contains invalid characters".to_string(),
86        };
87
88        assert_eq!(
89            error.to_string(),
90            "Invalid file path: Path contains invalid characters"
91        );
92    }
93
94    #[test]
95    fn test_file_not_found_error() {
96        let error = Error::FileNotFound {
97            path: "/path/to/missing/file.log".to_string(),
98        };
99
100        assert_eq!(
101            error.to_string(),
102            "File no longer exists: /path/to/missing/file.log"
103        );
104    }
105
106    #[test]
107    fn test_stream_closed_error() {
108        let error = Error::StreamClosed;
109        assert_eq!(error.to_string(), "Stream closed");
110    }
111
112    #[test]
113    fn test_error_debug_format() {
114        let error = Error::StreamClosed;
115        let debug_str = format!("{:?}", error);
116        assert_eq!(debug_str, "StreamClosed");
117    }
118
119    #[test]
120    fn test_result_type_alias() {
121        let success: Result<i32> = Ok(42);
122        let failure: Result<i32> = Err(Error::StreamClosed);
123
124        assert!(success.is_ok());
125        assert!(failure.is_err());
126        assert_eq!(success.unwrap(), 42);
127
128        match failure {
129            Err(Error::StreamClosed) => {}
130            _ => panic!("Expected StreamClosed error"),
131        }
132    }
133
134    #[test]
135    fn test_error_chain_with_io_error() {
136        let io_error = IoError::new(ErrorKind::PermissionDenied, "Access denied");
137        let error: Error = io_error.into();
138
139        // Test that the original error is preserved in the chain
140        match &error {
141            Error::Io(inner) => {
142                assert_eq!(inner.kind(), ErrorKind::PermissionDenied);
143                assert_eq!(inner.to_string(), "Access denied");
144            }
145            _ => panic!("Expected Error::Io variant"),
146        }
147    }
148
149    #[test]
150    fn test_error_send_sync_traits() {
151        // Ensure our error type implements Send + Sync for async compatibility
152        fn assert_send<T: Send>() {}
153        fn assert_sync<T: Sync>() {}
154
155        assert_send::<Error>();
156        assert_sync::<Error>();
157    }
158}