Skip to main content

grafton_ndi/
error.rs

1//! Error types for the grafton-ndi library.
2
3use thiserror::Error;
4
5use std::{ffi::NulError, io, time::Duration};
6
7/// The main error type for NDI operations.
8///
9/// This enum represents all possible errors that can occur when using the
10/// grafton-ndi library. It provides detailed error messages and automatic
11/// conversion from common error types.
12///
13/// This enum is marked `#[non_exhaustive]` so that new error variants can be
14/// added as the NDI SDK surface evolves without constituting a breaking change.
15/// Match arms should include a wildcard `_` pattern.
16#[derive(Debug, Error)]
17#[non_exhaustive]
18pub enum Error {
19    /// NDI runtime initialization failed.
20    ///
21    /// This typically occurs when the NDI SDK is not installed or cannot be loaded.
22    #[error("Failed to initialize the NDI runtime: {0}")]
23    InitializationFailed(String),
24
25    /// A null pointer was returned by the NDI SDK.
26    ///
27    /// Indicates an internal NDI error or invalid operation.
28    #[error("Encountered a null pointer in function: {0}")]
29    NullPointer(String),
30
31    /// Invalid UTF-8 data in a string from the NDI SDK.
32    #[error("Invalid UTF-8 string in data: {0}")]
33    InvalidUtf8(String),
34
35    /// Failed to create a C string due to null bytes.
36    #[error("Invalid CString: {0}")]
37    InvalidCString(#[from] NulError),
38
39    /// Frame capture operation failed.
40    #[error("Failed to capture frame: {0}")]
41    CaptureFailed(String),
42
43    /// Frame data is invalid or corrupted.
44    #[error("Invalid frame data: {0}")]
45    InvalidFrame(String),
46
47    /// PTZ (Pan-Tilt-Zoom) camera control command failed.
48    #[error("PTZ command failed: {0}")]
49    PtzCommandFailed(String),
50
51    /// Configuration parameters are invalid.
52    ///
53    /// This can occur when builder validation fails or conflicting options are set.
54    #[error("Invalid configuration: {0}")]
55    InvalidConfiguration(String),
56
57    /// I/O operation failed.
58    #[error(transparent)]
59    Io(#[from] io::Error),
60
61    /// Operation timed out with generic context.
62    ///
63    /// This is a general timeout error. For frame capture timeouts with retry information,
64    /// see [`Error::FrameTimeout`].
65    #[error("Operation timed out: {0}")]
66    Timeout(String),
67
68    /// Frame capture timed out after multiple retry attempts.
69    ///
70    /// This error includes detailed information about the retry attempts and total elapsed time,
71    /// making it easier to diagnose frame capture issues and distinguish them from other timeout scenarios.
72    ///
73    /// # Example
74    ///
75    /// ```
76    /// # use grafton_ndi::Error;
77    /// # use std::time::Duration;
78    /// match some_operation() {
79    ///     Err(Error::FrameTimeout { attempts, elapsed }) => {
80    ///         eprintln!("Frame timeout after {} attempts in {:?}", attempts, elapsed);
81    ///         // Could implement retry logic based on attempts count
82    ///     }
83    ///     Err(e) => eprintln!("Other error: {}", e),
84    ///     Ok(_) => println!("Success"),
85    /// }
86    /// # fn some_operation() -> Result<(), Error> { Ok(()) }
87    /// ```
88    #[error("Frame capture timed out after {attempts} attempts ({elapsed:?})")]
89    FrameTimeout {
90        /// Number of capture attempts made before timing out
91        attempts: usize,
92        /// Total elapsed time during capture attempts
93        elapsed: Duration,
94    },
95
96    /// NDI source became unavailable during operation.
97    ///
98    /// This error indicates that an NDI source that was previously available has gone offline
99    /// or become unreachable. This is different from [`Error::NoSourcesFound`] which indicates
100    /// that no matching sources were ever discovered.
101    ///
102    /// # Example
103    ///
104    /// ```
105    /// # use grafton_ndi::Error;
106    /// match some_operation() {
107    ///     Err(Error::SourceUnavailable { source_name }) => {
108    ///         eprintln!("Lost connection to source: {}", source_name);
109    ///         // Could attempt to reconnect or switch to backup source
110    ///     }
111    ///     Err(e) => eprintln!("Other error: {}", e),
112    ///     Ok(_) => println!("Success"),
113    /// }
114    /// # fn some_operation() -> Result<(), Error> { Ok(()) }
115    /// ```
116    #[error("NDI source became unavailable: {source_name}")]
117    SourceUnavailable {
118        /// Name or identifier of the source that became unavailable
119        source_name: String,
120    },
121
122    /// Receiver disconnected from its NDI source.
123    ///
124    /// This error indicates that an active receiver lost its connection to the source.
125    /// The reason field provides additional context about why the disconnection occurred.
126    ///
127    /// # Example
128    ///
129    /// ```
130    /// # use grafton_ndi::Error;
131    /// match some_operation() {
132    ///     Err(Error::Disconnected { reason }) => {
133    ///         eprintln!("Receiver disconnected: {}", reason);
134    ///         // Could implement automatic reconnection logic
135    ///     }
136    ///     Err(e) => eprintln!("Other error: {}", e),
137    ///     Ok(_) => println!("Success"),
138    /// }
139    /// # fn some_operation() -> Result<(), Error> { Ok(()) }
140    /// ```
141    #[error("Receiver disconnected from source: {reason}")]
142    Disconnected {
143        /// Reason for the disconnection
144        reason: String,
145    },
146
147    /// No NDI sources found matching the specified criteria.
148    ///
149    /// This error is returned when source discovery completes but no sources match
150    /// the requested criteria (e.g., host/IP filter, group filter).
151    ///
152    /// # Example
153    ///
154    /// ```
155    /// # use grafton_ndi::Error;
156    /// match some_operation() {
157    ///     Err(Error::NoSourcesFound { criteria }) => {
158    ///         eprintln!("No sources found matching: {}", criteria);
159    ///         // Could widen search criteria or wait longer
160    ///     }
161    ///     Err(e) => eprintln!("Other error: {}", e),
162    ///     Ok(_) => println!("Success"),
163    /// }
164    /// # fn some_operation() -> Result<(), Error> { Ok(()) }
165    /// ```
166    #[error("No NDI sources found matching: {criteria}")]
167    NoSourcesFound {
168        /// The search criteria that yielded no results
169        criteria: String,
170    },
171
172    /// Failed to spawn a blocking task on the async runtime.
173    ///
174    /// This error occurs when the async runtime fails to spawn a blocking task,
175    /// typically due to task cancellation or runtime shutdown. Unlike the previous
176    /// behavior which panicked on tokio `JoinError`, this error allows callers to
177    /// handle the failure gracefully.
178    ///
179    /// # Example
180    ///
181    /// ```
182    /// use grafton_ndi::Error;
183    ///
184    /// fn handle_error(err: Error) {
185    ///     match err {
186    ///         Error::SpawnFailed(reason) => {
187    ///             eprintln!("Async spawn failed: {}", reason);
188    ///             // Could retry or gracefully shutdown
189    ///         }
190    ///         e => eprintln!("Other error: {}", e),
191    ///     }
192    /// }
193    /// ```
194    #[error("Failed to spawn blocking task: {0}")]
195    SpawnFailed(String),
196}