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    /// The SDK rejected initialization, a handle could not be created, or the
22    /// runtime handle reference count was exhausted. A missing shared library
23    /// can instead prevent the application from loading before Rust code runs.
24    #[error("Failed to initialize the NDI runtime: {0}")]
25    InitializationFailed(String),
26
27    /// A null pointer was returned by the NDI SDK.
28    ///
29    /// Indicates an internal NDI error or invalid operation.
30    #[error("Encountered a null pointer in function: {0}")]
31    NullPointer(String),
32
33    /// Invalid UTF-8 data in a string from the NDI SDK.
34    #[error("Invalid UTF-8 string in data: {0}")]
35    InvalidUtf8(String),
36
37    /// Failed to create a C string due to null bytes.
38    #[error("Invalid CString: {0}")]
39    InvalidCString(#[from] NulError),
40
41    /// Frame capture operation failed.
42    #[error("Failed to capture frame: {0}")]
43    CaptureFailed(String),
44
45    /// Frame data is invalid or corrupted.
46    #[error("Invalid frame data: {0}")]
47    InvalidFrame(String),
48
49    /// PTZ (Pan-Tilt-Zoom) camera control command failed.
50    #[error("PTZ command failed: {0}")]
51    PtzCommandFailed(String),
52
53    /// Configuration parameters are invalid.
54    ///
55    /// This can occur when builder validation fails or conflicting options are set.
56    #[error("Invalid configuration: {0}")]
57    InvalidConfiguration(String),
58
59    /// I/O operation failed.
60    #[error(transparent)]
61    Io(#[from] io::Error),
62
63    /// Operation timed out with generic context.
64    ///
65    /// This is a general timeout error. For frame capture timeouts with retry information,
66    /// see [`Error::FrameTimeout`].
67    #[error("Operation timed out: {0}")]
68    Timeout(String),
69
70    /// The capture wait budget expired without a frame.
71    ///
72    /// Includes the number of capture attempts and elapsed time in the retry
73    /// loop. The attempt count can be zero or one; retries are not guaranteed.
74    /// For async capture, `elapsed`
75    /// excludes time queued before that loop begins.
76    ///
77    /// # Example
78    ///
79    /// ```
80    /// # use grafton_ndi::Error;
81    /// # use std::time::Duration;
82    /// match some_operation() {
83    ///     Err(Error::FrameTimeout { attempts, elapsed }) => {
84    ///         eprintln!("Frame timeout after {} attempts in {:?}", attempts, elapsed);
85    ///         // Could implement retry logic based on attempts count
86    ///     }
87    ///     Err(e) => eprintln!("Other error: {}", e),
88    ///     Ok(_) => println!("Success"),
89    /// }
90    /// # fn some_operation() -> Result<(), Error> { Ok(()) }
91    /// ```
92    #[error("Frame capture timed out after {attempts} attempts ({elapsed:?})")]
93    FrameTimeout {
94        /// Number of capture attempts made before timing out
95        attempts: usize,
96        /// Total elapsed time during capture attempts
97        elapsed: Duration,
98    },
99
100    /// NDI source became unavailable during operation.
101    ///
102    /// This error indicates that an NDI source that was previously available has gone offline
103    /// or become unreachable. This is different from [`Error::NoSourcesFound`] which indicates
104    /// that no matching sources were ever discovered.
105    ///
106    /// # Example
107    ///
108    /// ```
109    /// # use grafton_ndi::Error;
110    /// match some_operation() {
111    ///     Err(Error::SourceUnavailable { source_name }) => {
112    ///         eprintln!("Lost connection to source: {}", source_name);
113    ///         // Could attempt to reconnect or switch to backup source
114    ///     }
115    ///     Err(e) => eprintln!("Other error: {}", e),
116    ///     Ok(_) => println!("Success"),
117    /// }
118    /// # fn some_operation() -> Result<(), Error> { Ok(()) }
119    /// ```
120    #[error("NDI source became unavailable: {source_name}")]
121    SourceUnavailable {
122        /// Name or identifier of the source that became unavailable
123        source_name: String,
124    },
125
126    /// Receiver disconnected from its NDI source.
127    ///
128    /// This error indicates that an active receiver lost its connection to the source.
129    /// The reason field provides additional context about why the disconnection occurred.
130    ///
131    /// # Example
132    ///
133    /// ```
134    /// # use grafton_ndi::Error;
135    /// match some_operation() {
136    ///     Err(Error::Disconnected { reason }) => {
137    ///         eprintln!("Receiver disconnected: {}", reason);
138    ///         // Could implement automatic reconnection logic
139    ///     }
140    ///     Err(e) => eprintln!("Other error: {}", e),
141    ///     Ok(_) => println!("Success"),
142    /// }
143    /// # fn some_operation() -> Result<(), Error> { Ok(()) }
144    /// ```
145    #[error("Receiver disconnected from source: {reason}")]
146    Disconnected {
147        /// Reason for the disconnection
148        reason: String,
149    },
150
151    /// No NDI sources found matching the specified criteria.
152    ///
153    /// This error is returned when source discovery completes but no sources match
154    /// the requested criteria (e.g., host/IP filter, group filter).
155    ///
156    /// # Example
157    ///
158    /// ```
159    /// # use grafton_ndi::Error;
160    /// match some_operation() {
161    ///     Err(Error::NoSourcesFound { criteria }) => {
162    ///         eprintln!("No sources found matching: {}", criteria);
163    ///         // Could widen search criteria or wait longer
164    ///     }
165    ///     Err(e) => eprintln!("Other error: {}", e),
166    ///     Ok(_) => println!("Success"),
167    /// }
168    /// # fn some_operation() -> Result<(), Error> { Ok(()) }
169    /// ```
170    #[error("No NDI sources found matching: {criteria}")]
171    NoSourcesFound {
172        /// The search criteria that yielded no results
173        criteria: String,
174    },
175
176    /// An async blocking operation could not run or finish normally.
177    ///
178    /// Covers a missing Tokio runtime context, task cancellation or join failure,
179    /// and a panic caught while executing a blocking operation. Dropping a future
180    /// discards its result; cancellation does not deliver an error to that caller.
181    ///
182    /// # Example
183    ///
184    /// ```
185    /// use grafton_ndi::Error;
186    ///
187    /// fn handle_error(err: Error) {
188    ///     match err {
189    ///         Error::SpawnFailed(reason) => {
190    ///             eprintln!("Async spawn failed: {}", reason);
191    ///             // Could retry or gracefully shutdown
192    ///         }
193    ///         e => eprintln!("Other error: {}", e),
194    ///     }
195    /// }
196    /// ```
197    #[error("Failed to spawn blocking task: {0}")]
198    SpawnFailed(String),
199}