1use std::path::{Path, PathBuf};
12use std::sync::Arc;
13
14use wasmtime::component::{Component, HasSelf, Linker, ResourceTable};
15use wasmtime::{Engine, Store};
16
17use harness::registry::VerifyFn;
18use llm_lib::attempt::VerifyReport;
19
20use crate::engine::new_engine;
21use crate::host_state::HostState;
22use crate::wasi_ctx::read_only_repo_ctx;
23
24mod bindings {
25 wasmtime::component::bindgen!({
26 path: "wit",
27 world: "verify-plugin",
28 });
29}
30
31impl From<bindings::exports::n7n::fix_deps::verify::VerifyReport> for VerifyReport {
35 fn from(r: bindings::exports::n7n::fix_deps::verify::VerifyReport) -> Self {
36 VerifyReport {
37 ok: r.ok,
38 output: r.output,
39 infra_error: r.infra_error,
40 }
41 }
42}
43
44impl bindings::n7n::fix_deps::host_fs::Host for HostState {
45 fn read_file(&mut self, path: String) -> Result<String, String> {
46 std::fs::read_to_string(self.repo_root.join(path)).map_err(|e| e.to_string())
47 }
48}
49
50pub struct WasmVerify {
54 engine: Engine,
55 component: Component,
56 linker: Linker<HostState>,
57 repo_root: PathBuf,
58}
59
60impl WasmVerify {
61 pub fn from_file(
64 repo_root: impl Into<PathBuf>,
65 wasm_path: impl AsRef<Path>,
66 ) -> wasmtime::Result<Self> {
67 let engine = new_engine()?;
68 let component = Component::from_file(&engine, wasm_path.as_ref())?;
69 let mut linker = Linker::<HostState>::new(&engine);
70 wasmtime_wasi::p2::add_to_linker_sync(&mut linker)?;
71 bindings::n7n::fix_deps::host_fs::add_to_linker::<_, HasSelf<HostState>>(
72 &mut linker,
73 |s| s,
74 )?;
75 Ok(Self {
76 engine,
77 component,
78 linker,
79 repo_root: repo_root.into(),
80 })
81 }
82
83 fn call(&self) -> wasmtime::Result<VerifyReport> {
84 let wasi = read_only_repo_ctx(&self.repo_root)?;
85 let mut store = Store::new(
86 &self.engine,
87 HostState {
88 wasi,
89 table: ResourceTable::new(),
90 repo_root: self.repo_root.clone(),
91 },
92 );
93 let instance =
94 bindings::VerifyPlugin::instantiate(&mut store, &self.component, &self.linker)?;
95 let report = instance.n7n_fix_deps_verify().call_run(&mut store)?;
96 Ok(VerifyReport::from(report))
97 }
98
99 #[must_use]
108 pub fn into_verify_fn(self) -> VerifyFn {
109 let this = Arc::new(self);
110 Arc::new(move || {
111 let this = Arc::clone(&this);
112 Box::pin(async move {
113 this.call().unwrap_or_else(|err| VerifyReport {
114 ok: false,
115 output: format!("wasm-компонент verify: {err}"),
116 infra_error: true,
117 })
118 })
119 })
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
128
129 #[test]
130 fn bindgen_verify_report_converts_to_llm_lib_verify_report_field_by_field() {
131 let wit_report = bindings::exports::n7n::fix_deps::verify::VerifyReport {
132 ok: false,
133 output: "діагностика".to_string(),
134 infra_error: true,
135 };
136
137 let report = VerifyReport::from(wit_report);
138
139 assert!(!report.ok);
140 assert_eq!(report.output, "діагностика");
141 assert!(report.infra_error);
142 }
143}