its_wasmtime/
lib.rs

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}