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}