use std::fmt;
use failure::Fail;
use winapi::shared::minwindef::DWORD;
use winapi::shared::winerror::{FACILITY_WIN32, HRESULT, HRESULT_FROM_WIN32, SUCCEEDED};
use winapi::um::errhandlingapi::GetLastError;
#[derive(Clone, Debug, Default, Eq, Fail, PartialEq)]
pub struct Win32Error {
pub code: DWORD,
pub function: Option<&'static str>,
pub file_line: Option<FileLine>,
}
#[derive(Clone, Debug, Default, Eq, Fail, PartialEq)]
pub struct HResult {
pub hr: HRESULT,
pub function: Option<&'static str>,
pub file_line: Option<FileLine>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FileLine(pub &'static str, pub u32);
impl Win32Error {
pub fn new(code: DWORD) -> Self {
Win32Error {
code,
function: None,
file_line: None,
}
}
pub fn get_last_error() -> Self {
Win32Error::new(unsafe { GetLastError() })
}
pub fn function(self, function: &'static str) -> Self {
Self {
function: Some(function),
..self
}
}
pub fn file_line(self, file: &'static str, line: u32) -> Self {
Self {
file_line: Some(FileLine(file, line)),
..self
}
}
}
impl HResult {
pub fn new(hr: HRESULT) -> Self {
HResult {
hr,
function: None,
file_line: None,
}
}
pub fn function(self, function: &'static str) -> Self {
Self {
function: Some(function),
..self
}
}
pub fn file_line(self, file: &'static str, line: u32) -> Self {
Self {
file_line: Some(FileLine(file, line)),
..self
}
}
pub fn extract_code(&self) -> HRESULT {
self.hr & 0xFFFF
}
pub fn extract_facility(&self) -> HRESULT {
(self.hr >> 16) & 0x1fff
}
pub fn try_into_win32_err(self) -> Result<Win32Error, Self> {
if self.extract_facility() == FACILITY_WIN32 {
Ok(Win32Error {
code: self.extract_code() as DWORD,
function: self.function,
file_line: self.file_line,
})
} else {
Err(self)
}
}
}
impl fmt::Display for Win32Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
if let Some(function) = self.function {
if let Some(FileLine(file, line)) = self.file_line {
write!(f, "{}:{} ", file, line)?;
}
write!(f, "{} ", function)?;
write!(f, "failed, ")?;
}
write!(f, "{:#010x}", self.code)?;
Ok(())
}
}
impl fmt::Display for HResult {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
if let Some(function) = self.function {
if let Some(FileLine(file, line)) = self.file_line {
write!(f, "{}:{} ", file, line)?;
}
write!(f, "{} ", function)?;
if !SUCCEEDED(self.hr) {
write!(f, "failed. ")?;
} else {
write!(f, "returned ")?;
}
}
write!(f, "HRESULT {:#010x}", self.hr)?;
Ok(())
}
}
pub trait ResultExt<T, E> {
type Code;
fn function(self, function: &'static str) -> Result<T, E>;
fn file_line(self, file: &'static str, line: u32) -> Result<T, E>;
fn allow_err(self, code: Self::Code, replacement: T) -> Result<T, E>;
fn allow_err_with<F>(self, code: Self::Code, replacement: F) -> Result<T, E>
where
F: FnOnce() -> T;
}
impl<T> ResultExt<T, HResult> for Result<T, HResult> {
type Code = HRESULT;
fn function(self, function: &'static str) -> Self {
self.map_err(|e| e.function(function))
}
fn file_line(self, file: &'static str, line: u32) -> Self {
self.map_err(|e| e.file_line(file, line))
}
fn allow_err(self, code: Self::Code, replacement: T) -> Self {
match self {
Ok(r) => Ok(r),
Err(ref e) if e.hr == code => Ok(replacement),
Err(e) => Err(e),
}
}
fn allow_err_with<F>(self, code: Self::Code, replacement: F) -> Self
where
F: FnOnce() -> T,
{
match self {
Ok(r) => Ok(r),
Err(ref e) if e.hr == code => Ok(replacement()),
Err(e) => Err(e),
}
}
}
impl<T> ResultExt<T, Win32Error> for Result<T, Win32Error> {
type Code = DWORD;
fn function(self, function: &'static str) -> Self {
self.map_err(|e| e.function(function))
}
fn file_line(self, file: &'static str, line: u32) -> Self {
self.map_err(|e| e.file_line(file, line))
}
fn allow_err(self, code: Self::Code, replacement: T) -> Self {
match self {
Ok(r) => Ok(r),
Err(ref e) if e.code == code => Ok(replacement),
Err(e) => Err(e),
}
}
fn allow_err_with<F>(self, code: Self::Code, replacement: F) -> Self
where
F: FnOnce() -> T,
{
match self {
Ok(r) => Ok(r),
Err(ref e) if e.code == code => Ok(replacement()),
Err(e) => Err(e),
}
}
}
impl From<Win32Error> for HResult {
fn from(win32_error: Win32Error) -> Self {
HResult {
hr: HRESULT_FROM_WIN32(win32_error.code),
function: win32_error.function,
file_line: win32_error.file_line,
}
}
}
pub fn succeeded_or_err(hr: HRESULT) -> Result<HRESULT, HResult> {
if !SUCCEEDED(hr) {
Err(HResult::new(hr))
} else {
Ok(hr)
}
}
#[macro_export]
macro_rules! check_succeeded {
($f:ident ( $($arg:expr),* )) => {
{
use $crate::error::ResultExt;
$crate::error::succeeded_or_err($f($($arg),*))
.function(stringify!($f))
.file_line(file!(), line!())
}
};
($f:ident ( $($arg:expr),+ , )) => {
$crate::check_succeeded!($f($($arg),+))
};
}
pub fn true_or_last_err<T>(rv: T) -> Result<T, Win32Error>
where
T: Eq,
T: From<bool>,
{
if rv == T::from(false) {
Err(Win32Error::get_last_error())
} else {
Ok(rv)
}
}
#[macro_export]
macro_rules! check_true {
($f:ident ( $($arg:expr),* )) => {
{
use $crate::error::ResultExt;
$crate::error::true_or_last_err($f($($arg),*))
.function(stringify!($f))
.file_line(file!(), line!())
}
};
($f:ident ( $($arg:expr),+ , )) => {
$crate::check_true!($f($($arg),+))
};
}