winkit 0.1.0

Thin checked wrappers over the Win32 an installer needs: elevation, ACLs, services, the Restart Manager, the registry and shortcuts.
use std::fmt;

use windows_sys::Win32::Foundation::WIN32_ERROR;

/// A failed Windows call, kept with the name of the call so a support log says
/// which one it was rather than only what the code meant.
#[derive(Debug, Clone)]
pub struct Error {
    pub call: &'static str,
    pub code: u32,
    pub detail: Option<String>,
}

impl Error {
    pub(crate) fn last(call: &'static str) -> Self {
        Self {
            call,
            code: unsafe { windows_sys::Win32::Foundation::GetLastError() },
            detail: None,
        }
    }

    pub(crate) fn code(call: &'static str, code: WIN32_ERROR) -> Self {
        Self {
            call,
            code,
            detail: None,
        }
    }

    pub(crate) fn hresult(call: &'static str, hr: i32) -> Self {
        Self {
            call,
            code: hr as u32,
            detail: None,
        }
    }

    pub(crate) fn saying(call: &'static str, detail: impl Into<String>) -> Self {
        Self {
            call,
            code: 0,
            detail: Some(detail.into()),
        }
    }

    /// True when the call failed only because the thing was not there, which
    /// several callers treat as success.
    pub fn is_not_found(&self) -> bool {
        const ERROR_FILE_NOT_FOUND: u32 = 2;
        const ERROR_SERVICE_DOES_NOT_EXIST: u32 = 1060;
        matches!(
            self.code,
            ERROR_FILE_NOT_FOUND | ERROR_SERVICE_DOES_NOT_EXIST
        )
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.detail {
            Some(detail) => write!(f, "{}: {detail}", self.call),
            None => write!(f, "{} failed (0x{:08X})", self.call, self.code),
        }
    }
}

impl std::error::Error for Error {}

pub type Result<T> = std::result::Result<T, Error>;