Skip to main content

hyperlight_guest/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025 The Hyperlight Authors.
3
4use alloc::format;
5use alloc::string::{String, ToString as _};
6
7pub use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
8use hyperlight_common::func::Error as FuncError;
9use {anyhow, serde_json};
10
11pub type Result<T> = core::result::Result<T, HyperlightGuestError>;
12
13#[derive(Debug)]
14pub struct HyperlightGuestError {
15    pub kind: ErrorCode,
16    pub message: String,
17}
18
19impl HyperlightGuestError {
20    pub fn new(kind: ErrorCode, message: String) -> Self {
21        Self { kind, message }
22    }
23}
24
25impl From<anyhow::Error> for HyperlightGuestError {
26    fn from(error: anyhow::Error) -> Self {
27        Self {
28            kind: ErrorCode::GuestError,
29            message: format!("Error: {:?}", error),
30        }
31    }
32}
33
34impl From<serde_json::Error> for HyperlightGuestError {
35    fn from(error: serde_json::Error) -> Self {
36        Self {
37            kind: ErrorCode::GuestError,
38            message: format!("Error: {:?}", error),
39        }
40    }
41}
42
43impl From<FuncError> for HyperlightGuestError {
44    fn from(e: FuncError) -> Self {
45        match e {
46            FuncError::ParameterValueConversionFailure(..) => HyperlightGuestError::new(
47                ErrorCode::GuestFunctionParameterTypeMismatch,
48                e.to_string(),
49            ),
50            FuncError::ReturnValueConversionFailure(..) => HyperlightGuestError::new(
51                ErrorCode::GuestFunctionParameterTypeMismatch,
52                e.to_string(),
53            ),
54            FuncError::UnexpectedNoOfArguments(..) => HyperlightGuestError::new(
55                ErrorCode::GuestFunctionIncorrecNoOfParameters,
56                e.to_string(),
57            ),
58            FuncError::UnexpectedParameterValueType(..) => HyperlightGuestError::new(
59                ErrorCode::GuestFunctionParameterTypeMismatch,
60                e.to_string(),
61            ),
62            FuncError::UnexpectedReturnValueType(..) => HyperlightGuestError::new(
63                ErrorCode::GuestFunctionParameterTypeMismatch,
64                e.to_string(),
65            ),
66        }
67    }
68}
69
70/// Extension trait to add context to `Option<T>` and `Result<T, E>` types in guest code,
71/// converting them to `Result<T, HyperlightGuestError>`.
72///
73/// This is similar to anyhow::Context.
74pub trait GuestErrorContext {
75    type Ok;
76    /// Adds context to the error if `self` is `None` or `Err`.
77    fn context(self, ctx: impl Into<String>) -> Result<Self::Ok>;
78    /// Adds context and a specific error code to the error if `self` is `None` or `Err`.
79    fn context_and_code(self, ec: ErrorCode, ctx: impl Into<String>) -> Result<Self::Ok>;
80    /// Lazily adds context to the error if `self` is `None` or `Err`.
81    ///
82    /// This is useful if constructing the context message is expensive.
83    fn with_context<S: Into<String>>(self, ctx: impl FnOnce() -> S) -> Result<Self::Ok>;
84    /// Lazily adds context and a specific error code to the error if `self` is `None` or `Err`.
85    ///
86    /// This is useful if constructing the context message is expensive.
87    fn with_context_and_code<S: Into<String>>(
88        self,
89        ec: ErrorCode,
90        ctx: impl FnOnce() -> S,
91    ) -> Result<Self::Ok>;
92}
93
94impl<T> GuestErrorContext for Option<T> {
95    type Ok = T;
96    #[inline]
97    fn context(self, ctx: impl Into<String>) -> Result<T> {
98        self.with_context_and_code(ErrorCode::GuestError, || ctx)
99    }
100    #[inline]
101    fn context_and_code(self, ec: ErrorCode, ctx: impl Into<String>) -> Result<T> {
102        self.with_context_and_code(ec, || ctx)
103    }
104    #[inline]
105    fn with_context<S: Into<String>>(self, ctx: impl FnOnce() -> S) -> Result<T> {
106        self.with_context_and_code(ErrorCode::GuestError, ctx)
107    }
108    #[inline]
109    fn with_context_and_code<S: Into<String>>(
110        self,
111        ec: ErrorCode,
112        ctx: impl FnOnce() -> S,
113    ) -> Result<Self::Ok> {
114        match self {
115            Some(s) => Ok(s),
116            None => Err(HyperlightGuestError::new(ec, ctx().into())),
117        }
118    }
119}
120
121impl<T, E: core::fmt::Debug> GuestErrorContext for core::result::Result<T, E> {
122    type Ok = T;
123    #[inline]
124    fn context(self, ctx: impl Into<String>) -> Result<T> {
125        self.with_context_and_code(ErrorCode::GuestError, || ctx)
126    }
127    #[inline]
128    fn context_and_code(self, ec: ErrorCode, ctx: impl Into<String>) -> Result<T> {
129        self.with_context_and_code(ec, || ctx)
130    }
131    #[inline]
132    fn with_context<S: Into<String>>(self, ctx: impl FnOnce() -> S) -> Result<T> {
133        self.with_context_and_code(ErrorCode::GuestError, ctx)
134    }
135    #[inline]
136    fn with_context_and_code<S: Into<String>>(
137        self,
138        ec: ErrorCode,
139        ctx: impl FnOnce() -> S,
140    ) -> Result<T> {
141        match self {
142            Ok(s) => Ok(s),
143            Err(e) => Err(HyperlightGuestError::new(
144                ec,
145                format!("{}.\nCaused by: {e:?}", ctx().into()),
146            )),
147        }
148    }
149}
150
151/// Macro to return early with a `Err(HyperlightGuestError)`.
152/// Usage:
153/// ```ignore
154/// bail!(ErrorCode::UnknownError => "An error occurred: {}", details);
155/// // or
156/// bail!("A guest error occurred: {}", details); // defaults to ErrorCode::GuestError
157/// ```
158#[macro_export]
159macro_rules! bail {
160    ($ec:expr => $($msg:tt)*) => {
161        return ::core::result::Result::Err($crate::error::HyperlightGuestError::new($ec, ::alloc::format!($($msg)*)));
162    };
163    ($($msg:tt)*) => {
164        $crate::bail!($crate::error::ErrorCode::GuestError => $($msg)*);
165    };
166}
167
168/// Macro to ensure a condition is true, otherwise returns early with a `Err(HyperlightGuestError)`.
169/// Usage:
170/// ```ignore
171/// ensure!(1 + 1 == 3, ErrorCode::UnknownError => "Maths is broken: {}", details);
172/// // or
173/// ensure!(1 + 1 == 3, "Maths is broken: {}", details); // defaults to ErrorCode::GuestError
174/// // or
175/// ensure!(1 + 1 == 3); // defaults to ErrorCode::GuestError with a default message
176/// ```
177#[macro_export]
178macro_rules! ensure {
179    ($cond:expr) => {
180        if !($cond) {
181            $crate::bail!(::core::concat!("Condition failed: `", ::core::stringify!($cond), "`"));
182        }
183    };
184    ($cond:expr, $ec:expr => $($msg:tt)*) => {
185        if !($cond) {
186            $crate::bail!($ec => ::core::concat!("{}\nCaused by failed condition: `", ::core::stringify!($cond), "`"), ::core::format_args!($($msg)*));
187        }
188    };
189    ($cond:expr, $($msg:tt)*) => {
190        $crate::ensure!($cond, $crate::error::ErrorCode::GuestError => $($msg)*);
191    };
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197
198    #[test]
199    fn test_context_option_some() {
200        let value: Option<u32> = Some(42);
201        let result = value.context("Should be Some");
202        assert_eq!(result.unwrap(), 42);
203    }
204
205    #[test]
206    fn test_context_option_none() {
207        let value: Option<u32> = None;
208        let result = value.context("Should be Some");
209        let err = result.unwrap_err();
210        assert_eq!(err.kind, ErrorCode::GuestError);
211        assert_eq!(err.message, "Should be Some");
212    }
213
214    #[test]
215    fn test_context_and_code_option_none() {
216        let value: Option<u32> = None;
217        let result = value.context_and_code(ErrorCode::MallocFailed, "Should be Some");
218        let err = result.unwrap_err();
219        assert_eq!(err.kind, ErrorCode::MallocFailed);
220        assert_eq!(err.message, "Should be Some");
221    }
222
223    #[test]
224    fn test_with_context_option_none() {
225        let value: Option<u32> = None;
226        let result = value.with_context(|| "Lazy context message");
227        let err = result.unwrap_err();
228        assert_eq!(err.kind, ErrorCode::GuestError);
229        assert_eq!(err.message, "Lazy context message");
230    }
231
232    #[test]
233    fn test_with_context_and_code_option_none() {
234        let value: Option<u32> = None;
235        let result =
236            value.with_context_and_code(ErrorCode::MallocFailed, || "Lazy context message");
237        let err = result.unwrap_err();
238        assert_eq!(err.kind, ErrorCode::MallocFailed);
239        assert_eq!(err.message, "Lazy context message");
240    }
241
242    #[test]
243    fn test_context_result_ok() {
244        let value: core::result::Result<u32, &str> = Ok(42);
245        let result = value.context("Should be Ok");
246        assert_eq!(result.unwrap(), 42);
247    }
248
249    #[test]
250    fn test_context_result_err() {
251        let value: core::result::Result<u32, &str> = Err("Some error");
252        let result = value.context("Should be Ok");
253        let err = result.unwrap_err();
254        assert_eq!(err.kind, ErrorCode::GuestError);
255        assert_eq!(err.message, "Should be Ok.\nCaused by: \"Some error\"");
256    }
257
258    #[test]
259    fn test_context_and_code_result_err() {
260        let value: core::result::Result<u32, &str> = Err("Some error");
261        let result = value.context_and_code(ErrorCode::MallocFailed, "Should be Ok");
262        let err = result.unwrap_err();
263        assert_eq!(err.kind, ErrorCode::MallocFailed);
264        assert_eq!(err.message, "Should be Ok.\nCaused by: \"Some error\"");
265    }
266
267    #[test]
268    fn test_with_context_result_err() {
269        let value: core::result::Result<u32, &str> = Err("Some error");
270        let result = value.with_context(|| "Lazy context message");
271        let err = result.unwrap_err();
272        assert_eq!(err.kind, ErrorCode::GuestError);
273        assert_eq!(
274            err.message,
275            "Lazy context message.\nCaused by: \"Some error\""
276        );
277    }
278
279    #[test]
280    fn test_with_context_and_code_result_err() {
281        let value: core::result::Result<u32, &str> = Err("Some error");
282        let result =
283            value.with_context_and_code(ErrorCode::MallocFailed, || "Lazy context message");
284        let err = result.unwrap_err();
285        assert_eq!(err.kind, ErrorCode::MallocFailed);
286        assert_eq!(
287            err.message,
288            "Lazy context message.\nCaused by: \"Some error\""
289        );
290    }
291
292    #[test]
293    fn test_bail_macro() {
294        let result: Result<u32> = (|| {
295            bail!("A guest error occurred");
296        })();
297        let err = result.unwrap_err();
298        assert_eq!(err.kind, ErrorCode::GuestError);
299        assert_eq!(err.message, "A guest error occurred");
300    }
301
302    #[test]
303    fn test_bail_macro_with_error_code() {
304        let result: Result<u32> = (|| {
305            bail!(ErrorCode::MallocFailed => "Memory allocation failed");
306        })();
307        let err = result.unwrap_err();
308        assert_eq!(err.kind, ErrorCode::MallocFailed);
309        assert_eq!(err.message, "Memory allocation failed");
310    }
311
312    #[test]
313    fn test_ensure_macro_pass() {
314        let result: Result<u32> = (|| {
315            ensure!(1 + 1 == 2, "Math works");
316            Ok(42)
317        })();
318        assert_eq!(result.unwrap(), 42);
319    }
320
321    #[test]
322    fn test_ensure_macro_fail() {
323        let result: Result<u32> = (|| {
324            ensure!(1 + 1 == 3, "Math is broken");
325            Ok(42)
326        })();
327        let err = result.unwrap_err();
328        assert_eq!(err.kind, ErrorCode::GuestError);
329        assert_eq!(
330            err.message,
331            "Math is broken\nCaused by failed condition: `1 + 1 == 3`"
332        );
333    }
334
335    #[test]
336    fn test_ensure_macro_fail_no_message() {
337        let result: Result<u32> = (|| {
338            ensure!(1 + 1 == 3);
339            Ok(42)
340        })();
341        let err = result.unwrap_err();
342        assert_eq!(err.kind, ErrorCode::GuestError);
343        assert_eq!(err.message, "Condition failed: `1 + 1 == 3`");
344    }
345
346    #[test]
347    fn test_ensure_macro_fail_with_error_code() {
348        let result: Result<u32> = (|| {
349            ensure!(1 + 1 == 3, ErrorCode::UnknownError => "Math is broken");
350            Ok(42)
351        })();
352        let err = result.unwrap_err();
353        assert_eq!(err.kind, ErrorCode::UnknownError);
354        assert_eq!(
355            err.message,
356            "Math is broken\nCaused by failed condition: `1 + 1 == 3`"
357        );
358    }
359}