path-cli 0.5.0

CLI for deriving, querying, and visualizing Toolpath provenance (binary: path)
Documentation
use anyhow::Result;
use std::path::PathBuf;
use toolpath::v1::Document;

use crate::io::read_document_auto;

pub fn run(input: PathBuf) -> Result<()> {
    match read_document_auto(&input) {
        Ok(doc) => {
            println!("Valid: {}", describe(&doc));
            Ok(())
        }
        Err(e) => Err(anyhow::anyhow!("Invalid: {}", e)),
    }
}

fn describe(doc: &Document) -> String {
    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),
    }
}

#[cfg(test)]
fn validate_content(content: &str) -> Result<()> {
    match Document::from_json(content) {
        Ok(doc) => {
            println!("Valid: {}", describe(&doc));
            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());
    }
}