use crate::*;
use std::convert::TryInto;
use bindings::{
Windows::Win32::Automation::{GetErrorInfo, SetErrorInfo, BSTR},
Windows::Win32::WinRT::{ILanguageExceptionErrorInfo2, IRestrictedErrorInfo},
};
#[derive(Clone, PartialEq)]
pub struct Error {
code: ErrorCode,
info: Option<IRestrictedErrorInfo>,
}
impl Error {
pub fn new(code: ErrorCode, message: &str) -> Self {
let message: HString = message.into();
unsafe {
let _ = RoOriginateError(code, message.abi() as _);
}
let mut info = None;
let info = unsafe {
GetErrorInfo(0, &mut info)
.and_some(info)
.and_then(|e| e.cast())
.ok()
};
Self { code, info }
}
pub fn fast_error(code: ErrorCode) -> Self {
Self { code, info: None }
}
pub fn code(&self) -> ErrorCode {
self.code
}
pub fn info(&self) -> &Option<IRestrictedErrorInfo> {
&self.info
}
pub fn message(&self) -> String {
if let Some(info) = &self.info {
let mut fallback = BSTR::default();
let mut message = BSTR::default();
let mut unused = BSTR::default();
let mut code = ErrorCode(0);
unsafe {
let _ = info.GetErrorDetails(&mut fallback, &mut code, &mut message, &mut unused);
}
let message = if !message.is_empty() {
message
} else {
fallback
};
let message: String = message.try_into().unwrap_or_default();
if self.code == code {
return message.trim_end().to_owned();
}
}
self.code.message()
}
}
impl std::convert::From<Error> for ErrorCode {
fn from(error: Error) -> Self {
let code = error.code;
let info = error.info.and_then(|info| info.cast().ok());
unsafe {
let _ = SetErrorInfo(0, info);
}
code
}
}
impl std::convert::From<ErrorCode> for Error {
fn from(code: ErrorCode) -> Self {
let mut info = None;
let info: Option<IRestrictedErrorInfo> = unsafe {
GetErrorInfo(0, &mut info)
.and_some(info)
.and_then(|e| e.cast())
.ok()
};
if let Some(info) = info {
if let Ok(capture) = info.cast::<ILanguageExceptionErrorInfo2>() {
unsafe {
let _ = capture.CapturePropagationContext(None);
}
}
return Self {
code,
info: Some(info),
};
}
let mut result = None;
unsafe {
let _ = GetErrorInfo(0, &mut result);
}
if let Some(info) = result {
let mut message = BSTR::default();
unsafe {
let _ = info.GetDescription(&mut message);
}
let message: String = message.try_into().unwrap_or_default();
Self::new(code, &message)
} else {
Self::new(code, "")
}
}
}
impl std::fmt::Debug for Error {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fmt.debug_struct("Error")
.field("code", &format_args!("{:#010X}", self.code.0))
.field("message", &self.message())
.finish()
}
}
impl std::fmt::Display for Error {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fmt.write_str(&self.message())
}
}
impl std::error::Error for Error {}
demand_load! {
"combase.dll" {
fn RoOriginateError(code: ErrorCode, message: RawPtr) -> i32;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_message() {
let code = Error::fast_error(ErrorCode::from_win32(0));
assert_eq!(code.message(), "The operation completed successfully.");
let code = Error::fast_error(ErrorCode::from_win32(997));
assert_eq!(code.message(), "Overlapped I/O operation is in progress.");
}
}