astmap-core 0.0.1

Core domain types and logic for astmap
Documentation
use std::collections::HashMap;
use std::path::{Path, PathBuf};

use crate::error::{DbError, DiffError, ScanError};
use crate::model::{
    Config, DepLevel, DepType, Dependency, File, ImpactEntry, ParseResult, ParseStatus, PathStep,
    Symbol, SymbolKind, SymbolResult, SymbolWithParent, VcsFileChange,
};

// ── Storage Ports ──

/// Shared config operations used by multiple modules.
pub trait ConfigStore {
    fn config_get(&self, key: &str) -> Result<Option<String>, DbError>;
    fn config_set(&self, key: &str, value: &str) -> Result<(), DbError>;
    fn config_list(&self) -> Result<Vec<Config>, DbError>;
}

/// Shared read-only symbol/file lookup operations.
pub trait SymbolLookup {
    fn get_file_id(&self, path: &str) -> Result<Option<i64>, DbError>;
    fn get_symbol_by_id(&self, symbol_id: i64) -> Result<Option<Symbol>, DbError>;
    fn search_symbols_by_name(&self, name: &str, limit: i64) -> Result<Vec<SymbolResult>, DbError>;
    fn get_symbols_for_file(&self, file_id: i64) -> Result<Vec<Symbol>, DbError>;
    fn get_children(&self, symbol_id: i64) -> Result<Vec<Symbol>, DbError>;
    fn impact_analysis(&self, symbol_id: i64, max_depth: i64) -> Result<Vec<ImpactEntry>, DbError>;
    fn stats(&self) -> Result<(i64, i64, i64), DbError>;
}

/// Storage operations needed by the scan pipeline.
#[allow(clippy::too_many_arguments)]
pub trait ScanStore: ConfigStore + SymbolLookup {
    fn begin_scan(&self) -> Result<i64, DbError>;
    fn complete_scan(
        &self,
        scan_id: i64,
        file_count: i64,
        symbol_count: i64,
        dep_count: i64,
    ) -> Result<(), DbError>;
    fn upsert_file(
        &self,
        scan_id: i64,
        path: &str,
        language: &str,
        size_bytes: i64,
        content_hash: &str,
        last_modified: u64,
        parse_status: ParseStatus,
    ) -> Result<i64, DbError>;
    fn get_file_hash(&self, path: &str) -> Result<Option<String>, DbError>;
    fn get_all_file_paths(&self) -> Result<HashMap<String, i64>, DbError>;
    fn delete_file(&self, file_id: i64) -> Result<(), DbError>;
    fn insert_symbol(
        &self,
        file_id: i64,
        parent_id: Option<i64>,
        name: &str,
        kind: SymbolKind,
        signature: Option<&str>,
        start_line: i64,
        end_line: i64,
        start_byte: i64,
        end_byte: i64,
    ) -> Result<i64, DbError>;
    fn delete_symbols_for_file(&self, file_id: i64) -> Result<(), DbError>;
    fn insert_dependency(
        &self,
        source_file_id: i64,
        source_symbol_id: Option<i64>,
        target_file_id: Option<i64>,
        target_symbol_id: Option<i64>,
        dep_type: DepType,
        level: DepLevel,
        raw_import: Option<&str>,
    ) -> Result<i64, DbError>;
    fn delete_deps_for_file(&self, file_id: i64) -> Result<(), DbError>;
}

/// Read-only operations for querying the index.
pub trait QueryStore: ConfigStore + SymbolLookup {
    fn search_symbols(&self, query: &str, limit: i64) -> Result<Vec<SymbolResult>, DbError>;
    fn find_path(
        &self,
        from_symbol_id: i64,
        to_symbol_id: i64,
        max_depth: i64,
    ) -> Result<Vec<PathStep>, DbError>;
}

/// Operations needed by the diff module (working.rs, since.rs, session.rs).
pub trait DiffStore: ConfigStore + SymbolLookup {
    fn symbols_in_line_range(
        &self,
        file_path: &str,
        start_line: i64,
        end_line: i64,
    ) -> Result<Vec<SymbolResult>, DbError>;
    fn all_symbols_for_file(&self, file_path: &str) -> Result<Vec<SymbolWithParent>, DbError>;
}

/// Operations needed by the export module.
pub trait ExportStore: SymbolLookup {
    fn list_files(&self, pattern: Option<&str>) -> Result<Vec<File>, DbError>;
    fn get_deps_for_file(&self, file_id: i64) -> Result<Vec<Dependency>, DbError>;
}

// ── VCS Port ──

/// Abstraction over VCS operations needed by the diff module.
pub trait VcsProvider {
    /// Get changed files between working tree and a base ref.
    fn working_tree_changes(&self, base_ref: Option<&str>)
        -> Result<Vec<VcsFileChange>, DiffError>;
    /// Get changed files between two refs (from_ref..to_ref).
    fn changes_between_refs(
        &self,
        from_ref: &str,
        to_ref: &str,
    ) -> Result<Vec<VcsFileChange>, DiffError>;
    /// Read file content at a specific ref. Returns None if file doesn't exist or is binary.
    fn read_file_at_ref(&self, path: &str, git_ref: &str) -> Result<Option<String>, DiffError>;
    /// Count commits between two refs.
    fn commit_count(&self, from_ref: &str, to_ref: &str) -> Result<usize, DiffError>;
    /// Check if a ref exists.
    fn resolve_ref(&self, refspec: &str) -> Result<bool, DiffError>;
    /// Get HEAD commit SHA.
    fn head_sha(&self) -> Result<String, DiffError>;
    /// Find the root commit SHA (first commit with no parents).
    fn root_commit_sha(&self) -> Result<Option<String>, DiffError>;
}

// ── File Discovery Port ──

/// Abstraction over file discovery (directory walking with ignore rules).
pub trait FileDiscovery: Send + Sync {
    fn discover_files(
        &self,
        project_dir: &Path,
        supported_ext: &[&str],
    ) -> Result<Vec<PathBuf>, ScanError>;
}

// ── Language Ports ──

/// Trait for language-specific parsing.
pub trait LanguageParser: Send + Sync {
    fn parse(&self, source: &str, file_path: &Path) -> ParseResult;
    fn language_name(&self) -> &str;
}

/// Trait for language-specific import/module resolution.
pub trait LanguageResolver: Send + Sync {
    fn resolve_import(
        &self,
        import_path: &str,
        source_file: &str,
        project_root: &Path,
    ) -> Option<PathBuf>;
}

/// Describes how to find sibling files that belong to the same logical module/package.
/// Used by import resolution to expand candidate search scope.
pub struct SiblingExpansion {
    /// Directory prefix to match (e.g., "pkg/" or "" for root-level).
    pub prefix: String,
    /// File extension to match (e.g., "go", "py"). Empty means any extension.
    pub extension: String,
    /// If true, only match files without directory separators (root-level only).
    pub root_only: bool,
}

/// Registry that provides language parsers and resolvers.
pub trait LanguageRegistry: Send + Sync {
    fn parser_for(&self, ext: &str) -> Option<&dyn LanguageParser>;
    fn resolver_for(&self, ext: &str, root: &Path) -> Option<Box<dyn LanguageResolver>>;
    fn supported_extensions(&self) -> &[&str];
    fn config_files(&self) -> &[&str];
    fn sibling_expansion(&self, rel_path: &str) -> Option<SiblingExpansion>;
}