1 /// Execute the wiggle guest conversion code to exercise it 2 mod convert_just_errno { 3 use wiggle::GuestMemory; 4 use wiggle::error::Result; 5 use wiggle_test::{HostMemory, WasiCtx, impl_errno}; 6 7 /// The `errors` argument to the wiggle gives us a hook to map a rich error 8 /// type like this one (typical of wiggle use cases in wasi-common and beyond) 9 /// down to the flat error enums that witx can specify. 10 #[derive(Debug, thiserror::Error)] 11 pub enum RichError { 12 #[error("Invalid argument: {0}")] 13 InvalidArg(String), 14 #[error("Won't cross picket line: {0}")] 15 PicketLine(String), 16 } 17 18 // Define an errno with variants corresponding to RichError. Use it in a 19 // trivial function. 20 wiggle::from_witx!({ 21 witx_literal: " 22 (typename $errno (enum (@witx tag u8) $ok $invalid_arg $picket_line)) 23 (module $one_error_conversion 24 (@interface func (export \"foo\") 25 (param $strike u32) 26 (result $err (expected (error $errno))))) 27 ", 28 errors: { errno => trappable ErrnoT }, 29 }); 30 31 impl_errno!(types::Errno); 32 33 impl From<RichError> for types::ErrnoT { from(rich: RichError) -> types::ErrnoT34 fn from(rich: RichError) -> types::ErrnoT { 35 match rich { 36 RichError::InvalidArg(s) => { 37 types::ErrnoT::from(types::Errno::InvalidArg).context(s) 38 } 39 RichError::PicketLine(s) => { 40 types::ErrnoT::from(types::Errno::PicketLine).context(s) 41 } 42 } 43 } 44 } 45 46 impl<'a> one_error_conversion::OneErrorConversion for WasiCtx<'a> { foo(&mut self, _memory: &mut GuestMemory<'_>, strike: u32) -> Result<(), types::ErrnoT>47 fn foo(&mut self, _memory: &mut GuestMemory<'_>, strike: u32) -> Result<(), types::ErrnoT> { 48 // We use the argument to this function to exercise all of the 49 // possible error cases we could hit here 50 match strike { 51 0 => Ok(()), 52 1 => Err(RichError::PicketLine(format!("I'm not a scab")))?, 53 _ => Err(RichError::InvalidArg(format!("out-of-bounds: {strike}")))?, 54 } 55 } 56 } 57 58 #[test] one_error_conversion_test()59 fn one_error_conversion_test() { 60 let mut ctx = WasiCtx::new(); 61 let mut host_memory = HostMemory::new(); 62 let mut memory = host_memory.guest_memory(); 63 64 // Exercise each of the branches in `foo`. 65 // Start with the success case: 66 let r0 = one_error_conversion::foo(&mut ctx, &mut memory, 0).unwrap(); 67 assert_eq!( 68 r0, 69 types::Errno::Ok as i32, 70 "Expected return value for strike=0" 71 ); 72 assert!(ctx.log.borrow().is_empty(), "No error log for strike=0"); 73 74 // First error case: 75 let r1 = one_error_conversion::foo(&mut ctx, &mut memory, 1).unwrap(); 76 assert_eq!( 77 r1, 78 types::Errno::PicketLine as i32, 79 "Expected return value for strike=1" 80 ); 81 82 // Second error case: 83 let r2 = one_error_conversion::foo(&mut ctx, &mut memory, 2).unwrap(); 84 assert_eq!( 85 r2, 86 types::Errno::InvalidArg as i32, 87 "Expected return value for strike=2" 88 ); 89 } 90 } 91 92 /// Type-check the wiggle guest conversion code against a more complex case where 93 /// we use two distinct error types. 94 mod convert_multiple_error_types { 95 pub use super::convert_just_errno::RichError; 96 use wiggle::GuestMemory; 97 use wiggle::error::Result; 98 use wiggle_test::{WasiCtx, impl_errno}; 99 100 /// Test that we can map multiple types of errors. 101 #[derive(Debug, thiserror::Error)] 102 #[expect(dead_code, reason = "testing codegen below")] 103 pub enum AnotherRichError { 104 #[error("I've had this many cups of coffee and can't even think straight: {0}")] 105 TooMuchCoffee(usize), 106 } 107 108 // Just like the prior test, except that we have a second errno type. This should mean there 109 // are two functions in UserErrorConversion. 110 // Additionally, test that the function "baz" marked noreturn always returns a wasmtime::Trap. 111 wiggle::from_witx!({ 112 witx_literal: " 113 (typename $errno (enum (@witx tag u8) $ok $invalid_arg $picket_line)) 114 (typename $errno2 (enum (@witx tag u8) $ok $too_much_coffee)) 115 (module $two_error_conversions 116 (@interface func (export \"foo\") 117 (param $strike u32) 118 (result $err (expected (error $errno)))) 119 (@interface func (export \"bar\") 120 (param $drink u32) 121 (result $err (expected (error $errno2)))) 122 (@interface func (export \"baz\") 123 (param $drink u32) 124 (@witx noreturn))) 125 ", 126 errors: { errno => RichError, errno2 => AnotherRichError }, 127 }); 128 129 impl_errno!(types::Errno); 130 impl_errno!(types::Errno2); 131 132 // The UserErrorConversion trait will also have two methods for this test. They correspond to 133 // each member of the `errors` mapping. 134 // Bodies elided. 135 impl<'a> types::UserErrorConversion for WasiCtx<'a> { errno_from_rich_error(&mut self, _e: RichError) -> Result<types::Errno>136 fn errno_from_rich_error(&mut self, _e: RichError) -> Result<types::Errno> { 137 unimplemented!() 138 } errno2_from_another_rich_error( &mut self, _e: AnotherRichError, ) -> Result<types::Errno2>139 fn errno2_from_another_rich_error( 140 &mut self, 141 _e: AnotherRichError, 142 ) -> Result<types::Errno2> { 143 unimplemented!() 144 } 145 } 146 147 // And here's the witx module trait impl, bodies elided 148 impl<'a> two_error_conversions::TwoErrorConversions for WasiCtx<'a> { foo(&mut self, _: &mut GuestMemory<'_>, _: u32) -> Result<(), RichError>149 fn foo(&mut self, _: &mut GuestMemory<'_>, _: u32) -> Result<(), RichError> { 150 unimplemented!() 151 } bar(&mut self, _: &mut GuestMemory<'_>, _: u32) -> Result<(), AnotherRichError>152 fn bar(&mut self, _: &mut GuestMemory<'_>, _: u32) -> Result<(), AnotherRichError> { 153 unimplemented!() 154 } baz(&mut self, _: &mut GuestMemory<'_>, _: u32) -> wiggle::error::Error155 fn baz(&mut self, _: &mut GuestMemory<'_>, _: u32) -> wiggle::error::Error { 156 unimplemented!() 157 } 158 } 159 } 160