wslplugins_rs/plugin/error.rs
1//! # WSL Plugin Error Handling
2//!
3//! This module defines a custom error type for handling errors in WSL plugins.
4//! It integrates with Windows APIs, supports error codes and optional error messages,
5//! and provides utility methods for error creation and consumption.
6
7use crate::api::{errors::RequireUpdateError, Error as ApiError};
8use crate::WSLContext;
9use std::borrow::ToOwned;
10use std::ffi::{OsStr, OsString};
11use std::num::NonZeroI32;
12use thiserror::Error;
13#[cfg(feature = "tracing")]
14use tracing::debug;
15use windows_core::{Error as WinError, HRESULT};
16
17/// A specialized result type for operations that may return a WSL plugin error.
18///
19/// This alias simplifies the function signatures throughout the WSL plugin codebase.
20pub type Result<T> = std::result::Result<T, Error>;
21
22/// Represents an error in the WSL plugin system.
23///
24/// This struct encapsulates:
25/// - An error code (`HRESULT`) derived from Windows APIs.
26/// - An optional error message (`OsString`).
27#[derive(Debug, Error)]
28pub struct Error {
29 /// The error code associated with the failure.
30 code: NonZeroI32,
31 /// An optional error message providing more context about the error.
32 message: Option<OsString>,
33}
34
35impl std::fmt::Display for Error {
36 /// Formats the error for display.
37 ///
38 /// If an error message is present, it is included in the output.
39 /// Otherwise, only the error code is displayed.
40 #[inline]
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 match &self.message {
43 Some(message) => write!(
44 f,
45 "WSLPluginError {}: {}",
46 self.code,
47 message.to_string_lossy()
48 ),
49 None => write!(f, "WSLPluginError {}", self.code),
50 }
51 }
52}
53
54impl Error {
55 /// Creates a new error with a specified code and optional message.
56 ///
57 /// # Arguments
58 /// - `code`: The HRESULT representing the error code.
59 /// - `message`: An optional error message.
60 ///
61 /// # Returns
62 /// A new instance of `Error`.
63 #[must_use]
64 #[inline]
65 pub fn new(code: HRESULT, message: Option<&OsStr>) -> Self {
66 let code = if code.is_ok() {
67 WinError::from_hresult(code).code()
68 } else {
69 code
70 };
71 // SAFETY: the code getted from WinError is guaranteed to be valid
72 let code = unsafe { NonZeroI32::new_unchecked(code.0) };
73
74 Self {
75 code,
76 message: message.map(ToOwned::to_owned),
77 }
78 }
79
80 /// Creates an error with only an error code.
81 ///
82 /// # Arguments
83 /// - `code`: The HRESULT representing the error code.
84 ///
85 /// # Returns
86 /// A new instance of `Error` without an associated message.
87 #[must_use]
88 #[inline]
89 pub fn with_code(code: HRESULT) -> Self {
90 Self::new(code, None)
91 }
92
93 /// Creates an error with both a code and a message.
94 ///
95 /// # Arguments
96 /// - `code`: The HRESULT representing the error code.
97 /// - `message`: An associated error message.
98 ///
99 /// # Returns
100 /// A new instance of `Error`.
101 #[must_use]
102 #[inline]
103 pub fn with_message(code: HRESULT, message: &OsStr) -> Self {
104 Self::new(code, Some(message))
105 }
106
107 /// Retrieves the error code as an `HRESULT`.
108 ///
109 /// # Returns
110 /// The error code wrapped in an `HRESULT`.
111 #[inline]
112 pub const fn code(&self) -> HRESULT {
113 HRESULT(self.code.get())
114 }
115
116 /// Retrieves the optional error message.
117 ///
118 /// # Returns
119 /// A reference to the error message, if present.
120 #[must_use]
121 #[inline]
122 pub fn message(&self) -> Option<&OsStr> {
123 self.message.as_deref()
124 }
125
126 /// Consumes the error and optionally sets the plugin's error message in the WSL context.
127 ///
128 /// # Type Parameters
129 /// - `R`: The type to return after consuming the error.
130 ///
131 /// # Returns
132 /// The consumed error converted to the specified type.
133 pub(crate) fn consume_error_message_unwrap<R: From<Self>>(self) -> R {
134 if let Some(ref mess) = self.message {
135 if let Some(context) = WSLContext::get_current() {
136 #[cfg_attr(not(feature = "tracing"), expect(unused_variables))]
137 let plugin_error_result = context.api.plugin_error(mess.as_os_str());
138 #[cfg(feature = "tracing")]
139 if let Err(err) = plugin_error_result {
140 debug!(
141 "Unable to set plugin error message {} due to error: {}",
142 mess.to_string_lossy(),
143 err
144 );
145 }
146 }
147 }
148 R::from(self)
149 }
150}
151
152impl From<Error> for WinError {
153 /// Converts the `Error` into a `WinError`.
154 ///
155 /// # Returns
156 /// A `WinError` constructed from the error's code and message.
157 #[inline]
158 fn from(value: Error) -> Self {
159 let code = value.code();
160 value.message.as_ref().map_or_else(
161 || Self::from_hresult(code),
162 |message| {
163 let msg_string = message.to_string_lossy();
164 Self::new(code, &msg_string)
165 },
166 )
167 }
168}
169
170impl From<WinError> for Error {
171 /// Converts a `WinError` into an `Error`.
172 ///
173 /// # Returns
174 /// An `Error` containing the code and message from the `WinError`.
175 #[inline]
176 fn from(value: WinError) -> Self {
177 let os_message = if value.message().is_empty() {
178 None
179 } else {
180 Some(OsString::from(value.message()))
181 };
182
183 Self {
184 // SAFETY: As we have a valid WinError, we can safely extract the code.
185 code: unsafe { NonZeroI32::new_unchecked(value.code().0) },
186 message: os_message,
187 }
188 }
189}
190
191impl From<HRESULT> for Error {
192 /// Converts an `HRESULT` into an `Error`.
193 ///
194 /// # Returns
195 /// An `Error` containing the `HRESULT` as its code.
196 #[inline]
197 fn from(value: HRESULT) -> Self {
198 Self::new(value, None)
199 }
200}
201
202impl From<RequireUpdateError> for Error {
203 #[inline]
204 fn from(value: RequireUpdateError) -> Self {
205 Self::from(HRESULT::from(value))
206 }
207}
208
209impl From<ApiError> for Error {
210 #[inline]
211 fn from(value: ApiError) -> Self {
212 Self::from(HRESULT::from(value))
213 }
214}