use anyhow::Result;
use wasmtime::component::{Component, Linker};
use wasmtime::Store;
use super::mother_child::PluginEngine;
use super::{wasm_engine, GrantedCapabilities, PluginManifest};
pub type QueryDispatchFn = Box<dyn FnMut(&str, &str) -> Result<String, String> + Send>;
mod command_bindings {
pub struct CommandHostState {
pub plugin_name: String,
pub wasi: wasmtime_wasi::WasiCtx,
pub wasi_table: wasmtime::component::ResourceTable,
pub project_root: Option<std::path::PathBuf>,
pub grants: super::GrantedCapabilities,
pub query_fn: Option<super::QueryDispatchFn>,
}
impl wasmtime_wasi::WasiView for CommandHostState {
fn ctx(&mut self) -> wasmtime_wasi::WasiCtxView<'_> {
wasmtime_wasi::WasiCtxView {
ctx: &mut self.wasi,
table: &mut self.wasi_table,
}
}
}
wasmtime::component::bindgen!({
path: "wit/command/",
world: "command",
});
impl patina::host::log::Host for CommandHostState {
fn log(&mut self, level: patina::host::log::LogLevel, message: String) {
let level_str = match level {
patina::host::log::LogLevel::Debug => "DEBUG",
patina::host::log::LogLevel::Info => "INFO",
patina::host::log::LogLevel::Warn => "WARN",
patina::host::log::LogLevel::Error => "ERROR",
};
super::super::host_support::log(&self.plugin_name, level_str, &message);
}
}
impl patina::host::layer::Host for CommandHostState {
fn find_project_root(&mut self) -> Option<String> {
super::super::host_support::find_project_root(&self.project_root)
}
fn read_config(&mut self) -> Result<String, String> {
super::super::host_support::read_config(&self.project_root)
}
fn detect_environment(&mut self) -> Result<String, String> {
super::super::host_support::detect_environment()
}
fn get_stored_tools(&mut self) -> Vec<String> {
super::super::host_support::get_stored_tools(&self.project_root)
}
fn count_layer_files(&mut self, subdir: String) -> u32 {
super::super::host_support::count_layer_files(&self.project_root, &subdir)
}
fn get_project_uid(&mut self) -> Option<String> {
super::super::host_support::get_project_uid(&self.project_root)
}
fn check_adapter_version(
&mut self,
adapter_name: String,
) -> Result<Option<String>, String> {
super::super::host_support::check_adapter_version(&self.project_root, &adapter_name)
}
}
impl patina::host::query::Host for CommandHostState {
fn query(&mut self, kind: String, params: String) -> Result<String, String> {
super::super::host_support::query(
&self.plugin_name,
&self.grants,
&mut self.query_fn,
&kind,
¶ms,
)
}
}
}
pub struct CommandEngine {
linker: Linker<command_bindings::CommandHostState>,
}
impl CommandEngine {
pub fn new() -> Result<Self> {
let mut linker = Linker::new(wasm_engine());
wasmtime_wasi::p2::add_to_linker_sync(&mut linker)?;
command_bindings::Command::add_to_linker::<
command_bindings::CommandHostState,
wasmtime::component::HasSelf<command_bindings::CommandHostState>,
>(&mut linker, |s| s)?;
Ok(Self { linker })
}
pub fn load_component(&self, wasm: &[u8]) -> Result<Component> {
Component::new(wasm_engine(), wasm)
}
pub fn run_command(
&self,
component: &Component,
manifest: &PluginManifest,
args: &[String],
query_fn: Option<QueryDispatchFn>,
) -> Result<i32> {
PluginEngine::check_capabilities(manifest)?;
let wasi = wasmtime_wasi::WasiCtxBuilder::new()
.inherit_stdout()
.inherit_stderr()
.build();
let project_root = crate::session::SessionManager::find_project_root().ok();
let grants = manifest.granted_capabilities();
let host_state = command_bindings::CommandHostState {
plugin_name: manifest.name.clone(),
wasi,
wasi_table: wasmtime::component::ResourceTable::new(),
project_root,
grants,
query_fn,
};
let mut store = Store::new(wasm_engine(), host_state);
let instance = command_bindings::Command::instantiate(&mut store, component, &self.linker)?;
instance.call_init(&mut store)?;
instance.call_run(&mut store, args)
}
pub fn get_command_name(&self, component: &Component) -> Result<String> {
let host_state = Self::probe_host_state();
let mut store = Store::new(wasm_engine(), host_state);
let instance = command_bindings::Command::instantiate(&mut store, component, &self.linker)?;
instance.call_init(&mut store)?;
instance.call_name(&mut store)
}
pub fn get_command_description(&self, component: &Component) -> Result<String> {
let host_state = Self::probe_host_state();
let mut store = Store::new(wasm_engine(), host_state);
let instance = command_bindings::Command::instantiate(&mut store, component, &self.linker)?;
instance.call_init(&mut store)?;
instance.call_description(&mut store)
}
fn probe_host_state() -> command_bindings::CommandHostState {
let wasi = wasmtime_wasi::WasiCtxBuilder::new().build();
let project_root = crate::session::SessionManager::find_project_root().ok();
command_bindings::CommandHostState {
plugin_name: "probe".to_string(),
wasi,
wasi_table: wasmtime::component::ResourceTable::new(),
project_root,
grants: GrantedCapabilities::default(),
query_fn: None,
}
}
}