Path: blob/main/crates/wasi-keyvalue/src/lib.rs
3079 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//! 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 engine = Engine::default();28//!29//! let mut store = Store::new(&engine, Ctx {30//! table: ResourceTable::new(),31//! wasi_ctx: WasiCtx::builder().build(),32//! wasi_keyvalue_ctx: WasiKeyValueCtxBuilder::new().build(),33//! });34//!35//! let mut linker = Linker::<Ctx>::new(&engine);36//! wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;37//! // add `wasi-keyvalue` world's interfaces to the linker38//! wasmtime_wasi_keyvalue::add_to_linker(&mut linker, |h: &mut Ctx| {39//! WasiKeyValue::new(&h.wasi_keyvalue_ctx, &mut h.table)40//! })?;41//!42//! // ... use `linker` to instantiate within `store` ...43//!44//! Ok(())45//! }46//!47//! struct Ctx {48//! table: ResourceTable,49//! wasi_ctx: WasiCtx,50//! wasi_keyvalue_ctx: WasiKeyValueCtx,51//! }52//!53//! impl WasiView for Ctx {54//! fn ctx(&mut self) -> WasiCtxView<'_> {55//! WasiCtxView { ctx: &mut self.wasi_ctx, table: &mut self.table }56//! }57//! }58//! ```59//!60//! [wasi-keyvalue]: https://github.com/WebAssembly/wasi-keyvalue61//! [wasi:cli]: https://docs.rs/wasmtime-wasi/latest62//! [wasi:http]: https://docs.rs/wasmtime-wasi-http/latest6364#![deny(missing_docs)]6566mod generated {67wasmtime::component::bindgen!({68path: "wit",69world: "wasi:keyvalue/imports",70imports: { default: trappable },71with: {72"wasi:keyvalue/store.bucket": crate::Bucket,73},74trappable_error_type: {75"wasi:keyvalue/store.error" => crate::Error,76},77});78}7980use self::generated::wasi::keyvalue;81use std::collections::HashMap;82use wasmtime::Result;83use wasmtime::component::{HasData, Resource, ResourceTable, ResourceTableError};8485#[doc(hidden)]86pub enum Error {87NoSuchStore,88AccessDenied,89Other(String),90}9192impl From<ResourceTableError> for Error {93fn from(err: ResourceTableError) -> Self {94Self::Other(err.to_string())95}96}9798#[doc(hidden)]99pub struct Bucket {100in_memory_data: HashMap<String, Vec<u8>>,101}102103/// Builder-style structure used to create a [`WasiKeyValueCtx`].104#[derive(Default)]105pub struct WasiKeyValueCtxBuilder {106in_memory_data: HashMap<String, Vec<u8>>,107}108109impl WasiKeyValueCtxBuilder {110/// Creates a builder for a new context with default parameters set.111pub fn new() -> Self {112Default::default()113}114115/// Preset data for the In-Memory provider.116pub fn in_memory_data<I, K, V>(mut self, data: I) -> Self117where118I: IntoIterator<Item = (K, V)>,119K: Into<String>,120V: Into<Vec<u8>>,121{122self.in_memory_data = data123.into_iter()124.map(|(k, v)| (k.into(), v.into()))125.collect();126self127}128129/// Uses the configured context so far to construct the final [`WasiKeyValueCtx`].130pub fn build(self) -> WasiKeyValueCtx {131WasiKeyValueCtx {132in_memory_data: self.in_memory_data,133}134}135}136137/// Capture the state necessary for use in the `wasi-keyvalue` API implementation.138pub struct WasiKeyValueCtx {139in_memory_data: HashMap<String, Vec<u8>>,140}141142impl WasiKeyValueCtx {143/// Convenience function for calling [`WasiKeyValueCtxBuilder::new`].144pub fn builder() -> WasiKeyValueCtxBuilder {145WasiKeyValueCtxBuilder::new()146}147}148149/// A wrapper capturing the needed internal `wasi-keyvalue` state.150pub struct WasiKeyValue<'a> {151ctx: &'a WasiKeyValueCtx,152table: &'a mut ResourceTable,153}154155impl<'a> WasiKeyValue<'a> {156/// Create a new view into the `wasi-keyvalue` state.157pub fn new(ctx: &'a WasiKeyValueCtx, table: &'a mut ResourceTable) -> Self {158Self { ctx, table }159}160}161162impl keyvalue::store::Host for WasiKeyValue<'_> {163fn open(&mut self, identifier: String) -> Result<Resource<Bucket>, Error> {164match identifier.as_str() {165"" => Ok(self.table.push(Bucket {166in_memory_data: self.ctx.in_memory_data.clone(),167})?),168_ => Err(Error::NoSuchStore),169}170}171172fn convert_error(&mut self, err: Error) -> Result<keyvalue::store::Error> {173match err {174Error::NoSuchStore => Ok(keyvalue::store::Error::NoSuchStore),175Error::AccessDenied => Ok(keyvalue::store::Error::AccessDenied),176Error::Other(e) => Ok(keyvalue::store::Error::Other(e)),177}178}179}180181impl keyvalue::store::HostBucket for WasiKeyValue<'_> {182fn get(&mut self, bucket: Resource<Bucket>, key: String) -> Result<Option<Vec<u8>>, Error> {183let bucket = self.table.get_mut(&bucket)?;184Ok(bucket.in_memory_data.get(&key).cloned())185}186187fn set(&mut self, bucket: Resource<Bucket>, key: String, value: Vec<u8>) -> Result<(), Error> {188let bucket = self.table.get_mut(&bucket)?;189bucket.in_memory_data.insert(key, value);190Ok(())191}192193fn delete(&mut self, bucket: Resource<Bucket>, key: String) -> Result<(), Error> {194let bucket = self.table.get_mut(&bucket)?;195bucket.in_memory_data.remove(&key);196Ok(())197}198199fn exists(&mut self, bucket: Resource<Bucket>, key: String) -> Result<bool, Error> {200let bucket = self.table.get_mut(&bucket)?;201Ok(bucket.in_memory_data.contains_key(&key))202}203204fn list_keys(205&mut self,206bucket: Resource<Bucket>,207cursor: Option<u64>,208) -> Result<keyvalue::store::KeyResponse, Error> {209let bucket = self.table.get_mut(&bucket)?;210let keys: Vec<String> = bucket.in_memory_data.keys().cloned().collect();211let cursor = cursor.unwrap_or(0) as usize;212let keys_slice = &keys[cursor..];213Ok(keyvalue::store::KeyResponse {214keys: keys_slice.to_vec(),215cursor: None,216})217}218219fn drop(&mut self, bucket: Resource<Bucket>) -> Result<()> {220self.table.delete(bucket)?;221Ok(())222}223}224225impl keyvalue::atomics::Host for WasiKeyValue<'_> {226fn increment(227&mut self,228bucket: Resource<Bucket>,229key: String,230delta: u64,231) -> Result<u64, Error> {232let bucket = self.table.get_mut(&bucket)?;233let value = bucket234.in_memory_data235.entry(key.clone())236.or_insert("0".to_string().into_bytes());237let current_value = String::from_utf8(value.clone())238.map_err(|e| Error::Other(e.to_string()))?239.parse::<u64>()240.map_err(|e| Error::Other(e.to_string()))?;241let new_value = current_value + delta;242*value = new_value.to_string().into_bytes();243Ok(new_value)244}245}246247impl keyvalue::batch::Host for WasiKeyValue<'_> {248fn get_many(249&mut self,250bucket: Resource<Bucket>,251keys: Vec<String>,252) -> Result<Vec<Option<(String, Vec<u8>)>>, Error> {253let bucket = self.table.get_mut(&bucket)?;254Ok(keys255.into_iter()256.map(|key| {257bucket258.in_memory_data259.get(&key)260.map(|value| (key.clone(), value.clone()))261})262.collect())263}264265fn set_many(266&mut self,267bucket: Resource<Bucket>,268key_values: Vec<(String, Vec<u8>)>,269) -> Result<(), Error> {270let bucket = self.table.get_mut(&bucket)?;271for (key, value) in key_values {272bucket.in_memory_data.insert(key, value);273}274Ok(())275}276277fn delete_many(&mut self, bucket: Resource<Bucket>, keys: Vec<String>) -> Result<(), Error> {278let bucket = self.table.get_mut(&bucket)?;279for key in keys {280bucket.in_memory_data.remove(&key);281}282Ok(())283}284}285286/// Add all the `wasi-keyvalue` world's interfaces to a [`wasmtime::component::Linker`].287pub fn add_to_linker<T: Send + 'static>(288l: &mut wasmtime::component::Linker<T>,289f: fn(&mut T) -> WasiKeyValue<'_>,290) -> Result<()> {291keyvalue::store::add_to_linker::<_, HasWasiKeyValue>(l, f)?;292keyvalue::atomics::add_to_linker::<_, HasWasiKeyValue>(l, f)?;293keyvalue::batch::add_to_linker::<_, HasWasiKeyValue>(l, f)?;294Ok(())295}296297struct HasWasiKeyValue;298299impl HasData for HasWasiKeyValue {300type Data<'a> = WasiKeyValue<'a>;301}302303304