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 {
from(err: ResourceTableError) -> Self94 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.
new() -> Self112 pub fn new() -> Self {
113 Default::default()
114 }
115
116 /// Preset data for the In-Memory provider.
in_memory_data<I, K, V>(mut self, data: I) -> Self where I: IntoIterator<Item = (K, V)>, K: Into<String>, V: Into<Vec<u8>>,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`].
build(self) -> WasiKeyValueCtx131 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`].
builder() -> WasiKeyValueCtxBuilder145 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.
new(ctx: &'a WasiKeyValueCtx, table: &'a mut ResourceTable) -> Self158 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<'_> {
open(&mut self, identifier: String) -> Result<Resource<Bucket>, Error>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
convert_error(&mut self, err: Error) -> Result<keyvalue::store::Error>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<'_> {
get(&mut self, bucket: Resource<Bucket>, key: String) -> Result<Option<Vec<u8>>, Error>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
set(&mut self, bucket: Resource<Bucket>, key: String, value: Vec<u8>) -> Result<(), Error>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
delete(&mut self, bucket: Resource<Bucket>, key: String) -> Result<(), Error>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
exists(&mut self, bucket: Resource<Bucket>, key: String) -> Result<bool, Error>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
list_keys( &mut self, bucket: Resource<Bucket>, cursor: Option<u64>, ) -> Result<keyvalue::store::KeyResponse, Error>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
drop(&mut self, bucket: Resource<Bucket>) -> Result<()>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<'_> {
increment( &mut self, bucket: Resource<Bucket>, key: String, delta: u64, ) -> Result<u64, Error>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<'_> {
get_many( &mut self, bucket: Resource<Bucket>, keys: Vec<String>, ) -> Result<Vec<Option<(String, Vec<u8>)>>, Error>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
set_many( &mut self, bucket: Resource<Bucket>, key_values: Vec<(String, Vec<u8>)>, ) -> Result<(), Error>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
delete_many(&mut self, bucket: Resource<Bucket>, keys: Vec<String>) -> Result<(), Error>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`].
add_to_linker<T: Send + 'static>( l: &mut wasmtime::component::Linker<T>, f: fn(&mut T) -> WasiKeyValue<'_>, ) -> Result<()>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