astmap-core 0.0.1

Core domain types and logic for astmap
Documentation
use serde::{Deserialize, Serialize};
use strum::{Display, EnumString};

// ── Domain Enums ──

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Display, EnumString)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum SymbolKind {
    Class,
    Struct,
    Trait,
    Enum,
    Function,
    Method,
    Module,
    Interface,
    Const,
    TypeAlias,
    Impl,
    Variable,
    Component,
    Annotation,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Display, EnumString)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum ScanStatus {
    Running,
    Completed,
    Failed,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Display, EnumString)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum ParseStatus {
    Ok,
    Partial,
    Error,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Display, EnumString)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum DepType {
    Imports,
    Calls,
    Extends,
    Implements,
    TypeRef,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Display, EnumString)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum DepLevel {
    File,
    Symbol,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Display, EnumString)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum FileChangeStatus {
    Added,
    Modified,
    Deleted,
    Renamed,
}

// ── Domain Structs ──

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Scan {
    pub id: i64,
    pub started_at: u64,
    pub completed_at: Option<u64>,
    pub status: ScanStatus,
    pub file_count: i64,
    pub symbol_count: i64,
    pub dep_count: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct File {
    pub id: i64,
    pub scan_id: i64,
    pub path: String,
    pub language: String,
    pub size_bytes: i64,
    pub content_hash: String,
    pub last_modified: u64,
    pub parse_status: ParseStatus,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Symbol {
    pub id: i64,
    pub file_id: i64,
    pub parent_id: Option<i64>,
    pub name: String,
    pub kind: SymbolKind,
    pub signature: Option<String>,
    pub summary: Option<String>,
    pub start_line: i64,
    pub end_line: i64,
    pub start_byte: i64,
    pub end_byte: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Dependency {
    pub id: i64,
    pub source_file_id: i64,
    pub source_symbol_id: Option<i64>,
    pub target_file_id: Option<i64>,
    pub target_symbol_id: Option<i64>,
    pub dep_type: DepType,
    pub level: DepLevel,
    pub raw_import: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
    pub key: String,
    pub value: String,
    pub updated_at: u64,
}

/// Query result for symbol search
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SymbolResult {
    pub id: i64,
    pub name: String,
    pub kind: SymbolKind,
    pub file_path: String,
    pub start_line: i64,
    pub end_line: i64,
    pub signature: Option<String>,
    pub summary: Option<String>,
}

/// Symbol with resolved parent name (for symbol comparison across git refs)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SymbolWithParent {
    pub id: i64,
    pub name: String,
    pub kind: SymbolKind,
    pub file_path: String,
    pub start_line: i64,
    pub end_line: i64,
    pub signature: Option<String>,
    pub parent_name: Option<String>,
}

/// Impact analysis result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImpactEntry {
    pub symbol_id: i64,
    pub symbol_name: String,
    pub symbol_kind: SymbolKind,
    pub file_path: String,
    pub depth: i64,
    pub dep_type: DepType,
}

/// Dependency path entry for `why` command
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PathStep {
    pub symbol_id: i64,
    pub symbol_name: String,
    pub file_path: String,
    pub dep_type: DepType,
}

/// Inspect result: a symbol with its children.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InspectResult {
    pub symbol: Symbol,
    pub children: Vec<Symbol>,
}

/// Impact analysis result for a specific symbol.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImpactResult {
    pub symbol_id: i64,
    pub entries: Vec<ImpactEntry>,
}

/// Dependency path result for `why` command.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WhyResult {
    pub from_name: String,
    pub to_name: String,
    pub path: Vec<PathStep>,
}

/// A file changed in VCS (git diff output mapped to domain types).
#[derive(Debug, Clone)]
pub struct VcsFileChange {
    pub path: String,
    pub old_path: Option<String>,
    pub status: FileChangeStatus,
    pub changed_lines: Vec<(i64, i64)>,
}

// ── Extraction types (parser ↔ scanner contract) ──

/// Extracted symbol from AST
#[derive(Debug, Clone)]
pub struct ExtractedSymbol {
    pub name: String,
    pub kind: SymbolKind,
    pub signature: Option<String>,
    pub start_line: usize,
    pub end_line: usize,
    pub start_byte: usize,
    pub end_byte: usize,
    pub parent_index: Option<usize>, // index into the symbols vec
}

