1#![deny(missing_docs)]
68
69mod 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
83use self::generated::wasi::keyvalue;
84use anyhow::Result;
85use std::collections::HashMap;
86use wasmtime::component::{HasData, Resource, ResourceTable, ResourceTableError};
87
88#[doc(hidden)]
89pub enum Error {
90 NoSuchStore,
91 AccessDenied,
92 Other(String),
93}
94
95impl From<ResourceTableError> for Error {
96 fn from(err: ResourceTableError) -> Self {
97 Self::Other(err.to_string())
98 }
99}
100
101#[doc(hidden)]
102pub struct Bucket {
103 in_memory_data: HashMap<String, Vec<u8>>,
104}
105
106#[derive(Default)]
108pub struct WasiKeyValueCtxBuilder {
109 in_memory_data: HashMap<String, Vec<u8>>,
110}
111
112impl WasiKeyValueCtxBuilder {
113 pub fn new() -> Self {
115 Default::default()
116 }
117
118 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 pub fn build(self) -> WasiKeyValueCtx {
134 WasiKeyValueCtx {
135 in_memory_data: self.in_memory_data,
136 }
137 }
138}
139
140pub struct WasiKeyValueCtx {
142 in_memory_data: HashMap<String, Vec<u8>>,
143}
144
145impl WasiKeyValueCtx {
146 pub fn builder() -> WasiKeyValueCtxBuilder {
148 WasiKeyValueCtxBuilder::new()
149 }
150}
151
152pub struct WasiKeyValue<'a> {
154 ctx: &'a WasiKeyValueCtx,
155 table: &'a mut ResourceTable,
156}
157
158impl<'a> WasiKeyValue<'a> {
159 pub fn new(ctx: &'a WasiKeyValueCtx, table: &'a mut ResourceTable) -> Self {
161 Self { ctx, table }
162 }
163}
164
165impl 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
184impl 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
228impl 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
250impl 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
289pub 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
300struct HasWasiKeyValue;
301
302impl HasData for HasWasiKeyValue {
303 type Data<'a> = WasiKeyValue<'a>;
304}