git_lore/lore/
validation.rs1use std::path::Path;
2use std::process::Command;
3
4use anyhow::{Context, Result};
5use serde::{Deserialize, Serialize};
6
7use super::{AtomState, LoreAtom};
8
9#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
10pub struct ValidationIssue {
11 pub atom_id: String,
12 pub command: String,
13 pub reason: String,
14}
15
16pub fn scan_atoms(workspace_root: &Path, atoms: &[LoreAtom]) -> Vec<ValidationIssue> {
17 let mut issues = Vec::new();
18
19 for atom in atoms.iter().filter(|atom| atom.state != AtomState::Deprecated) {
20 let Some(script) = atom.validation_script.as_deref().map(str::trim) else {
21 continue;
22 };
23
24 if script.is_empty() {
25 continue;
26 }
27
28 match run_script(workspace_root, atom, script) {
29 Ok(()) => {}
30 Err(reason) => issues.push(ValidationIssue {
31 atom_id: atom.id.clone(),
32 command: script.to_string(),
33 reason: reason.to_string(),
34 }),
35 }
36 }
37
38 issues
39}
40
41fn run_script(workspace_root: &Path, atom: &LoreAtom, script: &str) -> Result<()> {
42 let output = Command::new("/bin/sh")
43 .arg("-lc")
44 .arg(script)
45 .current_dir(workspace_root)
46 .env("GIT_LORE_ATOM_ID", &atom.id)
47 .env("GIT_LORE_ATOM_KIND", format!("{:?}", atom.kind))
48 .env("GIT_LORE_ATOM_STATE", format!("{:?}", atom.state))
49 .env("GIT_LORE_ATOM_TITLE", &atom.title)
50 .env("GIT_LORE_ATOM_SCOPE", atom.scope.as_deref().unwrap_or(""))
51 .env(
52 "GIT_LORE_ATOM_PATH",
53 atom.path.as_ref().map(|path| path.display().to_string()).unwrap_or_default(),
54 )
55 .output()
56 .with_context(|| format!("failed to run validation script for atom {}", atom.id))?;
57
58 if output.status.success() {
59 return Ok(());
60 }
61
62 let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
63 let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
64 let details = if stderr.is_empty() && stdout.is_empty() {
65 format!("validation script exited with status {}", output.status)
66 } else if stderr.is_empty() {
67 format!("validation script failed: {stdout}")
68 } else if stdout.is_empty() {
69 format!("validation script failed: {stderr}")
70 } else {
71 format!("validation script failed: {stderr}; {stdout}")
72 };
73
74 Err(anyhow::anyhow!(details))
75}