1use std::path::Path;
2use std::process::Command;
3
4use serde::Serialize;
5use serde::de::DeserializeOwned;
6use thiserror::Error;
7
8#[derive(Debug, Error)]
9pub enum LeanError {
10 #[error("lean tool '{tool}' failed: {message}")]
11 ToolFailed { tool: String, message: String },
12 #[error("lean tool '{tool}' not found at {path}")]
13 NotFound { tool: String, path: String },
14 #[error("io error: {0}")]
15 Io(#[from] std::io::Error),
16 #[error("JSON error: {0}")]
17 Json(#[from] serde_json::Error),
18}
19
20pub fn invoke<I: Serialize, O: DeserializeOwned>(
23 tool: &str,
24 input: &I,
25 spec_dir: &Path,
26) -> Result<O, LeanError> {
27 let bin_path = spec_dir.join(".lake/build/bin/genesis");
28 if !bin_path.exists() {
29 return Err(LeanError::NotFound {
30 tool: tool.to_string(),
31 path: bin_path.display().to_string(),
32 });
33 }
34
35 let subcommand = tool
39 .strip_prefix("genesis_")
40 .unwrap_or(tool)
41 .replace('_', "-");
42
43 let input_json = serde_json::to_string(input)?;
44 let output = Command::new(&bin_path)
45 .arg(&subcommand)
46 .stdin(std::process::Stdio::piped())
47 .stdout(std::process::Stdio::piped())
48 .stderr(std::process::Stdio::piped())
49 .spawn()
50 .and_then(|mut child| {
51 use std::io::Write;
52 if let Some(ref mut stdin) = child.stdin {
53 stdin.write_all(input_json.as_bytes())?;
54 }
55 child.wait_with_output()
56 })?;
57
58 if !output.status.success() {
59 let stderr = String::from_utf8_lossy(&output.stderr);
60 return Err(LeanError::ToolFailed {
61 tool: tool.to_string(),
62 message: stderr.trim().to_string(),
63 });
64 }
65
66 let stdout = String::from_utf8_lossy(&output.stdout);
67 Ok(serde_json::from_str(stdout.trim())?)
68}