1#![deny(missing_docs)]
66
67mod 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
81use self::generated::wasi::keyvalue;
82use std::collections::HashMap;
83use wasmtime::Result;
84use wasmtime::component::{HasData, Resource, ResourceTable, ResourceTableError};
85
86#[doc(hidden)]
87pub enum Error {
88 NoSuchStore,
89 AccessDenied,
90 Other(String),
91}
92
93impl From<ResourceTableError> for Error {
94 fn from(err: ResourceTableError) -> Self {
95 Self::Other(err.to_string())
96 }
97}
98
99#[doc(hidden)]
100pub struct Bucket {
101 in_memory_data: HashMap<String, Vec<u8>>,
102}
103
104#[derive(Default)]
106pub struct WasiKeyValueCtxBuilder {
107 in_memory_data: HashMap<String, Vec<u8>>,
108}
109
110impl WasiKeyValueCtxBuilder {
111 pub fn new() -> Self {
113 Default::default()
114 }
115
116 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 pub fn build(self) -> WasiKeyValueCtx {
132 WasiKeyValueCtx {
133 in_memory_data: self.in_memory_data,
134 }
135 }
136}
137
138pub struct WasiKeyValueCtx {
140 in_memory_data: HashMap<String, Vec<u8>>,
141}
142
143impl WasiKeyValueCtx {
144 pub fn builder() -> WasiKeyValueCtxBuilder {
146 WasiKeyValueCtxBuilder::new()
147 }
148}
149
150pub struct WasiKeyValue<'a> {
152 ctx: &'a WasiKeyValueCtx,
153 table: &'a mut ResourceTable,
154}
155
156impl<'a> WasiKeyValue<'a> {
157 pub fn new(ctx: &'a WasiKeyValueCtx, table: &'a mut ResourceTable) -> Self {
159 Self { ctx, table }
160 }
161}
162
163impl 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
182impl 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
226impl 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
248impl 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
287pub 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
298struct HasWasiKeyValue;
299
300impl HasData for HasWasiKeyValue {
301 type Data<'a> = WasiKeyValue<'a>;
302}