leankg 0.16.7

Lightweight Knowledge Graph for AI-Assisted Development
Documentation
use leankg::db::schema::init_db;
use leankg::graph::GraphEngine;
use std::fs;
use std::path::Path;

#[derive(Debug)]
pub struct DataStoreCheckResult {
    pub elements_checked: usize,
    pub elements_valid: usize,
    pub elements_invalid: Vec<String>,
    pub relationships_checked: usize,
    pub relationships_valid: usize,
    pub relationships_invalid: Vec<String>,
    pub duplicates_found: usize,
    pub duplicate_names: Vec<String>,
}

pub fn check_indexed_elements_exist(
    db_path: &Path,
    source_root: &Path,
) -> Result<DataStoreCheckResult, Box<dyn std::error::Error>> {
    let db = init_db(db_path)?;
    let graph_engine = GraphEngine::new(db);

    let elements = graph_engine.all_elements()?;
    let mut elements_valid = 0;
    let mut elements_invalid = Vec::new();

    for elem in &elements {
        let file_path = source_root.join(&elem.file_path);
        if file_path.exists() {
            let content = fs::read_to_string(&file_path)?;
            let lines: Vec<&str> = content.lines().collect();
            let line_count = lines.len() as u32;

            if elem.line_start >= 1
                && elem.line_end >= elem.line_start
                && elem.line_end <= line_count
            {
                elements_valid += 1;
            } else {
                elements_invalid.push(format!(
                    "{}: line {}-{} exceeds file {} ({} lines)",
                    elem.qualified_name, elem.line_start, elem.line_end, elem.file_path, line_count
                ));
            }
        } else {
            elements_invalid.push(format!(
                "{}: file {} does not exist",
                elem.qualified_name, elem.file_path
            ));
        }
    }

    Ok(DataStoreCheckResult {
        elements_checked: elements.len(),
        elements_valid,
        elements_invalid,
        relationships_checked: 0,
        relationships_valid: 0,
        relationships_invalid: Vec::new(),
        duplicates_found: 0,
        duplicate_names: Vec::new(),
    })
}

pub fn check_no_duplicate_elements(
    db_path: &Path,
) -> Result<DataStoreCheckResult, Box<dyn std::error::Error>> {
    let db = init_db(db_path)?;
    let graph_engine = GraphEngine::new(db);

    let elements = graph_engine.all_elements()?;
    let mut name_counts: std::collections::HashMap<String, usize> =
        std::collections::HashMap::new();

    for elem in &elements {
        *name_counts.entry(elem.qualified_name.clone()).or_insert(0) += 1;
    }

    let duplicate_names: Vec<String> = name_counts
        .iter()
        .filter(|(_, c)| **c > 1)
        .map(|(n, _)| n.clone())
        .collect();

    Ok(DataStoreCheckResult {
        elements_checked: elements.len(),
        elements_valid: elements.len(),
        elements_invalid: Vec::new(),
        relationships_checked: 0,
        relationships_valid: 0,
        relationships_invalid: Vec::new(),
        duplicates_found: duplicate_names.len(),
        duplicate_names,
    })
}

pub fn check_relationship_validity(
    db_path: &Path,
    source_root: &Path,
) -> Result<DataStoreCheckResult, Box<dyn std::error::Error>> {
    let db = init_db(db_path)?;
    let graph_engine = GraphEngine::new(db);

    let relationships = graph_engine.all_relationships()?;
    let mut relationships_valid = 0;
    let mut relationships_invalid = Vec::new();

    for rel in &relationships {
        if rel.rel_type == "calls" {
            let target_file = source_root.join(
                &rel.target_qualified
                    .split("::")
                    .next()
                    .unwrap_or(&rel.target_qualified),
            );
            if target_file.exists() {
                let content = fs::read_to_string(&target_file)?;
                let target_name = rel.target_qualified.split("::").last().unwrap_or("");

                if content.contains(&format!("{}", target_name)) {
                    relationships_valid += 1;
                } else {
                    relationships_invalid.push(format!(
                        "{} -> {} ({}): target function '{}' not found in source",
                        rel.source_qualified, rel.target_qualified, rel.rel_type, target_name
                    ));
                }
            }
        } else {
            relationships_valid += 1;
        }
    }

    Ok(DataStoreCheckResult {
        elements_checked: 0,
        elements_valid: 0,
        elements_invalid: Vec::new(),
        relationships_checked: relationships.len(),
        relationships_valid,
        relationships_invalid,
        duplicates_found: 0,
        duplicate_names: Vec::new(),
    })
}