/// Extracted import statement
#[derive(Debug, Clone)]
pub struct ExtractedImport {
    pub path: String,
    pub imported_symbols: Vec<String>, // specific names imported (e.g., ["Repository", "DbError"])
}

/// A function/method call found in the source
#[derive(Debug, Clone)]
pub struct ExtractedCall {
    pub caller_name: String, // function/method containing the call
    pub callee_name: String, // function/method being called
    pub line: usize,
}

/// A type reference found in the source (parameter types, return types, field types)
#[derive(Debug, Clone)]
pub struct ExtractedTypeRef {
    pub from_symbol: String, // symbol containing the reference
    pub to_type: String,     // referenced type name
    pub line: usize,
}

/// Result of parsing a single file
#[derive(Debug)]
pub struct ParseResult {
    pub symbols: Vec<ExtractedSymbol>,
    pub imports: Vec<ExtractedImport>,
    pub calls: Vec<ExtractedCall>,
    pub type_refs: Vec<ExtractedTypeRef>,
    pub has_errors: bool,
}

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

    #[test]
    fn test_symbol_kind_roundtrip() {
        let variants = [
            SymbolKind::Class,
            SymbolKind::Struct,
            SymbolKind::Trait,
            SymbolKind::Enum,
            SymbolKind::Function,
            SymbolKind::Method,
            SymbolKind::Module,
            SymbolKind::Interface,
            SymbolKind::Const,
            SymbolKind::TypeAlias,
            SymbolKind::Impl,
            SymbolKind::Variable,
            SymbolKind::Component,
            SymbolKind::Annotation,
        ];
        for v in variants {
            let s = v.to_string();
            let parsed: SymbolKind = s.parse().unwrap();
            assert_eq!(v, parsed, "roundtrip failed for {:?}", v);
        }
    }

    #[test]
    fn test_symbol_kind_unknown() {
        let result = "bogus".parse::<SymbolKind>();
        assert!(result.is_err());
    }

    #[test]
    fn test_scan_status_roundtrip() {
        for v in [
            ScanStatus::Running,
            ScanStatus::Completed,
            ScanStatus::Failed,
        ] {
            let parsed: ScanStatus = v.to_string().parse().unwrap();
            assert_eq!(v, parsed);
        }
    }

    #[test]
    fn test_scan_status_unknown() {
        assert!("bogus".parse::<ScanStatus>().is_err());
    }

    #[test]
    fn test_parse_status_roundtrip() {
        for v in [ParseStatus::Ok, ParseStatus::Partial, ParseStatus::Error] {
            let parsed: ParseStatus = v.to_string().parse().unwrap();
            assert_eq!(v, parsed);
        }
    }

    #[test]
    fn test_parse_status_unknown() {
        assert!("bogus".parse::<ParseStatus>().is_err());
    }

    #[test]
    fn test_dep_type_roundtrip() {
        for v in [
            DepType::Imports,
            DepType::Calls,
            DepType::Extends,
            DepType::Implements,
            DepType::TypeRef,
        ] {
            let parsed: DepType = v.to_string().parse().unwrap();
            assert_eq!(v, parsed);
        }
    }

    #[test]
    fn test_dep_type_unknown() {
        assert!("bogus".parse::<DepType>().is_err());
    }

    #[test]
    fn test_dep_level_roundtrip() {
        for v in [DepLevel::File, DepLevel::Symbol] {
            let parsed: DepLevel = v.to_string().parse().unwrap();
            assert_eq!(v, parsed);
        }
    }

    #[test]
    fn test_dep_level_unknown() {
        assert!("bogus".parse::<DepLevel>().is_err());
    }

    #[test]
    fn test_file_change_status_roundtrip() {
        for v in [
            FileChangeStatus::Added,
            FileChangeStatus::Modified,
            FileChangeStatus::Deleted,
            FileChangeStatus::Renamed,
        ] {
            let parsed: FileChangeStatus = v.to_string().parse().unwrap();
            assert_eq!(v, parsed);
        }
    }

    #[test]
    fn test_file_change_status_unknown() {
        assert!("bogus".parse::<FileChangeStatus>().is_err());
    }
}