use std::path::PathBuf;
use thiserror::Error;
use crate::Result;
#[derive(Debug, Error)]
pub enum VmbError {
#[error("Vimba SDK error {code} ({}): {message}", error_name(*code))]
Sdk {
code: i32,
message: String,
},
#[error("Vimba X runtime has not been started")]
NotStarted,
#[error("Vimba X runtime is already started (singleton violation)")]
AlreadyStarted,
#[error("I/O error for {}: {source}", path.display())]
Io {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("invalid string (non-UTF-8 or interior nul) in {context}")]
InvalidString {
context: &'static str,
},
#[error("capture is already running on this camera")]
CaptureAlreadyRunning,
#[error("frame too small: expected {expected} bytes, got {actual}")]
FrameTooSmall {
expected: usize,
actual: usize,
},
#[error("failed to load Vimba X runtime: {message}")]
LoadFailed {
message: String,
},
}
pub const fn error_name(code: i32) -> &'static str {
match code {
0 => "VmbErrorSuccess",
-1 => "VmbErrorInternalFault",
-2 => "VmbErrorApiNotStarted",
-3 => "VmbErrorNotFound",
-4 => "VmbErrorBadHandle",
-5 => "VmbErrorDeviceNotOpen",
-6 => "VmbErrorInvalidAccess",
-7 => "VmbErrorBadParameter",
-8 => "VmbErrorStructSize",
-9 => "VmbErrorMoreData",
-10 => "VmbErrorWrongType",
-11 => "VmbErrorInvalidValue",
-12 => "VmbErrorTimeout",
-13 => "VmbErrorOther",
-14 => "VmbErrorResources",
-15 => "VmbErrorInvalidCall",
-16 => "VmbErrorNoTL",
-17 => "VmbErrorNotImplemented",
-18 => "VmbErrorNotSupported",
-19 => "VmbErrorIncomplete",
-20 => "VmbErrorIO",
-21 => "VmbErrorValidValueSetNotPresent",
-22 => "VmbErrorGenTLUnspecified",
-23 => "VmbErrorUnspecified",
-24 => "VmbErrorBusy",
-25 => "VmbErrorNoData",
-26 => "VmbErrorParsingChunkData",
-27 => "VmbErrorInUse",
-28 => "VmbErrorUnknown",
-29 => "VmbErrorXml",
-30 => "VmbErrorNotAvailable",
-31 => "VmbErrorNotInitialized",
-32 => "VmbErrorInvalidAddress",
-33 => "VmbErrorAlready",
-34 => "VmbErrorNoChunkData",
-35 => "VmbErrorUserCallbackException",
-36 => "VmbErrorFeaturesUnavailable",
-37 => "VmbErrorTLNotFound",
-39 => "VmbErrorAmbiguous",
-40 => "VmbErrorRetriesExceeded",
-41 => "VmbErrorInsufficientBufferCount",
1 => "VmbErrorCustom",
_ => "VmbErrorUnrecognized",
}
}
pub fn check(code: i32) -> Result<()> {
if code == 0 {
Ok(())
} else {
Err(VmbError::Sdk {
code,
message: format!("VmbC call failed ({})", error_name(code)),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_name_maps_known_codes() {
assert_eq!(error_name(0), "VmbErrorSuccess");
assert_eq!(error_name(-2), "VmbErrorApiNotStarted");
assert_eq!(error_name(-41), "VmbErrorInsufficientBufferCount");
assert_eq!(error_name(1), "VmbErrorCustom");
}
#[test]
fn error_name_unknown_code_has_fallback() {
assert_eq!(error_name(12345), "VmbErrorUnrecognized");
assert_eq!(error_name(-999), "VmbErrorUnrecognized");
}
#[test]
fn error_name_covers_every_documented_code() {
let expected: &[(i32, &str)] = &[
(0, "VmbErrorSuccess"),
(-1, "VmbErrorInternalFault"),
(-2, "VmbErrorApiNotStarted"),
(-3, "VmbErrorNotFound"),
(-4, "VmbErrorBadHandle"),
(-5, "VmbErrorDeviceNotOpen"),
(-6, "VmbErrorInvalidAccess"),
(-7, "VmbErrorBadParameter"),
(-8, "VmbErrorStructSize"),
(-9, "VmbErrorMoreData"),
(-10, "VmbErrorWrongType"),
(-11, "VmbErrorInvalidValue"),
(-12, "VmbErrorTimeout"),
(-13, "VmbErrorOther"),
(-14, "VmbErrorResources"),
(-15, "VmbErrorInvalidCall"),
(-16, "VmbErrorNoTL"),
(-17, "VmbErrorNotImplemented"),
(-18, "VmbErrorNotSupported"),
(-19, "VmbErrorIncomplete"),
(-20, "VmbErrorIO"),
(-21, "VmbErrorValidValueSetNotPresent"),
(-22, "VmbErrorGenTLUnspecified"),
(-23, "VmbErrorUnspecified"),
(-24, "VmbErrorBusy"),
(-25, "VmbErrorNoData"),
(-26, "VmbErrorParsingChunkData"),
(-27, "VmbErrorInUse"),
(-28, "VmbErrorUnknown"),
(-29, "VmbErrorXml"),
(-30, "VmbErrorNotAvailable"),
(-31, "VmbErrorNotInitialized"),
(-32, "VmbErrorInvalidAddress"),
(-33, "VmbErrorAlready"),
(-34, "VmbErrorNoChunkData"),
(-35, "VmbErrorUserCallbackException"),
(-36, "VmbErrorFeaturesUnavailable"),
(-37, "VmbErrorTLNotFound"),
(-39, "VmbErrorAmbiguous"),
(-40, "VmbErrorRetriesExceeded"),
(-41, "VmbErrorInsufficientBufferCount"),
(1, "VmbErrorCustom"),
];
for (code, name) in expected {
assert_eq!(error_name(*code), *name, "wrong name for code {code}");
}
assert_eq!(error_name(-38), "VmbErrorUnrecognized");
}
#[test]
fn display_includes_error_name() {
let err = VmbError::Sdk {
code: -4,
message: "bad handle".to_string(),
};
let s = format!("{err}");
assert!(s.contains("VmbErrorBadHandle"));
assert!(s.contains("bad handle"));
}
#[test]
fn check_success_is_ok() {
assert!(check(0).is_ok());
}
#[test]
fn check_error_is_sdk_with_code_and_name() {
match check(-4) {
Err(VmbError::Sdk { code, message }) => {
assert_eq!(code, -4);
assert!(message.contains("VmbErrorBadHandle"));
}
other => panic!("expected Err(Sdk), got {other:?}"),
}
}
#[test]
fn display_invalid_string_includes_context() {
let err = VmbError::InvalidString {
context: "camera_id",
};
assert!(format!("{err}").contains("camera_id"));
}
#[test]
fn display_frame_too_small_includes_counts() {
let err = VmbError::FrameTooSmall {
expected: 100,
actual: 80,
};
let s = format!("{err}");
assert!(s.contains("100"));
assert!(s.contains("80"));
}
#[test]
fn display_io_includes_path() {
let err = VmbError::Io {
path: PathBuf::from("/tmp/does-not-exist.xml"),
source: std::io::Error::other("boom"),
};
let s = format!("{err}");
assert!(s.contains("does-not-exist.xml"));
}
#[test]
fn already_started_and_not_started_display() {
assert!(format!("{}", VmbError::AlreadyStarted).contains("already started"));
assert!(format!("{}", VmbError::NotStarted).contains("not been started"));
}
#[test]
fn capture_already_running_display() {
assert!(format!("{}", VmbError::CaptureAlreadyRunning).contains("already running"));
}
#[test]
fn load_failed_display_includes_message() {
let err = VmbError::LoadFailed {
message: "libVmbC.so: cannot open shared object file".to_string(),
};
let s = format!("{err}");
assert!(s.contains("libVmbC.so"), "loader message missing from {s}");
assert!(s.contains("load"), "error descriptor missing from {s}");
}
}