use std::error::Error;
use std::fmt;
use winapi::shared::minwindef::DWORD;
use winapi::shared::winerror::{
ERROR_SUCCESS, FACILITY_WIN32, HRESULT, HRESULT_FROM_WIN32, SUCCEEDED, S_OK,
};
use winapi::um::errhandlingapi::GetLastError;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ErrorAndSource<T: ErrorCode> {
code: T,
function: Option<&'static str>,
file_line: Option<FileLine>,
}
pub trait ErrorCode:
Copy + fmt::Debug + Eq + PartialEq + fmt::Display + Send + Sync + 'static
{
type InnerT: Copy + Eq + PartialEq;
fn get(&self) -> Self::InnerT;
}
impl<T> ErrorAndSource<T>
where
T: ErrorCode,
{
pub fn code(&self) -> T::InnerT {
self.code.get()
}
pub fn function(self, function: &'static str) -> Self {
Self {
function: Some(function),
..self
}
}
pub fn get_function(&self) -> Option<&'static str> {
self.function
}
pub fn file_line(self, file: &'static str, line: u32) -> Self {
Self {
file_line: Some(FileLine(file, line)),
..self
}
}
pub fn get_file_line(&self) -> &Option<FileLine> {
&self.file_line
}
}
impl<T> fmt::Display for ErrorAndSource<T>
where
T: ErrorCode,
{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
if let Some(function) = self.function {
if let Some(ref file_line) = self.file_line {
write!(f, "{} ", file_line)?;
}
write!(f, "{} ", function)?;
write!(f, "error: ")?;
}
write!(f, "{}", self.code)?;
Ok(())
}
}
impl<T> Error for ErrorAndSource<T> where T: ErrorCode {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FileLine(pub &'static str, pub u32);
impl fmt::Display for FileLine {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{}:{}", self.0, self.1)
}
}
pub type Win32Error = ErrorAndSource<Win32ErrorInner>;
impl Win32Error {
pub fn new(code: DWORD) -> Self {
Win32Error {
code: Win32ErrorInner(code),
function: None,
file_line: None,
}
}
pub fn get_last_error() -> Self {
Win32Error::new(unsafe { GetLastError() })
}
}
#[doc(hidden)]
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Win32ErrorInner(DWORD);
impl ErrorCode for Win32ErrorInner {
type InnerT = DWORD;
fn get(&self) -> DWORD {
self.0
}
}
impl fmt::Display for Win32ErrorInner {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{:#010x}", self.0)
}
}
pub type HResult = ErrorAndSource<HResultInner>;
impl HResult {
pub fn new(hr: HRESULT) -> Self {
HResult {
code: HResultInner(hr),
function: None,
file_line: None,
}
}
pub fn extract_code(&self) -> HRESULT {
self.code.0 & 0xFFFF
}
pub fn extract_facility(&self) -> HRESULT {
(self.code.0 >> 16) & 0x1fff
}
pub fn try_into_win32_err(self) -> Result<Win32Error, Self> {
let code = if self.code() == S_OK {
ERROR_SUCCESS
} else if self.extract_facility() == FACILITY_WIN32 {
self.extract_code() as DWORD
} else {
return Err(self);
};
Ok(Win32Error {
code: Win32ErrorInner(code),
function: self.function,
file_line: self.file_line,
})
}
}
#[doc(hidden)]
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct HResultInner(HRESULT);
impl ErrorCode for HResultInner {
type InnerT = HRESULT;
fn get(&self) -> HRESULT {
self.0
}
}
impl fmt::Display for HResultInner {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "HRESULT {:#010x}", self.0)
}
}
pub trait ResultExt<T, E> {
type Code;
fn function(self, function: &'static str) -> Self;
fn file_line(self, file: &'static str, line: u32) -> Self;
fn allow_err(self, code: Self::Code, replacement: T) -> Self;
fn allow_err_with<F>(self, code: Self::Code, replacement: F) -> Self
where
F: FnOnce() -> T;
}
impl<T, EC> ResultExt<T, ErrorAndSource<EC>> for Result<T, ErrorAndSource<EC>>
where
EC: ErrorCode,
{
type Code = EC::InnerT;
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 {
self.or_else(|e| {
if e.code() == code {
Ok(replacement)
} else {
Err(e)
}
})
}
fn allow_err_with<F>(self, code: Self::Code, replacement: F) -> Self
where
F: FnOnce() -> T,
{
self.or_else(|e| {
if e.code() == code {
Ok(replacement())
} else {
Err(e)
}
})
}
}
impl From<Win32Error> for HResult {
fn from(win32_error: Win32Error) -> Self {
HResult {
code: HResultInner(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),+))
};
}