1 //! # Wasmtime's [wasi-keyvalue] Implementation 2 //! 3 //! This crate provides a Wasmtime host implementation of the [wasi-keyvalue] 4 //! API. With this crate, the runtime can run components that call APIs in 5 //! [wasi-keyvalue] and provide components with access to key-value storages. 6 //! 7 //! Currently supported storage backends: 8 //! * In-Memory (empty identifier) 9 //! 10 //! # Examples 11 //! 12 //! The usage of this crate is very similar to other WASI API implementations 13 //! such as [wasi:cli] and [wasi:http]. 14 //! 15 //! A common scenario is accessing KV store in a [wasi:cli] component. 16 //! A standalone example of doing all this looks like: 17 //! 18 //! ``` 19 //! use wasmtime::{ 20 //! component::{Linker, ResourceTable}, 21 //! Engine, Result, Store, 22 //! }; 23 //! use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; 24 //! use wasmtime_wasi_keyvalue::{WasiKeyValue, WasiKeyValueCtx, WasiKeyValueCtxBuilder}; 25 //! 26 //! #[tokio::main] 27 //! async fn main() -> Result<()> { 28 //! let engine = Engine::default(); 29 //! 30 //! let mut store = Store::new(&engine, Ctx { 31 //! table: ResourceTable::new(), 32 //! wasi_ctx: WasiCtx::builder().build(), 33 //! wasi_keyvalue_ctx: WasiKeyValueCtxBuilder::new().build(), 34 //! }); 35 //! 36 //! let mut linker = Linker::<Ctx>::new(&engine); 37 //! wasmtime_wasi::p2::add_to_linker_async(&mut linker)?; 38 //! // add `wasi-keyvalue` world's interfaces to the linker 39 //! wasmtime_wasi_keyvalue::add_to_linker(&mut linker, |h: &mut Ctx| { 40 //! WasiKeyValue::new(&h.wasi_keyvalue_ctx, &mut h.table) 41 //! })?; 42 //! 43 //! // ... use `linker` to instantiate within `store` ... 44 //! 45 //! Ok(()) 46 //! } 47 //! 48 //! struct Ctx { 49 //! table: ResourceTable, 50 //! wasi_ctx: WasiCtx, 51 //! wasi_keyvalue_ctx: WasiKeyValueCtx, 52 //! } 53 //! 54 //! impl WasiView for Ctx { 55 //! fn ctx(&mut self) -> WasiCtxView<'_> { 56 //! WasiCtxView { ctx: &mut self.wasi_ctx, table: &mut self.table } 57 //! } 58 //! } 59 //! ``` 60 //! 61 //! [wasi-keyvalue]: https://github.com/WebAssembly/wasi-keyvalue 62 //! [wasi:cli]: https://docs.rs/wasmtime-wasi/latest 63 //! [wasi:http]: https://docs.rs/wasmtime-wasi-http/latest 64 65 #![deny(missing_docs)] 66 67 mod generated { 68 wasmtime::component::bindgen!({ 69 path: "wit", 70 world: "wasi:keyvalue/imports", 71 imports: { default: trappable }, 72 with: { 73 "wasi:keyvalue/store.bucket": crate::Bucket, 74 }, 75 trappable_error_type: { 76 "wasi:keyvalue/store.error" => crate::Error, 77 }, 78 }); 79 } 80 81 use self::generated::wasi::keyvalue; 82 use std::collections::HashMap; 83 use wasmtime::Result; 84 use wasmtime::component::{HasData, Resource, ResourceTable, ResourceTableError}; 85 86 #[doc(hidden)] 87 pub enum Error { 88 NoSuchStore, 89 AccessDenied, 90 Other(String), 91 } 92 93 impl From<ResourceTableError> for Error { 94 fn from(err: ResourceTableError) -> Self { 95 Self::Other(err.to_string()) 96 } 97 } 98 99 #[doc(hidden)] 100 pub struct Bucket { 101 in_memory_data: HashMap<String, Vec<u8>>, 102 } 103 104 /// Builder-style structure used to create a [`WasiKeyValueCtx`]. 105 #[derive(Default)] 106 pub struct WasiKeyValueCtxBuilder { 107 in_memory_data: HashMap<String, Vec<u8>>, 108 } 109 110 impl WasiKeyValueCtxBuilder { 111 /// Creates a builder for a new context with default parameters set. 112 pub fn new() -> Self { 113 Default::default() 114 } 115 116 /// Preset data for the In-Memory provider. 117 pub fn in_memory_data<I, K, V>(mut self, data: I) -> Self 118 where 119 I: IntoIterator<Item = (K, V)>, 120 K: Into<String>, 121 V: Into<Vec<u8>>, 122 { 123 self.in_memory_data = data 124 .into_iter() 125 .map(|(k, v)| (k.into(), v.into())) 126 .collect(); 127 self 128 } 129 130 /// Uses the configured context so far to construct the final [`WasiKeyValueCtx`]. 131 pub fn build(self) -> WasiKeyValueCtx { 132 WasiKeyValueCtx { 133 in_memory_data: self.in_memory_data, 134 } 135 } 136 } 137 138 /// Capture the state necessary for use in the `wasi-keyvalue` API implementation. 139 pub struct WasiKeyValueCtx { 140 in_memory_data: HashMap<String, Vec<u8>>, 141 } 142 143 impl WasiKeyValueCtx { 144 /// Convenience function for calling [`WasiKeyValueCtxBuilder::new`]. 145 pub fn builder() -> WasiKeyValueCtxBuilder { 146 WasiKeyValueCtxBuilder::new() 147 } 148 } 149 150 /// A wrapper capturing the needed internal `wasi-keyvalue` state. 151 pub struct WasiKeyValue<'a> { 152 ctx: &'a WasiKeyValueCtx, 153 table: &'a mut ResourceTable, 154 } 155 156 impl<'a> WasiKeyValue<'a> { 157 /// Create a new view into the `wasi-keyvalue` state. 158 pub fn new(ctx: &'a WasiKeyValueCtx, table: &'a mut ResourceTable) -> Self { 159 Self { ctx, table } 160 } 161 } 162 163 impl keyvalue::store::Host for WasiKeyValue<'_> { 164 fn open(&mut self, identifier: String) -> Result<Resource<Bucket>, Error> { 165 match identifier.as_str() { 166 "" => Ok(self.table.push(Bucket { 167 in_memory_data: self.ctx.in_memory_data.clone(), 168 })?), 169 _ => Err(Error::NoSuchStore), 170 } 171 } 172 173 fn convert_error(&mut self, err: Error) -> Result<keyvalue::store::Error> { 174 match err { 175 Error::NoSuchStore => Ok(keyvalue::store::Error::NoSuchStore), 176 Error::AccessDenied => Ok(keyvalue::store::Error::AccessDenied), 177 Error::Other(e) => Ok(keyvalue::store::Error::Other(e)), 178 } 179 } 180 } 181 182 impl keyvalue::store::HostBucket for WasiKeyValue<'_> { 183 fn get(&mut self, bucket: Resource<Bucket>, key: String) -> Result<Option<Vec<u8>>, Error> { 184 let bucket = self.table.get_mut(&bucket)?; 185 Ok(bucket.in_memory_data.get(&key).cloned()) 186 } 187 188 fn set(&mut self, bucket: Resource<Bucket>, key: String, value: Vec<u8>) -> Result<(), Error> { 189 let bucket = self.table.get_mut(&bucket)?; 190 bucket.in_memory_data.insert(key, value); 191 Ok(()) 192 } 193 194 fn delete(&mut self, bucket: Resource<Bucket>, key: String) -> Result<(), Error> { 195 let bucket = self.table.get_mut(&bucket)?; 196 bucket.in_memory_data.remove(&key); 197 Ok(()) 198 } 199 200 fn exists(&mut self, bucket: Resource<Bucket>, key: String) -> Result<bool, Error> { 201 let bucket = self.table.get_mut(&bucket)?; 202 Ok(bucket.in_memory_data.contains_key(&key)) 203 } 204 205 fn list_keys( 206 &mut self, 207 bucket: Resource<Bucket>, 208 cursor: Option<u64>, 209 ) -> Result<keyvalue::store::KeyResponse, Error> { 210 let bucket = self.table.get_mut(&bucket)?; 211 let keys: Vec<String> = bucket.in_memory_data.keys().cloned().collect(); 212 let cursor = cursor.unwrap_or(0) as usize; 213 let keys_slice = &keys[cursor..]; 214 Ok(keyvalue::store::KeyResponse { 215 keys: keys_slice.to_vec(), 216 cursor: None, 217 }) 218 } 219 220 fn drop(&mut self, bucket: Resource<Bucket>) -> Result<()> { 221 self.table.delete(bucket)?; 222 Ok(()) 223 } 224 } 225 226 impl keyvalue::atomics::Host for WasiKeyValue<'_> { 227 fn increment( 228 &mut self, 229 bucket: Resource<Bucket>, 230 key: String, 231 delta: u64, 232 ) -> Result<u64, Error> { 233 let bucket = self.table.get_mut(&bucket)?; 234 let value = bucket 235 .in_memory_data 236 .entry(key.clone()) 237 .or_insert("0".to_string().into_bytes()); 238 let current_value = String::from_utf8(value.clone()) 239 .map_err(|e| Error::Other(e.to_string()))? 240 .parse::<u64>() 241 .map_err(|e| Error::Other(e.to_string()))?; 242 let new_value = current_value + delta; 243 *value = new_value.to_string().into_bytes(); 244 Ok(new_value) 245 } 246 } 247 248 impl keyvalue::batch::Host for WasiKeyValue<'_> { 249 fn get_many( 250 &mut self, 251 bucket: Resource<Bucket>, 252 keys: Vec<String>, 253 ) -> Result<Vec<Option<(String, Vec<u8>)>>, Error> { 254 let bucket = self.table.get_mut(&bucket)?; 255 Ok(keys 256 .into_iter() 257 .map(|key| { 258 bucket 259 .in_memory_data 260 .get(&key) 261 .map(|value| (key.clone(), value.clone())) 262 }) 263 .collect()) 264 } 265 266 fn set_many( 267 &mut self, 268 bucket: Resource<Bucket>, 269 key_values: Vec<(String, Vec<u8>)>, 270 ) -> Result<(), Error> { 271 let bucket = self.table.get_mut(&bucket)?; 272 for (key, value) in key_values { 273 bucket.in_memory_data.insert(key, value); 274 } 275 Ok(()) 276 } 277 278 fn delete_many(&mut self, bucket: Resource<Bucket>, keys: Vec<String>) -> Result<(), Error> { 279 let bucket = self.table.get_mut(&bucket)?; 280 for key in keys { 281 bucket.in_memory_data.remove(&key); 282 } 283 Ok(()) 284 } 285 } 286 287 /// Add all the `wasi-keyvalue` world's interfaces to a [`wasmtime::component::Linker`]. 288 pub fn add_to_linker<T: Send + 'static>( 289 l: &mut wasmtime::component::Linker<T>, 290 f: fn(&mut T) -> WasiKeyValue<'_>, 291 ) -> Result<()> { 292 keyvalue::store::add_to_linker::<_, HasWasiKeyValue>(l, f)?; 293 keyvalue::atomics::add_to_linker::<_, HasWasiKeyValue>(l, f)?; 294 keyvalue::batch::add_to_linker::<_, HasWasiKeyValue>(l, f)?; 295 Ok(()) 296 } 297 298 struct HasWasiKeyValue; 299 300 impl HasData for HasWasiKeyValue { 301 type Data<'a> = WasiKeyValue<'a>; 302 } 303