Path: blob/main/crates/wasi-keyvalue/src/lib.rs
1692 views
//! # Wasmtime's [wasi-keyvalue] Implementation1//!2//! This crate provides a Wasmtime host implementation of the [wasi-keyvalue]3//! API. With this crate, the runtime can run components that call APIs in4//! [wasi-keyvalue] and provide components with access to key-value storages.5//!6//! Currently supported storage backends:7//! * In-Memory (empty identifier)8//!9//! # Examples10//!11//! The usage of this crate is very similar to other WASI API implementations12//! such as [wasi:cli] and [wasi:http].13//!14//! A common scenario is accessing KV store in a [wasi:cli] component.15//! A standalone example of doing all this looks like:16//!17//! ```18//! use wasmtime::{19//! component::{Linker, ResourceTable},20//! Config, Engine, Result, Store,21//! };22//! use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};23//! use wasmtime_wasi_keyvalue::{WasiKeyValue, WasiKeyValueCtx, WasiKeyValueCtxBuilder};24//!25//! #[tokio::main]26//! async fn main() -> Result<()> {27//! let mut config = Config::new();28//! config.async_support(true);29//! let engine = Engine::new(&config)?;30//!31//! let mut store = Store::new(&engine, Ctx {32//! table: ResourceTable::new(),33//! wasi_ctx: WasiCtx::builder().build(),34//! wasi_keyvalue_ctx: WasiKeyValueCtxBuilder::new().build(),35//! });36//!37//! let mut linker = Linker::<Ctx>::new(&engine);38//! wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;39//! // add `wasi-keyvalue` world's interfaces to the linker40//! wasmtime_wasi_keyvalue::add_to_linker(&mut linker, |h: &mut Ctx| {41//! WasiKeyValue::new(&h.wasi_keyvalue_ctx, &mut h.table)42//! })?;43//!44//! // ... use `linker` to instantiate within `store` ...45//!46//! Ok(())47//! }48//!49//! struct Ctx {50//! table: ResourceTable,51//! wasi_ctx: WasiCtx,52//! wasi_keyvalue_ctx: WasiKeyValueCtx,53//! }54//!55//! impl WasiView for Ctx {56//! fn ctx(&mut self) -> WasiCtxView<'_> {57//! WasiCtxView { ctx: &mut self.wasi_ctx, table: &mut self.table }58//! }59//! }60//! ```61//!62//! [wasi-keyvalue]: https://github.com/WebAssembly/wasi-keyvalue63//! [wasi:cli]: https://docs.rs/wasmtime-wasi/latest64//! [wasi:http]: https://docs.rs/wasmtime-wasi-http/latest6566#![deny(missing_docs)]6768mod generated {69wasmtime::component::bindgen!({70path: "wit",71world: "wasi:keyvalue/imports",72imports: { default: trappable },73with: {74"wasi:keyvalue/store/bucket": crate::Bucket,75},76trappable_error_type: {77"wasi:keyvalue/store/error" => crate::Error,78},79});80}8182use self::generated::wasi::keyvalue;83use anyhow::Result;84use std::collections::HashMap;85use wasmtime::component::{HasData, Resource, ResourceTable, ResourceTableError};8687#[doc(hidden)]88pub enum Error {89NoSuchStore,90AccessDenied,91Other(String),92}9394impl From<ResourceTableError> for Error {95fn from(err: ResourceTableError) -> Self {96Self::Other(err.to_string())97}98}99100#[doc(hidden)]101pub struct Bucket {102in_memory_data: HashMap<String, Vec<u8>>,103}104105/// Builder-style structure used to create a [`WasiKeyValueCtx`].106#[derive(Default)]107pub struct WasiKeyValueCtxBuilder {108in_memory_data: HashMap<String, Vec<u8>>,109}110111impl WasiKeyValueCtxBuilder {112/// Creates a builder for a new context with default parameters set.113pub fn new() -> Self {114Default::default()115}116117/// Preset data for the In-Memory provider.118pub fn in_memory_data<I, K, V>(mut self, data: I) -> Self119where120I: IntoIterator<Item = (K, V)>,121K: Into<String>,122V: Into<Vec<u8>>,123{124self.in_memory_data = data125.into_iter()126.map(|(k, v)| (k.into(), v.into()))127.collect();128self129}130131/// Uses the configured context so far to construct the final [`WasiKeyValueCtx`].132pub fn build(self) -> WasiKeyValueCtx {133WasiKeyValueCtx {134in_memory_data: self.in_memory_data,135}136}137}138139/// Capture the state necessary for use in the `wasi-keyvalue` API implementation.140pub struct WasiKeyValueCtx {141in_memory_data: HashMap<String, Vec<u8>>,142}143144impl WasiKeyValueCtx {145/// Convenience function for calling [`WasiKeyValueCtxBuilder::new`].146pub fn builder() -> WasiKeyValueCtxBuilder {147WasiKeyValueCtxBuilder::new()148}149}150151/// A wrapper capturing the needed internal `wasi-keyvalue` state.152pub struct WasiKeyValue<'a> {153ctx: &'a WasiKeyValueCtx,154table: &'a mut ResourceTable,155}156157impl<'a> WasiKeyValue<'a> {158/// Create a new view into the `wasi-keyvalue` state.159pub fn new(ctx: &'a WasiKeyValueCtx, table: &'a mut ResourceTable) -> Self {160Self { ctx, table }161}162}163164impl keyvalue::store::Host for WasiKeyValue<'_> {165fn open(&mut self, identifier: String) -> Result<Resource<Bucket>, Error> {166match identifier.as_str() {167"" => Ok(self.table.push(Bucket {168in_memory_data: self.ctx.in_memory_data.clone(),169})?),170_ => Err(Error::NoSuchStore),171}172}173174fn convert_error(&mut self, err: Error) -> Result<keyvalue::store::Error> {175match err {176Error::NoSuchStore => Ok(keyvalue::store::Error::NoSuchStore),177Error::AccessDenied => Ok(keyvalue::store::Error::AccessDenied),178Error::Other(e) => Ok(keyvalue::store::Error::Other(e)),179}180}181}182183impl keyvalue::store::HostBucket for WasiKeyValue<'_> {184fn get(&mut self, bucket: Resource<Bucket>, key: String) -> Result<Option<Vec<u8>>, Error> {185let bucket = self.table.get_mut(&bucket)?;186Ok(bucket.in_memory_data.get(&key).cloned())187}188189fn set(&mut self, bucket: Resource<Bucket>, key: String, value: Vec<u8>) -> Result<(), Error> {190let bucket = self.table.get_mut(&bucket)?;191bucket.in_memory_data.insert(key, value);192Ok(())193}194195fn delete(&mut self, bucket: Resource<Bucket>, key: String) -> Result<(), Error> {196let bucket = self.table.get_mut(&bucket)?;197bucket.in_memory_data.remove(&key);198Ok(())199}200201fn exists(&mut self, bucket: Resource<Bucket>, key: String) -> Result<bool, Error> {202let bucket = self.table.get_mut(&bucket)?;203Ok(bucket.in_memory_data.contains_key(&key))204}205206fn list_keys(207&mut self,208bucket: Resource<Bucket>,209cursor: Option<u64>,210) -> Result<keyvalue::store::KeyResponse, Error> {211let bucket = self.table.get_mut(&bucket)?;212let keys: Vec<String> = bucket.in_memory_data.keys().cloned().collect();213let cursor = cursor.unwrap_or(0) as usize;214let keys_slice = &keys[cursor..];215Ok(keyvalue::store::KeyResponse {216keys: keys_slice.to_vec(),217cursor: None,218})219}220221fn drop(&mut self, bucket: Resource<Bucket>) -> Result<()> {222self.table.delete(bucket)?;223Ok(())224}225}226227impl keyvalue::atomics::Host for WasiKeyValue<'_> {228fn increment(229&mut self,230bucket: Resource<Bucket>,231key: String,232delta: u64,233) -> Result<u64, Error> {234let bucket = self.table.get_mut(&bucket)?;235let value = bucket236.in_memory_data237.entry(key.clone())238.or_insert("0".to_string().into_bytes());239let current_value = String::from_utf8(value.clone())240.map_err(|e| Error::Other(e.to_string()))?241.parse::<u64>()242.map_err(|e| Error::Other(e.to_string()))?;243let new_value = current_value + delta;244*value = new_value.to_string().into_bytes();245Ok(new_value)246}247}248249impl keyvalue::batch::Host for WasiKeyValue<'_> {250fn get_many(251&mut self,252bucket: Resource<Bucket>,253keys: Vec<String>,254) -> Result<Vec<Option<(String, Vec<u8>)>>, Error> {255let bucket = self.table.get_mut(&bucket)?;256Ok(keys257.into_iter()258.map(|key| {259bucket260.in_memory_data261.get(&key)262.map(|value| (key.clone(), value.clone()))263})264.collect())265}266267fn set_many(268&mut self,269bucket: Resource<Bucket>,270key_values: Vec<(String, Vec<u8>)>,271) -> Result<(), Error> {272let bucket = self.table.get_mut(&bucket)?;273for (key, value) in key_values {274bucket.in_memory_data.insert(key, value);275}276Ok(())277}278279fn delete_many(&mut self, bucket: Resource<Bucket>, keys: Vec<String>) -> Result<(), Error> {280let bucket = self.table.get_mut(&bucket)?;281for key in keys {282bucket.in_memory_data.remove(&key);283}284Ok(())285}286}287288/// Add all the `wasi-keyvalue` world's interfaces to a [`wasmtime::component::Linker`].289pub fn add_to_linker<T: Send + 'static>(290l: &mut wasmtime::component::Linker<T>,291f: fn(&mut T) -> WasiKeyValue<'_>,292) -> Result<()> {293keyvalue::store::add_to_linker::<_, HasWasiKeyValue>(l, f)?;294keyvalue::atomics::add_to_linker::<_, HasWasiKeyValue>(l, f)?;295keyvalue::batch::add_to_linker::<_, HasWasiKeyValue>(l, f)?;296Ok(())297}298299struct HasWasiKeyValue;300301impl HasData for HasWasiKeyValue {302type Data<'a> = WasiKeyValue<'a>;303}304305306