use crate::com_memory::ComWideString;
use thiserror::Error;
use windows_sys::Win32::Foundation::{
CO_E_NOTINITIALIZED, E_ABORT, E_ACCESSDENIED, E_FAIL, E_INVALIDARG, E_NOINTERFACE, E_NOTIMPL,
E_OUTOFMEMORY, E_POINTER, E_UNEXPECTED, RPC_E_CHANGED_MODE, RPC_E_DISCONNECTED,
RPC_E_WRONG_THREAD,
};
use windows_sys::core::HRESULT;
use wslcsdk_sys::errors::*;
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum WslcDomainError {
#[error("镜像未找到: {0}")]
ImageNotFound(String),
#[error("容器 ID 前缀存在歧义,匹配到多个容器: {0}")]
ContainerPrefixAmbiguous(String),
#[error("容器未找到: {0}")]
ContainerNotFound(String),
#[error("存储卷未找到: {0}")]
VolumeNotFound(String),
#[error("容器未处于运行状态: {0}")]
ContainerNotRunning(String),
#[error("容器已处于运行状态: {0}")]
ContainerAlreadyRunning(String),
#[error("会话名称属于系统保留名称: {0}")]
SessionReserved(String),
#[error("会话名称非法: {0}")]
InvalidSessionName(String),
#[error("网络未找到: {0}")]
NetworkNotFound(String),
#[error("Windows Update 组件搜索失败: {0}")]
WindowsUpdateSearchFailed(String),
#[error("WSLC SDK 版本落后,需要更新系统组件: {0}")]
SdkUpdateNeeded(String),
#[error("容器功能被系统或安全策略禁用: {0}")]
ContainerDisabled(String),
#[error("镜像仓库访问被安全策略拦截: {0}")]
RegistryBlockedByPolicy(String),
#[error("存储卷当前不可用或被独占锁定: {0}")]
VolumeNotAvailable(String),
#[error("会话未找到: {0}")]
SessionNotFound(String),
#[error("WSL 虚拟机未处于运行状态: {0}")]
VmNotRunning(String),
#[error("事件队列溢出,部分事件已丢失: {0}")]
EventsLost(String),
#[error("事件流已终止: {0}")]
EventStreamFinished(String),
#[error("容器已被删除: {0}")]
ContainerDeleted(String),
}
impl WslcDomainError {
pub fn from_hresult(hr: HRESULT, context: impl Into<String>) -> Option<Self> {
let message = context.into();
Some(match hr {
WSLC_E_IMAGE_NOT_FOUND => Self::ImageNotFound(message),
WSLC_E_CONTAINER_PREFIX_AMBIGUOUS => Self::ContainerPrefixAmbiguous(message),
WSLC_E_CONTAINER_NOT_FOUND => Self::ContainerNotFound(message),
WSLC_E_VOLUME_NOT_FOUND => Self::VolumeNotFound(message),
WSLC_E_CONTAINER_NOT_RUNNING => Self::ContainerNotRunning(message),
WSLC_E_CONTAINER_IS_RUNNING => Self::ContainerAlreadyRunning(message),
WSLC_E_SESSION_RESERVED => Self::SessionReserved(message),
WSLC_E_INVALID_SESSION_NAME => Self::InvalidSessionName(message),
WSLC_E_NETWORK_NOT_FOUND => Self::NetworkNotFound(message),
WSLC_E_WU_SEARCH_FAILED => Self::WindowsUpdateSearchFailed(message),
WSLC_E_SDK_UPDATE_NEEDED => Self::SdkUpdateNeeded(message),
WSLC_E_CONTAINER_DISABLED => Self::ContainerDisabled(message),
WSLC_E_REGISTRY_BLOCKED_BY_POLICY => Self::RegistryBlockedByPolicy(message),
WSLC_E_VOLUME_NOT_AVAILABLE => Self::VolumeNotAvailable(message),
WSLC_E_SESSION_NOT_FOUND => Self::SessionNotFound(message),
WSLC_E_VM_NOT_RUNNING => Self::VmNotRunning(message),
WSLC_E_EVENTS_LOST => Self::EventsLost(message),
WSLC_E_EVENT_STREAM_FINISHED => Self::EventStreamFinished(message),
WSLC_E_CONTAINER_DELETED => Self::ContainerDeleted(message),
_ => return None,
})
}
}
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum WslcError {
#[error(transparent)]
Domain(#[from] WslcDomainError),
#[error("Windows 调用失败,HRESULT: 0x{0:08X},详情: {1}")]
Hresult(u32, String),
#[error("SDK 返回非预期结果: {0}")]
UnexpectedSdkResult(String),
#[error("空指针或无效句柄")]
InvalidHandle,
#[error("文本编码转换失败: {0}")]
Utf8Error(String),
#[error("字符串中包含非法空字符 (Nul Byte): {0}")]
NulError(String),
#[error("JSON 序列化或反序列化失败: {0}")]
JsonError(String),
#[error("异步任务执行失败: {0}")]
TaskJoin(String),
#[error("{0}已被消费,不可重复获取")]
AlreadyConsumed(String),
#[error("内部通知通道异常终止: {0}")]
ChannelTerminated(String),
#[error("I/O 操作失败: {0}")]
Io(String),
#[error("配置或环境变量非法: {0}")]
InvalidConfiguration(String),
}
impl WslcError {
pub fn from_hresult(hr: HRESULT, context_desc: impl Into<String>) -> Self {
let message = context_desc.into();
if let Some(domain) = WslcDomainError::from_hresult(hr, message.clone()) {
return Self::Domain(domain);
}
let code = hr as u32;
match hresult_name(code) {
Some(name) => Self::Hresult(code, format!("{name},{message}")),
None => Self::Hresult(code, message),
}
}
pub(crate) fn check_hr(hr: HRESULT, context_desc: impl Into<String>) -> Result<(), Self> {
if hr >= 0 {
Ok(())
} else {
Err(Self::from_hresult(hr, context_desc))
}
}
pub(crate) unsafe fn from_hresult_and_raw_msg(hr: HRESULT, msg_ptr: *mut u16) -> Self {
let message = unsafe { ComWideString::from_raw(msg_ptr) }
.map(|msg| msg.to_string_lossy())
.unwrap_or_default();
Self::from_hresult(hr, message)
}
pub(crate) unsafe fn check(hr: HRESULT, msg_ptr: *mut u16) -> Result<(), Self> {
if hr >= 0 {
drop(unsafe { ComWideString::from_raw(msg_ptr) });
Ok(())
} else {
Err(unsafe { Self::from_hresult_and_raw_msg(hr, msg_ptr) })
}
}
}
fn hresult_name(code: u32) -> Option<&'static str> {
Some(match code as HRESULT {
E_ACCESSDENIED => "E_ACCESSDENIED (拒绝访问)",
E_INVALIDARG => "E_INVALIDARG (参数或标志位不合法)",
E_OUTOFMEMORY => "E_OUTOFMEMORY (内存不足)",
E_FAIL => "E_FAIL (未指定的失败)",
E_NOTIMPL => "E_NOTIMPL (未实现该功能)",
E_NOINTERFACE => "E_NOINTERFACE (不支持所请求的接口)",
E_POINTER => "E_POINTER (无效指针)",
E_UNEXPECTED => "E_UNEXPECTED (非预期状态)",
E_ABORT => "E_ABORT (操作已中止)",
CO_E_NOTINITIALIZED => "CO_E_NOTINITIALIZED (COM 尚未初始化)",
RPC_E_CHANGED_MODE => "RPC_E_CHANGED_MODE (COM 套间模型冲突,当前线程已被初始化为 STA)",
RPC_E_WRONG_THREAD => "RPC_E_WRONG_THREAD (在错误的线程上调用)",
RPC_E_DISCONNECTED => "RPC_E_DISCONNECTED (对象已与调用方断开连接)",
_ => return None,
})
}
#[cfg(test)]
mod tests {
use super::*;
const ALL_DOMAIN_CODES: [HRESULT; 19] = [
WSLC_E_IMAGE_NOT_FOUND,
WSLC_E_CONTAINER_PREFIX_AMBIGUOUS,
WSLC_E_CONTAINER_NOT_FOUND,
WSLC_E_VOLUME_NOT_FOUND,
WSLC_E_CONTAINER_NOT_RUNNING,
WSLC_E_CONTAINER_IS_RUNNING,
WSLC_E_SESSION_RESERVED,
WSLC_E_INVALID_SESSION_NAME,
WSLC_E_NETWORK_NOT_FOUND,
WSLC_E_WU_SEARCH_FAILED,
WSLC_E_SDK_UPDATE_NEEDED,
WSLC_E_CONTAINER_DISABLED,
WSLC_E_REGISTRY_BLOCKED_BY_POLICY,
WSLC_E_VOLUME_NOT_AVAILABLE,
WSLC_E_SESSION_NOT_FOUND,
WSLC_E_VM_NOT_RUNNING,
WSLC_E_EVENTS_LOST,
WSLC_E_EVENT_STREAM_FINISHED,
WSLC_E_CONTAINER_DELETED,
];
#[test]
fn test_domain_error_mapping_with_raw_message() {
unsafe {
let err = WslcError::from_hresult_and_raw_msg(
WSLC_E_CONTAINER_NOT_FOUND,
std::ptr::null_mut(),
);
assert_eq!(
err,
WslcError::Domain(WslcDomainError::ContainerNotFound(String::new()))
);
let err2 =
WslcError::from_hresult_and_raw_msg(WSLC_E_IMAGE_NOT_FOUND, std::ptr::null_mut());
assert_eq!(
err2,
WslcError::Domain(WslcDomainError::ImageNotFound(String::new()))
);
let err3 =
WslcError::from_hresult_and_raw_msg(WSLC_E_VM_NOT_RUNNING, std::ptr::null_mut());
assert_eq!(
err3,
WslcError::Domain(WslcDomainError::VmNotRunning(String::new()))
);
}
let err_direct = WslcError::from_hresult(WSLC_E_CONTAINER_NOT_FOUND, "测试上下文");
assert_eq!(
err_direct,
WslcError::Domain(WslcDomainError::ContainerNotFound("测试上下文".to_string()))
);
}
#[test]
fn test_every_official_code_lands_in_domain_layer() {
for code in ALL_DOMAIN_CODES {
let err = WslcError::from_hresult(code, "上下文");
assert!(
matches!(err, WslcError::Domain(_)),
"错误码 0x{:08X} 未映射为领域错误: {err:?}",
code as u32
);
}
assert!(WslcDomainError::from_hresult(0x8004_9999_u32 as HRESULT, "x").is_none());
}
#[test]
fn test_standard_hresult_reported_by_name() {
let err = WslcError::from_hresult(E_INVALIDARG, "设置容器标志位失败");
match err {
WslcError::Hresult(code, detail) => {
assert_eq!(code, E_INVALIDARG as u32);
assert!(detail.contains("E_INVALIDARG"), "实际描述: {detail}");
assert!(detail.contains("设置容器标志位失败"), "实际描述: {detail}");
}
other => panic!("预期返回 Hresult 变体,实际为: {other:?}"),
}
}
#[test]
fn test_unknown_hresult_keeps_raw_code_and_context() {
let err = WslcError::from_hresult(0x8004_9999_u32 as HRESULT, "未知调用失败");
match err {
WslcError::Hresult(code, detail) => {
assert_eq!(code, 0x8004_9999);
assert_eq!(detail, "未知调用失败");
}
other => panic!("预期返回 Hresult 变体,实际为: {other:?}"),
}
}
#[test]
fn test_check_hr_boundary() {
assert!(WslcError::check_hr(0, "S_OK").is_ok());
assert!(WslcError::check_hr(1, "S_FALSE 亦视为成功").is_ok());
assert!(WslcError::check_hr(WSLC_E_CONTAINER_NOT_FOUND, "失败").is_err());
}
#[test]
fn test_hresult_name_coverage() {
for code in [
E_ACCESSDENIED,
E_FAIL,
CO_E_NOTINITIALIZED,
RPC_E_CHANGED_MODE,
] {
assert!(
hresult_name(code as u32).is_some(),
"0x{:08X} 应有可读名称",
code as u32
);
}
assert!(hresult_name(0x8004_9999).is_none());
}
#[test]
fn test_business_terminal_states_are_distinguishable_from_task_failure() {
let consumed = WslcError::AlreadyConsumed("进程退出通知".to_string());
assert!(matches!(consumed, WslcError::AlreadyConsumed(_)));
assert!(!matches!(consumed, WslcError::TaskJoin(_)));
assert_eq!(
consumed.to_string(),
"进程退出通知已被消费,不可重复获取",
"错误描述须指出被消费的资源"
);
let channel = WslcError::ChannelTerminated("进程退出通知通道".to_string());
assert!(matches!(channel, WslcError::ChannelTerminated(_)));
assert!(!matches!(channel, WslcError::TaskJoin(_)));
assert_ne!(consumed, channel);
assert_ne!(consumed, WslcError::TaskJoin("x".to_string()));
}
}