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