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
75pub const fn error_name(code: i32) -> &'static str {
81 match code {
82 0 => "VmbErrorSuccess",
83 -1 => "VmbErrorInternalFault",
84 -2 => "VmbErrorApiNotStarted",
85 -3 => "VmbErrorNotFound",
86 -4 => "VmbErrorBadHandle",
87 -5 => "VmbErrorDeviceNotOpen",
88 -6 => "VmbErrorInvalidAccess",
89 -7 => "VmbErrorBadParameter",
90 -8 => "VmbErrorStructSize",
91 -9 => "VmbErrorMoreData",
92 -10 => "VmbErrorWrongType",
93 -11 => "VmbErrorInvalidValue",
94 -12 => "VmbErrorTimeout",
95 -13 => "VmbErrorOther",
96 -14 => "VmbErrorResources",
97 -15 => "VmbErrorInvalidCall",
98 -16 => "VmbErrorNoTL",
99 -17 => "VmbErrorNotImplemented",
100 -18 => "VmbErrorNotSupported",
101 -19 => "VmbErrorIncomplete",
102 -20 => "VmbErrorIO",
103 -21 => "VmbErrorValidValueSetNotPresent",
104 -22 => "VmbErrorGenTLUnspecified",
105 -23 => "VmbErrorUnspecified",
106 -24 => "VmbErrorBusy",
107 -25 => "VmbErrorNoData",
108 -26 => "VmbErrorParsingChunkData",
109 -27 => "VmbErrorInUse",
110 -28 => "VmbErrorUnknown",
111 -29 => "VmbErrorXml",
112 -30 => "VmbErrorNotAvailable",
113 -31 => "VmbErrorNotInitialized",
114 -32 => "VmbErrorInvalidAddress",
115 -33 => "VmbErrorAlready",
116 -34 => "VmbErrorNoChunkData",
117 -35 => "VmbErrorUserCallbackException",
118 -36 => "VmbErrorFeaturesUnavailable",
119 -37 => "VmbErrorTLNotFound",
120 -39 => "VmbErrorAmbiguous",
121 -40 => "VmbErrorRetriesExceeded",
122 -41 => "VmbErrorInsufficientBufferCount",
123 1 => "VmbErrorCustom",
124 _ => "VmbErrorUnrecognized",
125 }
126}
127
128pub fn check(code: i32) -> Result<()> {
134 if code == 0 {
135 Ok(())
136 } else {
137 Err(VmbError::Sdk {
138 code,
139 message: format!("VmbC call failed ({})", error_name(code)),
140 })
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn error_name_maps_known_codes() {
150 assert_eq!(error_name(0), "VmbErrorSuccess");
151 assert_eq!(error_name(-2), "VmbErrorApiNotStarted");
152 assert_eq!(error_name(-41), "VmbErrorInsufficientBufferCount");
153 assert_eq!(error_name(1), "VmbErrorCustom");
154 }
155
156 #[test]
157 fn error_name_unknown_code_has_fallback() {
158 assert_eq!(error_name(12345), "VmbErrorUnrecognized");
159 assert_eq!(error_name(-999), "VmbErrorUnrecognized");
160 }
161
162 #[test]
163 fn error_name_covers_every_documented_code() {
164 let expected: &[(i32, &str)] = &[
169 (0, "VmbErrorSuccess"),
170 (-1, "VmbErrorInternalFault"),
171 (-2, "VmbErrorApiNotStarted"),
172 (-3, "VmbErrorNotFound"),
173 (-4, "VmbErrorBadHandle"),
174 (-5, "VmbErrorDeviceNotOpen"),
175 (-6, "VmbErrorInvalidAccess"),
176 (-7, "VmbErrorBadParameter"),
177 (-8, "VmbErrorStructSize"),
178 (-9, "VmbErrorMoreData"),
179 (-10, "VmbErrorWrongType"),
180 (-11, "VmbErrorInvalidValue"),
181 (-12, "VmbErrorTimeout"),
182 (-13, "VmbErrorOther"),
183 (-14, "VmbErrorResources"),
184 (-15, "VmbErrorInvalidCall"),
185 (-16, "VmbErrorNoTL"),
186 (-17, "VmbErrorNotImplemented"),
187 (-18, "VmbErrorNotSupported"),
188 (-19, "VmbErrorIncomplete"),
189 (-20, "VmbErrorIO"),
190 (-21, "VmbErrorValidValueSetNotPresent"),
191 (-22, "VmbErrorGenTLUnspecified"),
192 (-23, "VmbErrorUnspecified"),
193 (-24, "VmbErrorBusy"),
194 (-25, "VmbErrorNoData"),
195 (-26, "VmbErrorParsingChunkData"),
196 (-27, "VmbErrorInUse"),
197 (-28, "VmbErrorUnknown"),
198 (-29, "VmbErrorXml"),
199 (-30, "VmbErrorNotAvailable"),
200 (-31, "VmbErrorNotInitialized"),
201 (-32, "VmbErrorInvalidAddress"),
202 (-33, "VmbErrorAlready"),
203 (-34, "VmbErrorNoChunkData"),
204 (-35, "VmbErrorUserCallbackException"),
205 (-36, "VmbErrorFeaturesUnavailable"),
206 (-37, "VmbErrorTLNotFound"),
207 (-39, "VmbErrorAmbiguous"),
208 (-40, "VmbErrorRetriesExceeded"),
209 (-41, "VmbErrorInsufficientBufferCount"),
210 (1, "VmbErrorCustom"),
211 ];
212 for (code, name) in expected {
213 assert_eq!(error_name(*code), *name, "wrong name for code {code}");
214 }
215 assert_eq!(error_name(-38), "VmbErrorUnrecognized");
218 }
219
220 #[test]
221 fn display_includes_error_name() {
222 let err = VmbError::Sdk {
223 code: -4,
224 message: "bad handle".to_string(),
225 };
226 let s = format!("{err}");
227 assert!(s.contains("VmbErrorBadHandle"));
228 assert!(s.contains("bad handle"));
229 }
230
231 #[test]
232 fn check_success_is_ok() {
233 assert!(check(0).is_ok());
234 }
235
236 #[test]
237 fn check_error_is_sdk_with_code_and_name() {
238 match check(-4) {
239 Err(VmbError::Sdk { code, message }) => {
240 assert_eq!(code, -4);
241 assert!(message.contains("VmbErrorBadHandle"));
242 }
243 other => panic!("expected Err(Sdk), got {other:?}"),
244 }
245 }
246
247 #[test]
248 fn display_invalid_string_includes_context() {
249 let err = VmbError::InvalidString {
250 context: "camera_id",
251 };
252 assert!(format!("{err}").contains("camera_id"));
253 }
254
255 #[test]
256 fn display_frame_too_small_includes_counts() {
257 let err = VmbError::FrameTooSmall {
258 expected: 100,
259 actual: 80,
260 };
261 let s = format!("{err}");
262 assert!(s.contains("100"));
263 assert!(s.contains("80"));
264 }
265
266 #[test]
267 fn display_io_includes_path() {
268 let err = VmbError::Io {
269 path: PathBuf::from("/tmp/does-not-exist.xml"),
270 source: std::io::Error::other("boom"),
271 };
272 let s = format!("{err}");
273 assert!(s.contains("does-not-exist.xml"));
274 }
275
276 #[test]
277 fn already_started_and_not_started_display() {
278 assert!(format!("{}", VmbError::AlreadyStarted).contains("already started"));
279 assert!(format!("{}", VmbError::NotStarted).contains("not been started"));
280 }
281
282 #[test]
283 fn capture_already_running_display() {
284 assert!(format!("{}", VmbError::CaptureAlreadyRunning).contains("already running"));
285 }
286}