1use thiserror::Error;
4
5#[derive(Error, Debug)]
7pub enum Error {
8 #[error("I/O error: {0}")]
10 Io(#[from] std::io::Error),
11
12 #[error("File watcher error: {0}")]
14 Watcher(#[from] notify::Error),
15
16 #[error("UTF-8 decoding error: {0}")]
18 Utf8(#[from] std::string::FromUtf8Error),
19
20 #[error("Invalid file path: {message}")]
22 InvalidPath { message: String },
23
24 #[error("File no longer exists: {path}")]
26 FileNotFound { path: String },
27
28 #[error("Stream closed")]
30 StreamClosed,
31}
32
33pub 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 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 fn assert_send<T: Send>() {}
153 fn assert_sync<T: Sync>() {}
154
155 assert_send::<Error>();
156 assert_sync::<Error>();
157 }
158}