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::{Config, Engine, Result, Store};
15 use wasmtime_wasi::p2::add_to_linker_async;
16 use wasmtime_wasi::p2::{IoView, WasiCtx, WasiCtxBuilder, 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 IoView for ComponentRunStates {
28     fn table(&mut self) -> &mut ResourceTable {
29         &mut self.resource_table
30     }
31 }
32 impl WasiView for ComponentRunStates {
33     fn ctx(&mut self) -> &mut WasiCtx {
34         &mut self.wasi_ctx
35     }
36 }
37 
38 impl ComponentRunStates {
39     pub fn new() -> Self {
40         // Create a WASI context and put it in a Store; all instances in the store
41         // share this context. `WasiCtxBuilder` provides a number of ways to
42         // configure what the target program will have access to.
43         ComponentRunStates {
44             wasi_ctx: WasiCtxBuilder::new().build(),
45             resource_table: ResourceTable::new(),
46         }
47     }
48 }
49 
50 bindgen!({
51     path: "./examples/resource-component/kv-store.wit",
52     world: "kv-database",
53     async: true,
54     with: {
55         "example:kv-store/kvdb/connection": Connection
56     },
57     // Interactions with `ResourceTable` can possibly trap so enable the ability
58     // to return traps from generated functions.
59     trappable_imports: true,
60 });
61 
62 pub struct Connection {
63     pub storage: HashMap<String, String>,
64 }
65 
66 impl KvDatabaseImports for ComponentRunStates {
67     async fn log(&mut self, msg: String) -> Result<(), wasmtime::Error> {
68         // provide host function to the component
69         println!("Log: {msg}");
70         Ok(())
71     }
72 }
73 
74 impl example::kv_store::kvdb::Host for ComponentRunStates {}
75 
76 impl example::kv_store::kvdb::HostConnection for ComponentRunStates {
77     async fn new(&mut self) -> Result<Resource<Connection>, wasmtime::Error> {
78         Ok(self.resource_table.push(Connection {
79             storage: HashMap::new(),
80         })?)
81     }
82 
83     async fn get(
84         &mut self,
85         resource: Resource<Connection>,
86         key: String,
87     ) -> Result<Option<String>, wasmtime::Error> {
88         let connection = self.resource_table.get(&resource)?;
89         Ok(connection.storage.get(&key).cloned())
90     }
91 
92     async fn set(
93         &mut self,
94         resource: Resource<Connection>,
95         key: String,
96         value: String,
97     ) -> Result<()> {
98         let connection = self.resource_table.get_mut(&resource)?;
99         connection.storage.insert(key, value);
100         Ok(())
101     }
102 
103     async fn remove(
104         &mut self,
105         resource: Resource<Connection>,
106         key: String,
107     ) -> Result<Option<String>> {
108         let connection = self.resource_table.get_mut(&resource)?;
109         Ok(connection.storage.remove(&key))
110     }
111 
112     async fn clear(&mut self, resource: Resource<Connection>) -> Result<(), wasmtime::Error> {
113         let large_string = self.resource_table.get_mut(&resource)?;
114         large_string.storage.clear();
115         Ok(())
116     }
117 
118     async fn drop(&mut self, resource: Resource<Connection>) -> Result<()> {
119         let _ = self.resource_table.delete(resource)?;
120         Ok(())
121     }
122 }
123 
124 #[tokio::main]
125 async fn main() -> Result<()> {
126     // Construct the wasm engine with async support enabled.
127     let mut config = Config::new();
128     config.async_support(true);
129     let engine = Engine::new(&config)?;
130     let mut linker = Linker::new(&engine);
131     let state = ComponentRunStates::new();
132     let mut store = Store::new(&engine, state);
133 
134     KvDatabase::add_to_linker::<_, HasSelf<_>>(&mut linker, |s| s)?;
135     add_to_linker_async(&mut linker)?;
136 
137     // Instantiate our component with the imports we've created, and run its function
138     let component = Component::from_file(&engine, "target/wasm32-wasip2/debug/guest_kvdb.wasm")?;
139     let bindings = KvDatabase::instantiate_async(&mut store, &component, &linker).await?;
140     let result = bindings
141         .call_replace_value(&mut store, "hello", "world")
142         .await?;
143     assert_eq!(result, None);
144     let result = bindings
145         .call_replace_value(&mut store, "hello", "wasmtime")
146         .await?;
147     assert_eq!(result, Some("world".to_string()));
148     Ok(())
149 }
150