1use std::path::PathBuf;
15
16use thiserror::Error;
17
18use crate::Result;
19
20#[derive(Debug, Error)]
22pub enum VmbError {
23 #[error("Vimba SDK error {code} ({}): {message}", error_name(*code))]
25 Sdk {
26 code: i32,
28 message: String,
30 },
31
32 #[error("Vimba X runtime has not been started")]
34 NotStarted,
35
36 #[error("Vimba X runtime is already started (singleton violation)")]
40 AlreadyStarted,
41
42 #[error("I/O error for {}: {source}", path.display())]
44 Io {
45 path: PathBuf,
47 #[source]
49 source: std::io::Error,
50 },
51
52 #[error("invalid string (non-UTF-8 or interior nul) in {context}")]
55 InvalidString {
56 context: &'static str,
58 },
59
60 #[error("capture is already running on this camera")]
63 CaptureAlreadyRunning,
64
65 #[error("frame too small: expected {expected} bytes, got {actual}")]
67 FrameTooSmall {
68 expected: usize,
70 actual: usize,
72 },
73
74 #[error("failed to load Vimba X runtime: {message}")]
78 LoadFailed {
79 message: String,
82 },
83}
84
85pub const fn error_name(code: i32) -> &'static str {
91 match code {
92 0 => "VmbErrorSuccess",
93 -1 => "VmbErrorInternalFault",
94 -2 => "VmbErrorApiNotStarted",
95 -3 => "VmbErrorNotFound",
96 -4 => "VmbErrorBadHandle",
97 -5 => "VmbErrorDeviceNotOpen",
98 -6 => "VmbErrorInvalidAccess",
99 -7 => "VmbErrorBadParameter",
100 -8 => "VmbErrorStructSize",
101 -9 => "VmbErrorMoreData",
102 -10 => "VmbErrorWrongType",
103 -11 => "VmbErrorInvalidValue",
104 -12 => "VmbErrorTimeout",
105 -13 => "VmbErrorOther",
106 -14 => "VmbErrorResources",
107 -15 => "VmbErrorInvalidCall",
108 -16 => "VmbErrorNoTL",
109 -17 => "VmbErrorNotImplemented",
110 -18 => "VmbErrorNotSupported",
111 -19 => "VmbErrorIncomplete",
112 -20 => "VmbErrorIO",
113 -21 => "VmbErrorValidValueSetNotPresent",
114 -22 => "VmbErrorGenTLUnspecified",
115 -23 => "VmbErrorUnspecified",
116 -24 => "VmbErrorBusy",
117 -25 => "VmbErrorNoData",
118 -26 => "VmbErrorParsingChunkData",
119 -27 => "VmbErrorInUse",
120 -28 => "VmbErrorUnknown",
121 -29 => "VmbErrorXml",
122 -30 => "VmbErrorNotAvailable",
123 -31 => "VmbErrorNotInitialized",
124 -32 => "VmbErrorInvalidAddress",
125 -33 => "VmbErrorAlready",
126 -34 => "VmbErrorNoChunkData",
127 -35 => "VmbErrorUserCallbackException",
128 -36 => "VmbErrorFeaturesUnavailable",
129 -37 => "VmbErrorTLNotFound",
130 -39 => "VmbErrorAmbiguous",
131 -40 => "VmbErrorRetriesExceeded",
132 -41 => "VmbErrorInsufficientBufferCount",
133 1 => "VmbErrorCustom",
134 _ => "VmbErrorUnrecognized",
135 }
136}
137
138pub fn check(code: i32) -> Result<()> {
144 if code == 0 {
145 Ok(())
146 } else {
147 Err(VmbError::Sdk {
148 code,
149 message: format!("VmbC call failed ({})", error_name(code)),
150 })
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn error_name_maps_known_codes() {
160 assert_eq!(error_name(0), "VmbErrorSuccess");
161 assert_eq!(error_name(-2), "VmbErrorApiNotStarted");
162 assert_eq!(error_name(-41), "VmbErrorInsufficientBufferCount");
163 assert_eq!(error_name(1), "VmbErrorCustom");
164 }
165
166 #[test]
167 fn error_name_unknown_code_has_fallback() {
168 assert_eq!(error_name(12345), "VmbErrorUnrecognized");
169 assert_eq!(error_name(-999), "VmbErrorUnrecognized");
170 }
171
172 #[test]
173 fn error_name_covers_every_documented_code() {
174 let expected: &[(i32, &str)] = &[
179 (0, "VmbErrorSuccess"),
180 (-1, "VmbErrorInternalFault"),
181 (-2, "VmbErrorApiNotStarted"),
182 (-3, "VmbErrorNotFound"),
183 (-4, "VmbErrorBadHandle"),
184 (-5, "VmbErrorDeviceNotOpen"),
185 (-6, "VmbErrorInvalidAccess"),
186 (-7, "VmbErrorBadParameter"),
187 (-8, "VmbErrorStructSize"),
188 (-9, "VmbErrorMoreData"),
189 (-10, "VmbErrorWrongType"),
190 (-11, "VmbErrorInvalidValue"),
191 (-12, "VmbErrorTimeout"),
192 (-13, "VmbErrorOther"),
193 (-14, "VmbErrorResources"),
194 (-15, "VmbErrorInvalidCall"),
195 (-16, "VmbErrorNoTL"),
196 (-17, "VmbErrorNotImplemented"),
197 (-18, "VmbErrorNotSupported"),
198 (-19, "VmbErrorIncomplete"),
199 (-20, "VmbErrorIO"),
200 (-21, "VmbErrorValidValueSetNotPresent"),
201 (-22, "VmbErrorGenTLUnspecified"),
202 (-23, "VmbErrorUnspecified"),
203 (-24, "VmbErrorBusy"),
204 (-25, "VmbErrorNoData"),
205 (-26, "VmbErrorParsingChunkData"),
206 (-27, "VmbErrorInUse"),
207 (-28, "VmbErrorUnknown"),
208 (-29, "VmbErrorXml"),
209 (-30, "VmbErrorNotAvailable"),
210 (-31, "VmbErrorNotInitialized"),
211 (-32, "VmbErrorInvalidAddress"),
212 (-33, "VmbErrorAlready"),
213 (-34, "VmbErrorNoChunkData"),
214 (-35, "VmbErrorUserCallbackException"),
215 (-36, "VmbErrorFeaturesUnavailable"),
216 (-37, "VmbErrorTLNotFound"),
217 (-39, "VmbErrorAmbiguous"),
218 (-40, "VmbErrorRetriesExceeded"),
219 (-41, "VmbErrorInsufficientBufferCount"),
220 (1, "VmbErrorCustom"),
221 ];
222 for (code, name) in expected {
223 assert_eq!(error_name(*code), *name, "wrong name for code {code}");
224 }
225 assert_eq!(error_name(-38), "VmbErrorUnrecognized");
228 }
229
230 #[test]
231 fn display_includes_error_name() {
232 let err = VmbError::Sdk {
233 code: -4,
234 message: "bad handle".to_string(),
235 };
236 let s = format!("{err}");
237 assert!(s.contains("VmbErrorBadHandle"));
238 assert!(s.contains("bad handle"));
239 }
240
241 #[test]
242 fn check_success_is_ok() {
243 assert!(check(0).is_ok());
244 }
245
246 #[test]
247 fn check_error_is_sdk_with_code_and_name() {
248 match check(-4) {
249 Err(VmbError::Sdk { code, message }) => {
250 assert_eq!(code, -4);
251 assert!(message.contains("VmbErrorBadHandle"));
252 }
253 other => panic!("expected Err(Sdk), got {other:?}"),
254 }
255 }
256
257 #[test]
258 fn display_invalid_string_includes_context() {
259 let err = VmbError::InvalidString {
260 context: "camera_id",
261 };
262 assert!(format!("{err}").contains("camera_id"));
263 }
264
265 #[test]
266 fn display_frame_too_small_includes_counts() {
267 let err = VmbError::FrameTooSmall {
268 expected: 100,
269 actual: 80,
270 };
271 let s = format!("{err}");
272 assert!(s.contains("100"));
273 assert!(s.contains("80"));
274 }
275
276 #[test]
277 fn display_io_includes_path() {
278 let err = VmbError::Io {
279 path: PathBuf::from("/tmp/does-not-exist.xml"),
280 source: std::io::Error::other("boom"),
281 };
282 let s = format!("{err}");
283 assert!(s.contains("does-not-exist.xml"));
284 }
285
286 #[test]
287 fn already_started_and_not_started_display() {
288 assert!(format!("{}", VmbError::AlreadyStarted).contains("already started"));
289 assert!(format!("{}", VmbError::NotStarted).contains("not been started"));
290 }
291
292 #[test]
293 fn capture_already_running_display() {
294 assert!(format!("{}", VmbError::CaptureAlreadyRunning).contains("already running"));
295 }
296
297 #[test]
298 fn load_failed_display_includes_message() {
299 let err = VmbError::LoadFailed {
300 message: "libVmbC.so: cannot open shared object file".to_string(),
301 };
302 let s = format!("{err}");
303 assert!(s.contains("libVmbC.so"), "loader message missing from {s}");
304 assert!(s.contains("load"), "error descriptor missing from {s}");
305 }
306}