Skip to main content

git_lore/lore/
validation.rs

1use 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}