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}