Skip to main content

mssf_core/error/
mod.rs

1// ------------------------------------------------------------
2// Copyright (c) Microsoft Corporation.  All rights reserved.
3// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
4// ------------------------------------------------------------
5
6use crate::HRESULT;
7use mssf_com::FabricTypes::FABRIC_ERROR_CODE;
8
9mod errorcode;
10pub use errorcode::ErrorCode;
11use windows_core::WString;
12
13/// Result containing mssf Error.
14pub type Result<T> = core::result::Result<T, Error>;
15
16/// Make passing error code to SF api easier.
17/// Provides conversion from windows errors or fabric error code
18/// to windows_core::Error.
19/// All safe code uses this Error, and bridge and proxy code needs to
20/// convert this Error into/from WinError.
21#[derive(Clone, PartialEq)]
22pub struct Error {
23    code: super::HRESULT,
24    msg: Option<WString>,
25}
26
27impl Error {
28    pub fn new(code: HRESULT, msg: Option<WString>) -> Self {
29        Self { code, msg }
30    }
31
32    /// Create error from HRESULT code only, with no message.
33    pub fn from_hresult(code: HRESULT) -> Self {
34        Self::new(code, None)
35    }
36
37    /// Convert to fabric error code if possible.
38    pub fn try_as_fabric_error_code(&self) -> std::result::Result<ErrorCode, &str> {
39        ErrorCode::try_from(FABRIC_ERROR_CODE(self.code.0))
40    }
41
42    /// Create error from current thread last error code and message.
43    pub fn from_thread(code: HRESULT) -> Self {
44        let msg = get_last_error_message();
45        Self::new(code, msg)
46    }
47
48    pub fn code(&self) -> HRESULT {
49        self.code
50    }
51}
52
53impl From<HRESULT> for Error {
54    fn from(value: HRESULT) -> Self {
55        Self::from_hresult(value)
56    }
57}
58
59impl From<FABRIC_ERROR_CODE> for Error {
60    fn from(value: FABRIC_ERROR_CODE) -> Self {
61        Self::from_hresult(HRESULT(value.0))
62    }
63}
64
65impl From<Error> for super::WinError {
66    fn from(val: Error) -> Self {
67        super::WinError::from_hresult(val.code)
68    }
69}
70
71impl From<Error> for HRESULT {
72    fn from(value: Error) -> Self {
73        value.code
74    }
75}
76
77impl From<crate::WinError> for Error {
78    fn from(error: crate::WinError) -> Self {
79        Self::from_hresult(error.code())
80    }
81}
82
83impl core::fmt::Debug for Error {
84    fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
85        let mut debug = fmt.debug_struct("FabricError");
86        let code_str = ErrorCode::try_from(FABRIC_ERROR_CODE(self.code.0)).ok();
87        debug.field("code", &self.code.0);
88        match code_str {
89            Some(c) => debug.field("code_str", &c),
90            None => debug.field("code_str", &"unknown fabric error"),
91        };
92        match &self.msg {
93            Some(m) => debug.field("message", m),
94            None => debug.field("message", &"none"),
95        };
96        debug.finish()
97    }
98}
99
100impl core::fmt::Display for Error {
101    fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
102        let str_code = ErrorCode::try_from(FABRIC_ERROR_CODE(self.code.0)).ok();
103        match str_code {
104            Some(c) => core::write!(fmt, "{} ({})", c, self.code.0),
105            None => core::write!(fmt, "{}", self.code.0),
106        }?;
107        match &self.msg {
108            Some(m) => core::write!(fmt, ": {m}"),
109            None => Ok(()),
110        }
111    }
112}
113
114impl std::error::Error for Error {}
115
116// conversion from common error types in std
117
118impl From<std::io::Error> for Error {
119    fn from(value: std::io::Error) -> Self {
120        // Use the windows implementation to convert
121        crate::WinError::from(value).into()
122    }
123}
124
125impl From<Error> for std::io::Error {
126    fn from(value: Error) -> Self {
127        // Use windows implementation
128        Self::from(crate::WinError::from(value))
129    }
130}
131
132impl From<core::num::TryFromIntError> for Error {
133    fn from(value: core::num::TryFromIntError) -> Self {
134        // Use windows implementation
135        crate::WinError::from(value).into()
136    }
137}
138
139// Get last error message from current thread from SF.
140// Call this after an SF API call failure.
141fn get_last_error_message() -> Option<WString> {
142    // The call returns error only when input COM holder ptr is null:
143    // https://github.com/microsoft/service-fabric/blob/ddfed33de371857f8fdb92287a8e0497297c8ccf/src/prod/src/retail/native/FabricCommon/FabricCommon.cpp#L233
144    // Our COM layer always provides a valid pointer, so we can safely ignore here.
145    let msg = crate::api::API_TABLE.fabric_get_last_error_message().ok()?;
146    // If message is not set, the returned string is be empty c string from cpp side.
147    // We convert null or empty string to None, and non-empty string to Some.
148    let smsg = crate::strings::StringResult::from(&msg).into_inner();
149    // Intetional check len instead of is_empty because WString can be Nul and not empty.
150    #[allow(clippy::len_zero)]
151    match smsg.len() == 0 {
152        true => None,
153        false => Some(smsg),
154    }
155}
156
157#[cfg(test)]
158mod test {
159
160    use super::{Error, ErrorCode};
161    use crate::HRESULT;
162    use mssf_com::FabricTypes::FABRIC_E_CODE_PACKAGE_NOT_FOUND;
163    use windows_core::Win32::Foundation::{E_ACCESSDENIED, E_POINTER};
164
165    #[test]
166    fn test_fabric_error() {
167        let fe = Error::from(FABRIC_E_CODE_PACKAGE_NOT_FOUND);
168        // check debug string
169        assert_eq!(
170            format!("{fe:?}"),
171            "FabricError { code: -2147017733, code_str: FABRIC_E_CODE_PACKAGE_NOT_FOUND, message: \"none\" }"
172        );
173        // check display string
174        assert_eq!(
175            format!("{fe}"),
176            "FABRIC_E_CODE_PACKAGE_NOT_FOUND (-2147017733)"
177        );
178        let e = crate::WinError::from(fe.clone());
179        assert_eq!(e.code(), fe.into());
180        let ec = Error::from(e)
181            .try_as_fabric_error_code()
182            .expect("unknown code");
183        assert_eq!(ec, ErrorCode::FABRIC_E_CODE_PACKAGE_NOT_FOUND);
184    }
185
186    #[test]
187    fn test_hresult_error() {
188        let err1: HRESULT = Error::from(ErrorCode::E_ACCESSDENIED).into();
189        let err2 = E_ACCESSDENIED;
190        assert_eq!(err1, err2);
191
192        let e: crate::WinError = ErrorCode::E_POINTER.into();
193        assert_eq!(e, E_POINTER.into());
194
195        const SEC_E_INTERNAL_ERROR: crate::HRESULT = crate::HRESULT(0x80090304_u32 as _);
196        // use an error that is not fabric error
197        let fe = Error::from(SEC_E_INTERNAL_ERROR);
198        // check display string
199        assert_eq!(format!("{fe}"), "-2146893052");
200        assert_eq!(
201            format!("{fe:?}"),
202            "FabricError { code: -2146893052, code_str: \"unknown fabric error\", message: \"none\" }"
203        );
204    }
205
206    #[test]
207    fn test_error_from_thread() {
208        // Call an obvious failing API to get error from thread.
209        // In this call SF does not set the last error message.
210        let err = crate::runtime::create_com_runtime().unwrap_err();
211        let err_thread = Error::from_thread(err.code());
212        match err_thread.try_as_fabric_error_code().unwrap() {
213            // When onebox is not running on the same machine.
214            ErrorCode::FABRIC_INTERNAL_E_CANNOT_CONNECT => {
215                assert_eq!(
216                    format!("{err_thread}"),
217                    "FABRIC_INTERNAL_E_CANNOT_CONNECT (-2147017536)"
218                );
219            }
220            // When onebox is running. Test is not spawned by SF.
221            ErrorCode::FABRIC_E_CONNECTION_DENIED => {
222                assert_eq!(
223                    format!("{err_thread}"),
224                    "FABRIC_E_CONNECTION_DENIED (-2147017661)"
225                );
226            }
227            c => panic!("unexpected error code {c}"),
228        }
229    }
230}