pub fn generate_data_store_report(result: &DataStoreCheckResult) -> String {
    let mut report = String::from("# Data Store Correctness Report\n\n");

    if result.elements_checked > 0 {
        report.push_str("## Elements\n");
        report.push_str(&format!(
            "- Checked: {}\n- Valid: {}\n- Invalid: {}\n",
            result.elements_checked,
            result.elements_valid,
            result.elements_invalid.len()
        ));
        if !result.elements_invalid.is_empty() {
            report.push_str("### Invalid Elements\n");
            for err in &result.elements_invalid {
                report.push_str(&format!("- {}\n", err));
            }
        }
    }

    if result.relationships_checked > 0 {
        report.push_str("\n## Relationships\n");
        report.push_str(&format!(
            "- Checked: {}\n- Valid: {}\n- Invalid: {}\n",
            result.relationships_checked,
            result.relationships_valid,
            result.relationships_invalid.len()
        ));
        if !result.relationships_invalid.is_empty() {
            report.push_str("### Invalid Relationships\n");
            for err in &result.relationships_invalid {
                report.push_str(&format!("- {}\n", err));
            }
        }
    }

    if result.duplicates_found > 0 {
        report.push_str("\n## Duplicates\n");
        report.push_str(&format!(
            "- Found: {} duplicate qualified names\n",
            result.duplicates_found
        ));
        for name in &result.duplicate_names {
            report.push_str(&format!("- {}\n", name));
        }
    }

    report
}

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

    #[test]
    fn test_data_store_result_struct() {
        let result = DataStoreCheckResult {
            elements_checked: 10,
            elements_valid: 8,
            elements_invalid: vec!["elem1".to_string(), "elem2".to_string()],
            relationships_checked: 5,
            relationships_valid: 5,
            relationships_invalid: vec![],
            duplicates_found: 1,
            duplicate_names: vec!["dup::test".to_string()],
        };

        assert_eq!(result.elements_checked, 10);
        assert_eq!(result.elements_valid, 8);
        assert_eq!(result.duplicates_found, 1);
    }

    #[test]
    fn test_generate_report_format() {
        let result = DataStoreCheckResult {
            elements_checked: 100,
            elements_valid: 95,
            elements_invalid: vec!["test::elem".to_string()],
            relationships_checked: 50,
            relationships_valid: 48,
            relationships_invalid: vec!["bad::rel".to_string()],
            duplicates_found: 2,
            duplicate_names: vec!["dup1".to_string(), "dup2".to_string()],
        };

        let report = generate_data_store_report(&result);
        assert!(report.contains("# Data Store Correctness Report"));
        assert!(report.contains("Checked: 100"));
        assert!(report.contains("Invalid: 1"));
        assert!(report.contains("Found: 2"));
    }

    #[test]
    fn test_empty_result_report() {
        let result = DataStoreCheckResult {
            elements_checked: 0,
            elements_valid: 0,
            elements_invalid: vec![],
            relationships_checked: 0,
            relationships_valid: 0,
            relationships_invalid: vec![],
            duplicates_found: 0,
            duplicate_names: vec![],
        };

        let report = generate_data_store_report(&result);
        assert!(report.contains("# Data Store Correctness Report"));
        assert!(report.len() > 30);
    }
}