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