1use 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
70pub trait GuestErrorContext {
75 type Ok;
76 fn context(self, ctx: impl Into<String>) -> Result<Self::Ok>;
78 fn context_and_code(self, ec: ErrorCode, ctx: impl Into<String>) -> Result<Self::Ok>;
80 fn with_context<S: Into<String>>(self, ctx: impl FnOnce() -> S) -> Result<Self::Ok>;
84 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_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_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}