Skip to main content

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}