toolpath-cli 0.3.0

CLI for deriving, querying, and visualizing Toolpath provenance
use anyhow::{Context, Result};
use std::path::PathBuf;
use toolpath::v1::Document;

pub fn run(input: PathBuf) -> Result<()> {
    let content =
        std::fs::read_to_string(&input).with_context(|| format!("Failed to read {:?}", input))?;
    validate_content(&content)
}

fn validate_content(content: &str) -> Result<()> {
    match Document::from_json(content) {
        Ok(doc) => {
            let kind = match &doc {
                Document::Graph(g) => format!("Graph (id: {})", g.graph.id),
                Document::Path(p) => format!("Path (id: {}, {} steps)", p.path.id, p.steps.len()),
                Document::Step(s) => format!("Step (id: {})", s.step.id),
            };
            println!("Valid: {}", kind);
            Ok(())
        }
        Err(e) => Err(anyhow::anyhow!("Invalid: {}", e)),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::Write;

    #[test]
    fn test_validate_valid_step() {
        let json = r#"{"Step":{"step":{"id":"s1","actor":"human:alex","timestamp":"2026-01-01T00:00:00Z"},"change":{}}}"#;
        assert!(validate_content(json).is_ok());
    }

    #[test]
    fn test_validate_valid_path() {
        let json = r#"{"Path":{"path":{"id":"p1","head":"s1"},"steps":[]}}"#;
        assert!(validate_content(json).is_ok());
    }

    #[test]
    fn test_validate_valid_graph() {
        let json = r#"{"Graph":{"graph":{"id":"g1"},"paths":[]}}"#;
        assert!(validate_content(json).is_ok());
    }

    #[test]
    fn test_validate_invalid_json() {
        assert!(validate_content("not json").is_err());
    }

    #[test]
    fn test_validate_invalid_structure() {
        assert!(validate_content(r#"{"Unknown":{}}"#).is_err());
    }

    #[test]
    fn test_run_with_temp_file() {
        let mut f = tempfile::NamedTempFile::new().unwrap();
        write!(f, r#"{{"Step":{{"step":{{"id":"s1","actor":"human:alex","timestamp":"2026-01-01T00:00:00Z"}},"change":{{}}}}}}"#).unwrap();
        f.flush().unwrap();
        assert!(run(f.path().to_path_buf()).is_ok());
    }

    #[test]
    fn test_run_nonexistent_file() {
        assert!(run(PathBuf::from("/nonexistent/file.json")).is_err());
    }
}