w32_error/
lib.rs

1// Copyright (c) 2020 FaultyRAM
2//
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option. This file may not be copied,
6// modified, or distributed except according to those terms.
7
8//! # w32-error
9//!
10//! w32-error provides the `W32Error` type for wrapping Windows API error codes. A `W32Error` has
11//! the same layout as a `DWORD`, and implements `Display` for printing human-readable error
12//! messages. When the `std` crate feature is enabled, `W32Error` also implements `Error`, and can
13//! be converted to or from an `io::Error` via the standard `TryFrom`/`TryInto`
14//! (`io::Error` => `W32Error`) and `From`/`Into` (`W32Error` => `io::Error`) traits.
15//!
16//! ## Examples
17//!
18//! A `W32Error` can be constructed from an arbitrary `DWORD`:
19//!
20//! ```should_panic
21//! use w32_error::W32Error;
22//!
23//! fn main() -> Result<(), W32Error> {
24//!     Err(W32Error::new(0))
25//! }
26//! ```
27//!
28//! The `W32Error::last_thread_error` method constructs a `W32Error` using the last-error code set
29//! for the calling thread:
30//!
31//! ```ignore
32//! use std::io;
33//! use w32_error::W32Error;
34//!
35//! fn windows_api_call() -> Result<(), W32Error> {
36//!     let result = SomeWindowsAPIFunction();
37//!     if result == 0 { // Zero indicates failure.
38//!         Err(W32Error::last_thread_error())
39//!     } else {
40//!         // ...
41//!     }
42//! }
43//!
44//! fn main() -> io::Result<()> {
45//!     windows_api_call()?; // Converts `W32Error` to `io::Error` on failure.
46//!     Ok(())
47//! }
48//! ```
49//!
50//! `W32Error` implements `Display` via calling `FormatMessageW`:
51//!
52//! ```
53//! use w32_error::W32Error;
54//!
55//! fn main() {
56//!     let error = W32Error::new(0);
57//!     println!("{}", error);
58//! }
59
60#![cfg_attr(not(feature = "std"), no_std)]
61#![deny(
62    clippy::all,
63    clippy::pedantic,
64    warnings,
65    future_incompatible,
66    rust_2018_idioms,
67    rustdoc,
68    unused,
69    deprecated_in_future,
70    missing_copy_implementations,
71    missing_debug_implementations,
72    non_ascii_idents,
73    trivial_casts,
74    trivial_numeric_casts,
75    unreachable_pub,
76    unused_import_braces,
77    unused_lifetimes,
78    unused_results
79)]
80#![allow(clippy::needless_doctest_main, clippy::must_use_candidate)]
81
82#[cfg(not(target_os = "windows"))]
83compile_error!("w32-error only supports Windows-based targets");
84
85#[cfg(not(feature = "std"))]
86use core as std_crate;
87#[cfg(feature = "std")]
88use std as std_crate;
89
90use std_crate::{
91    char,
92    fmt::{self, Display, Formatter, Write},
93    hint, mem, ptr,
94};
95#[cfg(feature = "std")]
96use std_crate::{convert::TryFrom, error::Error, io};
97use winapi::{
98    shared::minwindef::DWORD,
99    um::{
100        errhandlingapi::GetLastError,
101        winbase::{
102            FormatMessageW, FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_IGNORE_INSERTS,
103            FORMAT_MESSAGE_MAX_WIDTH_MASK,
104        },
105        winnt::WCHAR,
106    },
107};
108
109#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
110#[must_use = "this `W32Error` is unhandled"]
111#[repr(transparent)]
112/// A Windows API error.
113pub struct W32Error(DWORD);
114
115#[cfg(feature = "std")]
116#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
117#[must_use = "this `TryFromIoError` is unhandled"]
118/// A failure to convert an `io::Error` into a `W32Error`.
119pub struct TryFromIoError;
120
121impl W32Error {
122    /// Wraps an arbitrary error code.
123    ///
124    /// # Examples
125    ///
126    /// ```
127    /// # use w32_error::W32Error;
128    /// let error = W32Error::new(0);
129    /// println!("{}", error);
130    /// ```
131    pub const fn new(code: DWORD) -> Self {
132        Self(code)
133    }
134
135    #[cfg(feature = "std")]
136    #[allow(clippy::cast_sign_loss)]
137    /// Wraps the error code from an `io::Error` if it has one.
138    ///
139    /// # Examples
140    ///
141    /// ```
142    /// # use w32_error::W32Error;
143    /// use std::io;
144    /// let io_error = io::Error::from_raw_os_error(0);
145    /// let opt = W32Error::from_io_error(&io_error);
146    /// assert_eq!(opt, Some(W32Error::new(0)));
147    /// ```
148    pub fn from_io_error(other: &io::Error) -> Option<Self> {
149        if let Some(code) = other.raw_os_error() {
150            Some(W32Error::new(code as DWORD))
151        } else {
152            None
153        }
154    }
155
156    /// Wraps the error code that is currently set for the calling thread.
157    ///
158    /// This is equivalent to calling the Windows API function `GetLastError` and passing the return
159    /// value to `W32Error::new`.
160    ///
161    /// # Examples
162    ///
163    /// ```
164    /// # use w32_error::W32Error;
165    /// let error = W32Error::last_thread_error();
166    /// println!("{}", error);
167    /// ```
168    pub fn last_thread_error() -> Self {
169        Self::new(unsafe { GetLastError() })
170    }
171
172    /// Returns the underlying error code wrapped by a `W32Error`.
173    ///
174    /// # Examples
175    ///
176    /// ```
177    /// # use w32_error::W32Error;
178    /// assert_eq!(W32Error::new(0).into_inner(), 0);
179    /// ```
180    pub const fn into_inner(self) -> DWORD {
181        self.0
182    }
183}
184
185impl Display for W32Error {
186    #[allow(clippy::cast_possible_truncation)]
187    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
188        const MAX_CHARACTERS: usize = 1024;
189        // According to the MSDN documentation for `FormatMessage`, `wide_buffer` cannot be larger
190        // than 64KB.
191        debug_assert!(mem::size_of::<[WCHAR; MAX_CHARACTERS]>() <= 65536);
192        let mut wide_buffer = [WCHAR::default(); MAX_CHARACTERS];
193        let len = unsafe {
194            FormatMessageW(
195                FORMAT_MESSAGE_FROM_SYSTEM
196                    | FORMAT_MESSAGE_IGNORE_INSERTS
197                    | FORMAT_MESSAGE_MAX_WIDTH_MASK,
198                ptr::null(),
199                self.0,
200                0,
201                wide_buffer.as_mut_ptr(),
202                MAX_CHARACTERS as DWORD,
203                ptr::null_mut(),
204            ) as usize
205        };
206        if len == 0 {
207            // `FormatMessage` failed. Write out the error code itself as a last resort.
208            f.write_fmt(format_args!("{:#08X}", self.0))
209        } else {
210            // Strip leading and trailing whitespace from the error message.
211            // If `FormatMessage` is instructed to strip inserts and manual line breaks from the
212            // message, they may be replaced with whitespace.
213            let mut char_buffer = [char::default(); MAX_CHARACTERS];
214            let char_msg = &mut char_buffer[..len];
215            let wide_msg = &wide_buffer[..len];
216            char::decode_utf16(wide_msg.iter().copied())
217                .map(|res| res.unwrap_or(char::REPLACEMENT_CHARACTER))
218                .zip(char_msg.iter_mut())
219                .for_each(|(src, dst)| *dst = src);
220            if let Some(a) = char_msg.iter().position(|c| !c.is_whitespace()) {
221                let b = char_msg
222                    .iter()
223                    .rposition(|c| !c.is_whitespace())
224                    .unwrap_or_else(|| unsafe { hint::unreachable_unchecked() });
225                for &c in &char_msg[a..=b] {
226                    f.write_char(c)?;
227                }
228            }
229            Ok(())
230        }
231    }
232}
233
234#[cfg(feature = "std")]
235impl Error for W32Error {}
236
237#[cfg(feature = "std")]
238impl From<W32Error> for io::Error {
239    #[allow(clippy::cast_possible_wrap)]
240    fn from(other: W32Error) -> Self {
241        io::Error::from_raw_os_error(other.into_inner() as i32)
242    }
243}
244
245impl From<DWORD> for W32Error {
246    fn from(other: DWORD) -> Self {
247        Self::new(other)
248    }
249}
250
251impl From<W32Error> for DWORD {
252    fn from(other: W32Error) -> Self {
253        other.into_inner()
254    }
255}
256
257#[cfg(feature = "std")]
258impl TryFrom<io::Error> for W32Error {
259    type Error = TryFromIoError;
260
261    fn try_from(other: io::Error) -> Result<Self, Self::Error> {
262        Self::from_io_error(&other).ok_or(TryFromIoError)
263    }
264}
265
266#[cfg(feature = "std")]
267impl Display for TryFromIoError {
268    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
269        f.write_str("the given `io::Error` did not contain a Windows API error code")
270    }
271}
272
273#[cfg(feature = "std")]
274impl Error for TryFromIoError {}