1use wasmtime::{component::Linker, Config, Engine, Store};
2use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView};
3
4pub struct RuntimeView<T: NestedView> {
5 pub table: ResourceTable,
6 pub ctx: WasiCtx,
7 pub nested_view: T,
8}
9
10impl<T> RuntimeView<T>
11where
12 T: NestedView,
13{
14 fn new(nested_view: T) -> Self {
15 let table = ResourceTable::new();
16 let ctx = WasiCtxBuilder::new().inherit_stdio().build();
17
18 Self {
19 table,
20 ctx,
21 nested_view,
22 }
23 }
24}
25
26impl<T> WasiView for RuntimeView<T>
27where
28 T: Send + NestedView,
29{
30 fn table(&mut self) -> &mut ResourceTable {
31 &mut self.table
32 }
33
34 fn ctx(&mut self) -> &mut WasiCtx {
35 &mut self.ctx
36 }
37}
38
39pub trait NestedView: Send + Sized {
40 fn add_all_to_linker(&mut self, linker: &mut Linker<RuntimeView<Self>>) -> anyhow::Result<()>;
41}
42
43pub struct Runtime<T: NestedView> {
44 pub engine: Engine,
45 pub linker: Linker<RuntimeView<T>>,
46 pub store: Store<RuntimeView<T>>,
47}
48
49pub fn runtime<T>(with_wasi: bool, mut nested_view: T) -> anyhow::Result<Runtime<T>>
50where
51 T: NestedView,
52{
53 let config = {
54 let mut config = Config::new();
55 config.wasm_component_model(true);
56 config.async_support(true);
57 config
58 };
59
60 let engine = Engine::new(&config)?;
61
62 let mut linker = Linker::new(&engine);
63
64 if with_wasi {
65 wasmtime_wasi::add_to_linker_async(&mut linker)?;
66 }
67
68 nested_view.add_all_to_linker(&mut linker)?;
69
70 let runtime_view = RuntimeView::new(nested_view);
71 let store = Store::new(&engine, runtime_view);
72
73 Ok(Runtime {
74 engine,
75 linker,
76 store,
77 })
78}
79
80#[cfg(test)]
81mod simple_component_test {
82 use super::*;
83 use wasmtime::{component::Component, AsContextMut};
84 use wasmtime_wasi::async_trait;
85
86 wasmtime::component::bindgen!({
87 path: "./tests/simple_component/wit/world.wit",
88 world: "example",
89 async: true,
90 });
91
92 struct SimpleComponentView {
93 message: String,
94 }
95
96 #[async_trait]
97 impl host::Host for SimpleComponentView {
98 async fn get_data(&mut self) -> wasmtime::Result<String> {
99 Ok(self.message.clone())
100 }
101 }
102
103 impl NestedView for SimpleComponentView {
104 fn add_all_to_linker(
105 &mut self,
106 linker: &mut Linker<RuntimeView<Self>>,
107 ) -> anyhow::Result<()> {
108 Ok(host::add_to_linker(linker, |v| &mut v.nested_view)?)
109 }
110 }
111
112 #[tokio::test]
113 async fn it_invokes_simple_component() {
114 let nested_view = SimpleComponentView {
115 message: "Hello, World!".into(),
116 };
117
118 let mut runtime = runtime(true, nested_view).expect("Failed to build runtime");
119
120 let component = Component::from_file(
121 &runtime.engine,
122 "./tests/simple_component/target/wasm32-wasi/debug/simple_component.wasm",
123 )
124 .expect(
125 "Failed to load component from disk. Did you compile it using `cargo component build`?",
126 );
127
128 let (instance, _) =
129 Example::instantiate_async(&mut runtime.store, &component, &runtime.linker)
130 .await
131 .expect("failed to instantiate component");
132
133 let store = runtime.store.as_context_mut();
134
135 let result = instance
136 .call_hello_world(store)
137 .await
138 .expect("failed to invoke demo function");
139
140 assert_eq!(result, "Hello, World! 0");
141
142 let store = runtime.store.as_context_mut();
143
144 let result = instance
145 .call_hello_world(store)
146 .await
147 .expect("failed to invoke demo function");
148
149 assert_eq!(result, "Hello, World! 1");
150 }
151}
152
153#[cfg(test)]
154mod simple_resource_test {
155 use self::component::simple_resource;
156
157 use super::*;
158 use anyhow::Ok;
159 use wasmtime::component::Component;
160 use wasmtime_wasi::async_trait;
161
162 wasmtime::component::bindgen!({
163 path: "./tests/simple_resource/wit/world.wit",
164 world: "example",
165 async: true,
166 with: {
167 "component:simple-resource/some-resource/foo-resource": SomeResource,
168 },
169 });
170
171 pub struct SomeResource {
172 message: String,
173 }
174
175 pub struct ResourceView {
176 table: ResourceTable,
177 }
178
179 impl NestedView for ResourceView {
180 fn add_all_to_linker(
181 &mut self,
182 linker: &mut Linker<RuntimeView<Self>>,
183 ) -> anyhow::Result<()> {
184 simple_resource::some_resource::add_to_linker(linker, |v| &mut v.nested_view)
185 }
186 }
187
188 impl simple_resource::some_resource::Host for ResourceView {}
189
190 #[async_trait]
191 impl simple_resource::some_resource::HostFooResource for ResourceView {
192 async fn foo(
193 &mut self,
194 this: wasmtime::component::Resource<simple_resource::some_resource::FooResource>,
195 ) -> wasmtime::Result<String> {
196 let value = self.table.get(&this);
197
198 Ok(value.map(|v| v.message.clone())?)
199 }
200
201 async fn new(
202 &mut self,
203 ) -> wasmtime::Result<
204 wasmtime::component::Resource<simple_resource::some_resource::FooResource>,
205 > {
206 Ok(self.table.push(SomeResource {
207 message: "noodles".into(),
208 })?)
209 }
210
211 fn drop(
212 &mut self,
213 rep: wasmtime::component::Resource<simple_resource::some_resource::FooResource>,
214 ) -> wasmtime::Result<()> {
215 let _ = self.table.delete(rep)?;
216 Ok(())
217 }
218 }
219
220 #[tokio::test]
221 async fn test() {
222 let nested_view = ResourceView {
223 table: ResourceTable::new(),
224 };
225
226 let mut runtime = runtime(true, nested_view).expect("Failed to build runtime");
227
228 let component = Component::from_file(
229 &runtime.engine,
230 "./tests/simple_resource/target/wasm32-wasi/debug/simple_resource.wasm",
231 )
232 .expect(
233 "Failed to load component from disk. Did you compile it using `cargo component build`?",
234 );
235
236 let (instance, _) =
237 Example::instantiate_async(&mut runtime.store, &component, &runtime.linker)
238 .await
239 .expect("failed to instantiate component");
240
241 let result = instance
242 .call_test(runtime.store)
243 .await
244 .expect("failed to invoke");
245 assert_eq!(result, "Hello, World! noodles")
246 }
247}