use std::path::{Path, PathBuf};
use std::sync::Arc;
use wasmtime::component::{Component, HasSelf, Linker, ResourceTable};
use wasmtime::{Engine, Store};
use harness::pipeline::{DetectFn, Violation};
use crate::engine::new_engine;
use crate::host_state::HostState;
use crate::wasi_ctx::read_only_repo_ctx;
mod bindings {
wasmtime::component::bindgen!({
path: "wit",
world: "detector-plugin",
});
}
impl From<bindings::exports::n7n::fix_deps::detector::Violation> for Violation {
fn from(v: bindings::exports::n7n::fix_deps::detector::Violation) -> Self {
Violation {
file: PathBuf::from(v.file),
line: v.line.map(|l| l as usize),
message: v.message,
}
}
}
impl bindings::n7n::fix_deps::host_fs::Host for HostState {
fn read_file(&mut self, path: String) -> Result<String, String> {
std::fs::read_to_string(self.repo_root.join(path)).map_err(|e| e.to_string())
}
}
pub struct WasmDetector {
engine: Engine,
component: Component,
linker: Linker<HostState>,
repo_root: PathBuf,
}
impl WasmDetector {
pub fn from_file(
repo_root: impl Into<PathBuf>,
wasm_path: impl AsRef<Path>,
) -> wasmtime::Result<Self> {
let engine = new_engine()?;
let component = Component::from_file(&engine, wasm_path.as_ref())?;
let mut linker = Linker::<HostState>::new(&engine);
wasmtime_wasi::p2::add_to_linker_sync(&mut linker)?;
bindings::n7n::fix_deps::host_fs::add_to_linker::<_, HasSelf<HostState>>(
&mut linker,
|s| s,
)?;
Ok(Self {
engine,
component,
linker,
repo_root: repo_root.into(),
})
}
fn call(&self) -> wasmtime::Result<Result<Vec<Violation>, String>> {
let wasi = read_only_repo_ctx(&self.repo_root)?;
let mut store = Store::new(
&self.engine,
HostState {
wasi,
table: ResourceTable::new(),
repo_root: self.repo_root.clone(),
},
);
let instance =
bindings::DetectorPlugin::instantiate(&mut store, &self.component, &self.linker)?;
let result = instance.n7n_fix_deps_detector().call_detect(&mut store)?;
Ok(result.map(|violations| violations.into_iter().map(Violation::from).collect()))
}
#[must_use]
pub fn into_detect_fn(self) -> DetectFn {
let this = Arc::new(self);
Arc::new(move || {
let this = Arc::clone(&this);
Box::pin(async move {
this.call()
.unwrap_or_else(|err| Err(format!("wasm-компонент detector: {err}")))
})
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bindgen_violation_converts_to_harness_violation_field_by_field() {
let wit_violation = bindings::exports::n7n::fix_deps::detector::Violation {
file: "src/a.rs".to_string(),
line: Some(12),
message: "порушення".to_string(),
};
let violation = Violation::from(wit_violation);
assert_eq!(violation.file, PathBuf::from("src/a.rs"));
assert_eq!(violation.line, Some(12));
assert_eq!(violation.message, "порушення");
}
#[test]
fn bindgen_violation_without_a_line_converts_to_none() {
let wit_violation = bindings::exports::n7n::fix_deps::detector::Violation {
file: "src/b.rs".to_string(),
line: None,
message: "без рядка".to_string(),
};
assert_eq!(Violation::from(wit_violation).line, None);
}
}