use anyhow::{Context, Result};
use wasmtime::{component::Component, component::Linker, Store};
use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView};
use crate::Image;
pub trait HostStateImpl: WasiView + Send + 'static {
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
}
pub struct HostState {
wasi: WasiCtx,
table: ResourceTable,
}
impl HostState {
pub fn new() -> Result<Self> {
let wasi = WasiCtxBuilder::new()
.inherit_stdio()
.inherit_network()
.build();
let table = ResourceTable::new();
Ok(Self { wasi, table })
}
pub fn with_wasi<F>(f: F) -> Result<Self>
where
F: FnOnce(&mut WasiCtxBuilder) -> &mut WasiCtxBuilder,
{
let mut builder = WasiCtxBuilder::new();
f(&mut builder);
let wasi = builder.build();
let table = ResourceTable::new();
Ok(Self { wasi, table })
}
}
impl Default for HostState {
fn default() -> Self {
Self::new().expect("Failed to create default HostState")
}
}
impl WasiView for HostState {
fn ctx(&mut self) -> WasiCtxView<'_> {
WasiCtxView {
ctx: &mut self.wasi,
table: &mut self.table,
}
}
}
impl HostStateImpl for HostState {
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
pub struct GuestInstance {
inner: Box<dyn std::any::Any + Send + Sync>,
}
impl GuestInstance {
pub fn new<T: 'static + Send + Sync>(instance: T) -> Self {
Self {
inner: Box::new(instance),
}
}
pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
self.inner.downcast_ref::<T>()
}
pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.inner.downcast_mut::<T>()
}
}
pub struct GuestHandlerContext<'a, T: HostStateImpl> {
pub linker: &'a mut Linker<T>,
pub store: &'a mut Store<T>,
pub component: &'a Component,
}
impl<'a, T: HostStateImpl> GuestHandlerContext<'a, T> {
pub fn new(
linker: &'a mut Linker<T>,
store: &'a mut Store<T>,
component: &'a Component,
) -> Self {
Self {
linker,
store,
component,
}
}
}
pub struct ContainerBuilder<T: HostStateImpl> {
image: Image,
host_state: T,
#[allow(clippy::type_complexity)]
guest_initializer: Option<
Box<
dyn for<'a> FnOnce(GuestHandlerContext<'a, T>) -> Result<GuestInstance, anyhow::Error>
+ Send,
>,
>,
}
impl<T: HostStateImpl> ContainerBuilder<T>
where
T: Default,
{
pub fn new(image: Image) -> Self {
Self {
image,
host_state: T::default(),
guest_initializer: None,
}
}
}
impl<T: HostStateImpl> ContainerBuilder<T> {
pub fn with_host_state(mut self, state: T) -> Self {
self.host_state = state;
self
}
pub fn with_guest_initializer<F>(mut self, f: F) -> Self
where
F: for<'a> FnOnce(GuestHandlerContext<'a, T>) -> Result<GuestInstance, anyhow::Error>
+ Send
+ 'static,
{
self.guest_initializer = Some(Box::new(f));
self
}
pub fn build(self) -> Result<Container<T>> {
let mut store = Store::new(self.image.engine(), self.host_state);
let mut linker = Linker::new(self.image.engine());
wasmtime_wasi::p2::add_to_linker_sync(&mut linker)
.context("Failed to add WASI to linker")?;
let guest_instance = if let Some(initializer) = self.guest_initializer {
let ctx = GuestHandlerContext::new(&mut linker, &mut store, self.image.component());
initializer(ctx)?
} else {
return Err(anyhow::anyhow!(
"Guest initializer is required. Use with_guest_initializer() to set it."
));
};
Ok(Container {
store,
guest: guest_instance,
})
}
}
pub struct Container<T: HostStateImpl = HostState> {
store: Store<T>,
guest: GuestInstance,
}
impl Container {
pub fn builder(image: Image) -> ContainerBuilder<HostState> {
ContainerBuilder::new(image)
}
}
impl<T: HostStateImpl> Container<T> {
pub fn store_mut(&mut self) -> &mut Store<T> {
&mut self.store
}
pub fn store(&self) -> &Store<T> {
&self.store
}
pub fn guest(&self) -> &GuestInstance {
&self.guest
}
pub fn guest_mut(&mut self) -> &mut GuestInstance {
&mut self.guest
}
pub fn host_state_mut(&mut self) -> &mut T {
self.store.data_mut()
}
pub fn host_state(&self) -> &T {
self.store.data()
}
}
impl<T: HostStateImpl> std::fmt::Debug for Container<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Container").finish()
}
}