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