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