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 {}