use windows::core::HRESULT;
pub type HcsResult<T> = Result<T, HcsError>;
#[derive(Debug, thiserror::Error)]
pub enum HcsError {
#[error("compute system not found: {id}")]
NotFound {
id: String,
},
#[error("access denied (caller must be Administrator or Hyper-V Administrator): HRESULT 0x{hresult:08x}")]
AccessDenied {
hresult: i32,
},
#[error("operation is still pending")]
OperationPending,
#[error("system event callback already registered; operation callbacks are unavailable on this system")]
SystemCallbackAlreadySet,
#[error("invalid schema: {message} (HRESULT 0x{hresult:08x})")]
InvalidSchema {
hresult: i32,
message: String,
},
#[error("HCS call failed: {message} (HRESULT 0x{hresult:08x})")]
Other {
hresult: i32,
message: String,
},
#[error("HCS JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("HCS callback thread panicked: {0}")]
CallbackPanic(String),
}
impl HcsError {
#[must_use]
pub fn from_hresult(hr: HRESULT, message: impl Into<String>) -> Self {
match hr.0 {
-0x7FC8_FF00 => Self::NotFound { id: message.into() },
-0x7FFF_FFFB => Self::AccessDenied { hresult: hr.0 },
-0x7FC8_FEFD => Self::OperationPending,
-0x7FC8_FEF7 => Self::SystemCallbackAlreadySet,
-0x7FC8_FFFE | -0x7FC8_FFFD => Self::InvalidSchema {
hresult: hr.0,
message: message.into(),
},
_ => Self::Other {
hresult: hr.0,
message: message.into(),
},
}
}
pub fn check(hr: HRESULT, message: impl FnOnce() -> String) -> HcsResult<()> {
if hr.is_ok() {
Ok(())
} else {
Err(Self::from_hresult(hr, message()))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use windows::core::HRESULT;
fn hr(code: i32) -> HRESULT {
HRESULT(code)
}
#[test]
fn access_denied_maps_specifically() {
let err = HcsError::from_hresult(hr(-0x7FFF_FFFB), "raw");
assert!(matches!(err, HcsError::AccessDenied { .. }));
}
#[test]
fn not_found_maps_specifically() {
let err = HcsError::from_hresult(hr(-0x7FC8_FF00), "my-container");
if let HcsError::NotFound { id } = err {
assert_eq!(id, "my-container");
} else {
panic!("expected NotFound, got {err:?}");
}
}
#[test]
fn unknown_hresult_falls_through_to_other() {
let err = HcsError::from_hresult(hr(-0x1234_5678), "unknown code");
if let HcsError::Other { hresult, message } = err {
assert_eq!(hresult, -0x1234_5678);
assert_eq!(message, "unknown code");
} else {
panic!("expected Other, got {err:?}");
}
}
#[test]
fn check_success_returns_ok() {
let r = HcsError::check(HRESULT(0), || "should not be called".to_string());
assert!(r.is_ok());
}
#[test]
fn check_failure_returns_err() {
let r = HcsError::check(hr(-0x7FFF_FFFB), || "access denied".to_string());
assert!(matches!(r, Err(HcsError::AccessDenied { .. })));
}
}