1use super::*;
2use core::num::NonZeroI32;
3
4#[expect(unused_imports)]
5use core::mem::size_of;
6
7#[derive(Clone)]
46pub struct Error {
47 code: NonZeroI32,
52
53 info: ErrorInfo,
55}
56
57const S_EMPTY_ERROR: NonZeroI32 = const_nonzero_i32(u32::from_be_bytes(*b"S_OK") as i32);
60
61const fn const_nonzero_i32(i: i32) -> NonZeroI32 {
65 if let Some(nz) = NonZeroI32::new(i) {
66 nz
67 } else {
68 panic!();
69 }
70}
71
72fn nonzero_hresult(hr: HRESULT) -> NonZeroI32 {
73 if let Some(nz) = NonZeroI32::new(hr.0) {
74 nz
75 } else {
76 S_EMPTY_ERROR
77 }
78}
79
80impl Error {
81 pub const fn empty() -> Self {
83 Self {
84 code: S_EMPTY_ERROR,
85 info: ErrorInfo::empty(),
86 }
87 }
88
89 pub fn new<T: AsRef<str>>(code: HRESULT, message: T) -> Self {
92 #[cfg(windows)]
93 {
94 let message: &str = message.as_ref();
95 if message.is_empty() {
96 Self::from_hresult(code)
97 } else {
98 ErrorInfo::originate_error(code, message);
99 code.into()
100 }
101 }
102 #[cfg(not(windows))]
103 {
104 let _ = message;
105 Self::from_hresult(code)
106 }
107 }
108
109 pub fn from_hresult(code: HRESULT) -> Self {
111 Self {
112 code: nonzero_hresult(code),
113 info: ErrorInfo::empty(),
114 }
115 }
116
117 pub fn from_thread() -> Self {
119 Self::from_hresult(HRESULT::from_thread())
120 }
121
122 pub const fn code(&self) -> HRESULT {
124 if self.code.get() == S_EMPTY_ERROR.get() {
125 HRESULT(0)
126 } else {
127 HRESULT(self.code.get())
128 }
129 }
130
131 pub fn message(&self) -> String {
133 if let Some(message) = self.info.message() {
134 return message;
135 }
136
137 self.code().message()
139 }
140
141 #[cfg(windows)]
143 pub fn as_ptr(&self) -> *mut core::ffi::c_void {
144 self.info.as_ptr()
145 }
146}
147
148#[cfg(feature = "std")]
149impl std::error::Error for Error {}
150
151impl From<Error> for HRESULT {
152 fn from(error: Error) -> Self {
153 let code = error.code();
154 error.info.into_thread();
155 code
156 }
157}
158
159impl From<HRESULT> for Error {
160 fn from(code: HRESULT) -> Self {
161 Self {
162 code: nonzero_hresult(code),
163 info: ErrorInfo::from_thread(),
164 }
165 }
166}
167
168#[cfg(feature = "std")]
169impl From<Error> for std::io::Error {
170 fn from(from: Error) -> Self {
171 Self::from_raw_os_error(from.code().0)
172 }
173}
174
175#[cfg(feature = "std")]
176impl From<std::io::Error> for Error {
177 fn from(from: std::io::Error) -> Self {
178 match from.raw_os_error() {
179 Some(status) => HRESULT::from_win32(status as u32).into(),
180 None => HRESULT(E_UNEXPECTED).into(),
181 }
182 }
183}
184
185impl From<alloc::string::FromUtf16Error> for Error {
186 fn from(_: alloc::string::FromUtf16Error) -> Self {
187 Self::from_hresult(HRESULT::from_win32(ERROR_NO_UNICODE_TRANSLATION))
188 }
189}
190
191impl From<alloc::string::FromUtf8Error> for Error {
192 fn from(_: alloc::string::FromUtf8Error) -> Self {
193 Self::from_hresult(HRESULT::from_win32(ERROR_NO_UNICODE_TRANSLATION))
194 }
195}
196
197impl From<core::num::TryFromIntError> for Error {
198 fn from(_: core::num::TryFromIntError) -> Self {
199 Self::from_hresult(HRESULT::from_win32(ERROR_INVALID_DATA))
200 }
201}
202
203impl core::fmt::Debug for Error {
204 fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
205 let mut debug = fmt.debug_struct("Error");
206 debug
207 .field("code", &self.code())
208 .field("message", &self.message())
209 .finish()
210 }
211}
212
213impl core::fmt::Display for Error {
214 fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
215 let message = self.message();
216 if message.is_empty() {
217 core::write!(fmt, "{}", self.code())
218 } else {
219 core::write!(fmt, "{} ({})", message, self.code())
220 }
221 }
222}
223
224impl core::hash::Hash for Error {
225 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
226 self.code.hash(state);
227 }
229}
230
231impl PartialEq for Error {
233 fn eq(&self, other: &Self) -> bool {
234 self.code == other.code
235 }
236}
237
238impl Eq for Error {}
239
240impl PartialOrd for Error {
241 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
242 Some(self.cmp(other))
243 }
244}
245
246impl Ord for Error {
247 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
248 self.code.cmp(&other.code)
249 }
250}
251
252use error_info::*;
253
254#[cfg(all(windows, not(windows_slim_errors)))]
255mod error_info {
256 use super::*;
257 use crate::com::ComPtr;
258
259 #[derive(Clone, Default)]
265 pub(crate) struct ErrorInfo {
266 pub(super) ptr: Option<ComPtr>,
267 }
268
269 impl ErrorInfo {
270 pub(crate) const fn empty() -> Self {
271 Self { ptr: None }
272 }
273
274 pub(crate) fn from_thread() -> Self {
275 unsafe {
276 let mut ptr = core::mem::MaybeUninit::zeroed();
277 crate::bindings::GetErrorInfo(0, ptr.as_mut_ptr() as *mut _);
278 Self {
279 ptr: ptr.assume_init(),
280 }
281 }
282 }
283
284 pub(crate) fn into_thread(self) {
285 if let Some(ptr) = self.ptr {
286 unsafe {
287 crate::bindings::SetErrorInfo(0, ptr.as_raw());
288 }
289 }
290 }
291
292 pub(crate) fn originate_error(code: HRESULT, message: &str) {
293 let message: Vec<_> = message.encode_utf16().collect();
294 unsafe {
295 RoOriginateErrorW(code.0, message.len() as u32, message.as_ptr());
296 }
297 }
298
299 pub(crate) fn message(&self) -> Option<String> {
300 use crate::bstr::BasicString;
301
302 let ptr = self.ptr.as_ref()?;
303
304 let mut message = BasicString::default();
305
306 if let Some(info) = ptr.cast(&IID_IRestrictedErrorInfo) {
308 let mut fallback = BasicString::default();
309 let mut code = 0;
310
311 unsafe {
312 com_call!(
313 IRestrictedErrorInfo_Vtbl,
314 info.GetErrorDetails(
315 &mut fallback as *mut _ as _,
316 &mut code,
317 &mut message as *mut _ as _,
318 &mut BasicString::default() as *mut _ as _
319 )
320 );
321 }
322
323 if message.is_empty() {
324 message = fallback
325 };
326 }
327
328 if message.is_empty() {
330 unsafe {
331 com_call!(
332 IErrorInfo_Vtbl,
333 ptr.GetDescription(&mut message as *mut _ as _)
334 );
335 }
336 }
337
338 Some(String::from_utf16_lossy(wide_trim_end(&message)))
339 }
340
341 pub(crate) fn as_ptr(&self) -> *mut core::ffi::c_void {
342 if let Some(info) = self.ptr.as_ref() {
343 info.as_raw()
344 } else {
345 core::ptr::null_mut()
346 }
347 }
348 }
349
350 unsafe impl Send for ErrorInfo {}
351 unsafe impl Sync for ErrorInfo {}
352}
353
354#[cfg(not(all(windows, not(windows_slim_errors))))]
355mod error_info {
356 use super::*;
357
358 #[derive(Clone, Default)]
361 pub(crate) struct EmptyErrorInfo;
362
363 pub(crate) use EmptyErrorInfo as ErrorInfo;
364
365 impl EmptyErrorInfo {
366 pub(crate) const fn empty() -> Self {
367 Self
368 }
369
370 pub(crate) fn from_thread() -> Self {
371 Self
372 }
373
374 pub(crate) fn into_thread(self) {}
375
376 #[cfg(windows)]
377 pub(crate) fn originate_error(_code: HRESULT, _message: &str) {}
378
379 pub(crate) fn message(&self) -> Option<String> {
380 None
381 }
382
383 #[cfg(windows)]
384 pub(crate) fn as_ptr(&self) -> *mut core::ffi::c_void {
385 core::ptr::null_mut()
386 }
387 }
388}