1 //! Example of instantiating a WASIp2 component with the use of resource
2 
3 /*
4 You can execute this example with:
5     cmake examples/
6     cargo run --example resource-component
7 */
8 
9 use std::collections::HashMap;
10 
11 use wasmtime::component::bindgen;
12 use wasmtime::component::{Component, Linker, ResourceTable};
13 use wasmtime::component::{HasSelf, Resource};
14 use wasmtime::{Engine, Result, Store};
15 use wasmtime_wasi::p2::add_to_linker_async;
16 use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
17 
18 pub struct ComponentRunStates {
19     // These two are required basically as a standard way to enable the impl of IoView and
20     // WasiView.
21     // impl of WasiView is required by [`wasmtime_wasi::p2::add_to_linker_sync`]
22     pub wasi_ctx: WasiCtx,
23     pub resource_table: ResourceTable,
24     // You can add other custom host states if needed
25 }
26 
27 impl WasiView for ComponentRunStates {
ctx(&mut self) -> WasiCtxView<'_>28     fn ctx(&mut self) -> WasiCtxView<'_> {
29         WasiCtxView {
30             ctx: &mut self.wasi_ctx,
31             table: &mut self.resource_table,
32         }
33     }
34 }
35 
36 impl ComponentRunStates {
new() -> Self37     pub fn new() -> Self {
38         // Create a WASI context and put it in a Store; all instances in the store
39         // share this context. `WasiCtx` provides a number of ways to
40         // configure what the target program will have access to.
41         ComponentRunStates {
42             wasi_ctx: WasiCtx::builder().build(),
43             resource_table: ResourceTable::new(),
44         }
45     }
46 }
47 
48 bindgen!({
49     path: "./examples/resource-component/kv-store.wit",
50     world: "kv-database",
51     // Interactions with `ResourceTable` can possibly trap so enable the ability
52     // to return traps from generated functions.
53     imports: { default: async | trappable },
54     exports: { default: async },
55     with: {
56         "example:kv-store/kvdb.connection": Connection
57     },
58 });
59 
60 pub struct Connection {
61     pub storage: HashMap<String, String>,
62 }
63 
64 impl KvDatabaseImports for ComponentRunStates {
log(&mut self, msg: String) -> Result<(), wasmtime::Error>65     async fn log(&mut self, msg: String) -> Result<(), wasmtime::Error> {
66         // provide host function to the component
67         println!("Log: {msg}");
68         Ok(())
69     }
70 }
71 
72 impl example::kv_store::kvdb::Host for ComponentRunStates {}
73 
74 impl example::kv_store::kvdb::HostConnection for ComponentRunStates {
new(&mut self) -> Result<Resource<Connection>, wasmtime::Error>75     async fn new(&mut self) -> Result<Resource<Connection>, wasmtime::Error> {
76         Ok(self.resource_table.push(Connection {
77             storage: HashMap::new(),
78         })?)
79     }
80 
get( &mut self, resource: Resource<Connection>, key: String, ) -> Result<Option<String>, wasmtime::Error>81     async fn get(
82         &mut self,
83         resource: Resource<Connection>,
84         key: String,
85     ) -> Result<Option<String>, wasmtime::Error> {
86         let connection = self.resource_table.get(&resource)?;
87         Ok(connection.storage.get(&key).cloned())
88     }
89 
set( &mut self, resource: Resource<Connection>, key: String, value: String, ) -> Result<()>90     async fn set(
91         &mut self,
92         resource: Resource<Connection>,
93         key: String,
94         value: String,
95     ) -> Result<()> {
96         let connection = self.resource_table.get_mut(&resource)?;
97         connection.storage.insert(key, value);
98         Ok(())
99     }
100 
remove( &mut self, resource: Resource<Connection>, key: String, ) -> Result<Option<String>>101     async fn remove(
102         &mut self,
103         resource: Resource<Connection>,
104         key: String,
105     ) -> Result<Option<String>> {
106         let connection = self.resource_table.get_mut(&resource)?;
107         Ok(connection.storage.remove(&key))
108     }
109 
clear(&mut self, resource: Resource<Connection>) -> Result<(), wasmtime::Error>110     async fn clear(&mut self, resource: Resource<Connection>) -> Result<(), wasmtime::Error> {
111         let large_string = self.resource_table.get_mut(&resource)?;
112         large_string.storage.clear();
113         Ok(())
114     }
115 
drop(&mut self, resource: Resource<Connection>) -> Result<()>116     async fn drop(&mut self, resource: Resource<Connection>) -> Result<()> {
117         let _ = self.resource_table.delete(resource)?;
118         Ok(())
119     }
120 }
121 
122 #[tokio::main]
main() -> Result<()>123 async fn main() -> Result<()> {
124     let engine = Engine::default();
125     let mut linker = Linker::new(&engine);
126     let state = ComponentRunStates::new();
127     let mut store = Store::new(&engine, state);
128 
129     KvDatabase::add_to_linker::<_, HasSelf<_>>(&mut linker, |s| s)?;
130     add_to_linker_async(&mut linker)?;
131 
132     // Instantiate our component with the imports we've created, and run its function
133     let component = Component::from_file(&engine, "target/wasm32-wasip2/debug/guest_kvdb.wasm")?;
134     let bindings = KvDatabase::instantiate_async(&mut store, &component, &linker).await?;
135     let result = bindings
136         .call_replace_value(&mut store, "hello", "world")
137         .await?;
138     assert_eq!(result, None);
139     let result = bindings
140         .call_replace_value(&mut store, "hello", "wasmtime")
141         .await?;
142     assert_eq!(result, Some("world".to_string()));
143     Ok(())
144 }
145