ddns_a/monitor/
error.rs

1//! Error types for the monitor layer.
2
3use crate::network::FetchError;
4use thiserror::Error;
5
6/// Error type for API event listeners.
7///
8/// Represents failures in platform-specific event notification APIs.
9/// These errors may be recoverable (by falling back to polling mode).
10#[derive(Debug, Error)]
11pub enum ApiError {
12    /// Windows API call failed.
13    #[cfg(windows)]
14    #[error("Windows API error: {0}")]
15    WindowsApi(#[from] windows::core::Error),
16
17    /// The API listener stopped unexpectedly.
18    ///
19    /// This can happen when the underlying event stream terminates
20    /// without explicit shutdown request.
21    #[error("Listener stopped unexpectedly")]
22    Stopped,
23}
24
25/// Error type for monitor operations.
26///
27/// Describes failures during IP address monitoring.
28/// Callers decide recovery strategy based on the error variant.
29#[derive(Debug, Error)]
30pub enum MonitorError {
31    /// Failed to fetch network adapter addresses.
32    #[error("Failed to fetch addresses: {0}")]
33    Fetch(#[from] FetchError),
34
35    /// The API listener failed.
36    ///
37    /// This error indicates that the platform event notification API
38    /// has failed. The monitor should fall back to polling-only mode.
39    #[error("API listener failed: {0}")]
40    ApiListenerFailed(#[source] ApiError),
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    mod api_error {
48        use super::*;
49
50        #[test]
51        fn stopped_displays_message() {
52            let error = ApiError::Stopped;
53            assert_eq!(error.to_string(), "Listener stopped unexpectedly");
54        }
55
56        #[cfg(windows)]
57        #[test]
58        fn windows_api_error_preserves_source() {
59            use windows::core::{Error as WinError, HRESULT};
60
61            // Create a Windows error using a known HRESULT
62            let win_error = WinError::from_hresult(HRESULT(-2_147_024_809)); // E_INVALIDARG
63            let api_error: ApiError = win_error.into();
64
65            // Verify it's the correct variant and displays properly
66            assert!(api_error.to_string().contains("Windows API error"));
67        }
68    }
69
70    mod monitor_error {
71        use super::*;
72        use std::error::Error;
73
74        #[test]
75        fn fetch_error_displays_with_context() {
76            let fetch_error = FetchError::Platform {
77                message: "test failure".to_string(),
78            };
79            let monitor_error = MonitorError::Fetch(fetch_error);
80
81            assert!(monitor_error.to_string().contains("Failed to fetch"));
82            assert!(monitor_error.to_string().contains("test failure"));
83        }
84
85        #[test]
86        fn fetch_error_preserves_source_chain() {
87            let fetch_error = FetchError::Platform {
88                message: "inner error".to_string(),
89            };
90            let monitor_error = MonitorError::Fetch(fetch_error);
91
92            let source = monitor_error.source();
93            assert!(source.is_some());
94            assert!(source.unwrap().to_string().contains("inner error"));
95        }
96
97        #[test]
98        fn api_listener_failed_displays_with_context() {
99            let api_error = ApiError::Stopped;
100            let monitor_error = MonitorError::ApiListenerFailed(api_error);
101
102            assert!(monitor_error.to_string().contains("API listener failed"));
103        }
104
105        #[test]
106        fn api_listener_failed_preserves_source() {
107            let api_error = ApiError::Stopped;
108            let monitor_error = MonitorError::ApiListenerFailed(api_error);
109
110            let source = monitor_error.source();
111            assert!(source.is_some());
112            assert!(
113                source
114                    .unwrap()
115                    .to_string()
116                    .contains("Listener stopped unexpectedly")
117            );
118        }
119
120        #[test]
121        fn from_fetch_error_conversion() {
122            let fetch_error = FetchError::PermissionDenied {
123                context: "elevated required".to_string(),
124            };
125            let monitor_error: MonitorError = fetch_error.into();
126
127            assert!(matches!(monitor_error, MonitorError::Fetch(_)));
128        }
129    }
130}