use crate::cli::Cli;
use crate::permissions::NetworkPermissions;
use anyhow::Result;
use std::path::Path;
use wasmtime::component::{Component, Linker as ComponentLinker, ResourceTable};
use wasmtime::{Config, Engine, Store};
use wasmtime_wasi::p2::{WasiCtx, WasiCtxBuilder};
use crate::api::host_api::HostApi;
use crate::api::plugin_api::PluginApi;
use crate::store::{PluginHost, WasiState};
pub struct WasmEngine {
engine: Engine,
}
impl WasmEngine {
pub fn new() -> Result<Self> {
let mut config = Config::new();
config.wasm_component_model(true);
config.async_support(true);
let engine = Engine::new(&config)?;
Ok(Self { engine })
}
#[allow(unused)]
pub fn engine(&self) -> &Engine {
&self.engine
}
pub async fn load_component(&self, source: &str) -> Result<Component> {
if source.starts_with("http://") || source.starts_with("https://") {
self.load_component_from_url(source).await
} else {
let path = Path::new(source);
self.load_component_from_file(path).await
}
}
pub async fn load_component_from_file(&self, path: &Path) -> Result<Component> {
let component = Component::from_file(&self.engine, path)?;
Ok(component)
}
pub async fn load_component_from_url(&self, url: &str) -> Result<Component> {
let client = reqwest::Client::new();
let response = client.get(url).send().await?;
if !response.status().is_success() {
return Err(anyhow::anyhow!(
"Failed to download component from {}: HTTP {}",
url,
response.status()
));
}
let bytes = response.bytes().await?;
let component = Component::from_binary(&self.engine, &bytes)?;
Ok(component)
}
#[allow(unused)]
pub fn load_component_from_bytes(&self, bytes: &[u8]) -> Result<Component> {
let component = Component::from_binary(&self.engine, bytes)?;
Ok(component)
}
pub fn build_wasi_ctx(cli: &Cli) -> Result<WasiCtx> {
let host_path = cli.dir.clone();
let guest_path = ".";
let (dir_perms, file_perms) = if cli.allow_all || cli.allow_read && cli.allow_write {
(
wasmtime_wasi::DirPerms::all(),
wasmtime_wasi::FilePerms::all(),
)
} else if cli.allow_read {
(
wasmtime_wasi::DirPerms::READ,
wasmtime_wasi::FilePerms::READ,
)
} else if cli.allow_write {
(
wasmtime_wasi::DirPerms::MUTATE,
wasmtime_wasi::FilePerms::WRITE,
)
} else {
(
wasmtime_wasi::DirPerms::empty(),
wasmtime_wasi::FilePerms::empty(),
)
};
let mut wasi_builder = WasiCtxBuilder::new();
wasi_builder.preopened_dir(host_path, guest_path, dir_perms, file_perms)?;
let wasi_ctx = wasi_builder.build();
Ok(wasi_ctx)
}
pub fn create_store(&self, wasi_ctx: WasiCtx, cli: &Cli) -> Store<WasiState> {
let repl_vars =
std::sync::Arc::new(std::sync::Mutex::new(std::collections::HashMap::new()));
Store::new(
&self.engine,
WasiState {
ctx: wasi_ctx,
table: ResourceTable::new(),
plugin_host: PluginHost {
network_permissions: NetworkPermissions::from(cli),
repl_vars: repl_vars.clone(),
},
repl_vars,
plugins_names: Vec::new(),
},
)
}
pub async fn instantiate_plugin(
&self,
store: &mut Store<WasiState>,
component: Component,
) -> Result<PluginApi> {
let mut linker: ComponentLinker<WasiState> = ComponentLinker::new(&self.engine);
wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
PluginApi::add_to_linker::<WasiState, wasmtime::component::HasSelf<PluginHost>>(
&mut linker,
|state: &mut WasiState| &mut state.plugin_host,
)?;
let plugin = PluginApi::instantiate_async(store, &component, &linker).await?;
Ok(plugin)
}
pub async fn instantiate_repl_logic(
&self,
store: &mut Store<WasiState>,
component: Component,
) -> Result<HostApi> {
let mut linker: ComponentLinker<WasiState> = ComponentLinker::new(&self.engine);
wasmtime_wasi::p2::add_to_linker_sync(&mut linker)?;
HostApi::add_to_linker::<WasiState, wasmtime::component::HasSelf<WasiState>>(
&mut linker,
|state: &mut WasiState| state,
)?;
let repl_logic = HostApi::instantiate_async(store, &component, &linker).await?;
Ok(repl_logic)
}
}