win_durable_ioring/error_code.rs
1// Copyright (c) 2026 Mike Grier
2// Split from error_code.rs at 4fbcb343.
3//! An error's code, read without the ring crate (DI-D-39), and the faults the Windows path
4//! reports with it (DI-D-50).
5
6use std::io;
7use std::sync::Arc;
8
9use durable_ioring::{ErrorCode, IoFault};
10use win_error_codes::{HResult, Win32Error};
11use windows_ioring_sys::IoRingErrorExt;
12
13#[cfg(test)]
14mod tests;
15
16/// The source every fault the Windows path reports names: the I/O ring beneath it.
17pub(crate) const WINDOWS_IORING: &str = "windows-ioring";
18
19/// A fault the Windows path reports for `error`: the I/O ring's, with the code it carries.
20pub(crate) fn windows_fault(error: io::Error) -> IoFault {
21 IoFault {
22 source: WINDOWS_IORING,
23 code: error_code_of(&error),
24 error: Arc::new(error),
25 }
26}
27
28/// The code `error` carries, if it carries one.
29///
30/// Every failure the I/O ring reports answers `Some`, because the ring reports each as an
31/// `HRESULT`; the Windows path reads it so into each [`IoFault`] it relays. An error carrying a
32/// Win32 code, as `io::Error::from_raw_os_error` makes, answers it wrapped as
33/// `HRESULT_FROM_WIN32` wraps it ([`error_code_from_win32`]). An error with neither, such as one a
34/// consumer's provider made from a message, answers `None`. A code read from a
35/// `FailureCause::Provider` error is the provider's own, relayed, not one dioring captured
36/// (DI-D-47).
37///
38/// ```
39/// use win_durable_ioring::{error_code_of, win32_code_of};
40///
41/// // ERROR_IO_DEVICE, as std reports a Win32 error.
42/// let code = error_code_of(&std::io::Error::from_raw_os_error(1117)).unwrap();
43/// assert_eq!(win32_code_of(code), Some(1117));
44/// assert_eq!(code.hresult().cast_unsigned(), 0x8007_045D);
45/// ```
46#[must_use]
47pub fn error_code_of(error: &io::Error) -> Option<ErrorCode> {
48 if let Some(ring) = error.as_ioring_error() {
49 return Some(ErrorCode::from_hresult(ring.code()));
50 }
51 error
52 .raw_os_error()
53 .map(|code| error_code_from_win32(code.cast_unsigned()))
54}
55
56/// The code a Win32 error names, wrapped as `HRESULT_FROM_WIN32` wraps it: a value that is already
57/// an `HRESULT` -- zero, or one with the severity bit set -- is taken as it is, and any other
58/// keeps only its low sixteen bits. The rule is `win-error-codes`'s ([`HResult::from_win32`]); the
59/// number space is this crate's, since the codes this driver reports are `HRESULT`s.
60///
61/// ```
62/// use win_durable_ioring::{error_code_from_win32, win32_code_of};
63///
64/// // ERROR_IO_DEVICE.
65/// let code = error_code_from_win32(1117);
66/// assert_eq!(code.hresult().cast_unsigned(), 0x8007_045D);
67/// assert_eq!(win32_code_of(code), Some(1117));
68/// ```
69#[must_use]
70pub fn error_code_from_win32(code: u32) -> ErrorCode {
71 ErrorCode::from_hresult(HResult::from_win32(Win32Error::from_code(code)).raw())
72}
73
74/// The Win32 code `code` wraps, if it is one wrapped by `HRESULT_FROM_WIN32`: severity set,
75/// `FACILITY_WIN32`, and no other bit above the code. `None` for `S_OK`, a success wrapping no
76/// error, and for an `HRESULT` of any other facility. The inverse of [`error_code_from_win32`]
77/// for the codes from 1 to 65535.
78#[must_use]
79pub fn win32_code_of(code: ErrorCode) -> Option<u32> {
80 HResult::from_raw(code.hresult())
81 .win32()
82 .map(Win32Error::code)
83}