Skip to main content

astmap_core/
model.rs

1use serde::{Deserialize, Serialize};
2use strum::{Display, EnumString};
3
4// ── Domain Enums ──
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Display, EnumString)]
7#[serde(rename_all = "snake_case")]
8#[strum(serialize_all = "snake_case")]
9pub enum SymbolKind {
10    Class,
11    Struct,
12    Trait,
13    Enum,
14    Function,
15    Method,
16    Module,
17    Interface,
18    Const,
19    TypeAlias,
20    Impl,
21    Variable,
22    Component,
23    Annotation,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Display, EnumString)]
27#[serde(rename_all = "snake_case")]
28#[strum(serialize_all = "snake_case")]
29pub enum ScanStatus {
30    Running,
31    Completed,
32    Failed,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Display, EnumString)]
36#[serde(rename_all = "snake_case")]
37#[strum(serialize_all = "snake_case")]
38pub enum ParseStatus {
39    Ok,
40    Partial,
41    Error,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Display, EnumString)]
45#[serde(rename_all = "snake_case")]
46#[strum(serialize_all = "snake_case")]
47pub enum DepType {
48    Imports,
49    Calls,
50    Extends,
51    Implements,
52    TypeRef,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Display, EnumString)]
56#[serde(rename_all = "snake_case")]
57#[strum(serialize_all = "snake_case")]
58pub enum DepLevel {
59    File,
60    Symbol,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Display, EnumString)]
64#[serde(rename_all = "snake_case")]
65#[strum(serialize_all = "snake_case")]
66pub enum FileChangeStatus {
67    Added,
68    Modified,
69    Deleted,
70    Renamed,
71}
72
73// ── Domain Structs ──
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct Scan {
77    pub id: i64,
78    pub started_at: u64,
79    pub completed_at: Option<u64>,
80    pub status: ScanStatus,
81    pub file_count: i64,
82    pub symbol_count: i64,
83    pub dep_count: i64,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct File {
88    pub id: i64,
89    pub scan_id: i64,
90    pub path: String,
91    pub language: String,
92    pub size_bytes: i64,
93    pub content_hash: String,
94    pub last_modified: u64,
95    pub parse_status: ParseStatus,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct Symbol {
100    pub id: i64,
101    pub file_id: i64,
102    pub parent_id: Option<i64>,
103    pub name: String,
104    pub kind: SymbolKind,
105    pub signature: Option<String>,
106    pub summary: Option<String>,
107    pub start_line: i64,
108    pub end_line: i64,
109    pub start_byte: i64,
110    pub end_byte: i64,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct Dependency {
115    pub id: i64,
116    pub source_file_id: i64,
117    pub source_symbol_id: Option<i64>,
118    pub target_file_id: Option<i64>,
119    pub target_symbol_id: Option<i64>,
120    pub dep_type: DepType,
121    pub level: DepLevel,
122    pub raw_import: Option<String>,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct Config {
127    pub key: String,
128    pub value: String,
129    pub updated_at: u64,
130}
131
132/// Query result for symbol search
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct SymbolResult {
135    pub id: i64,
136    pub name: String,
137    pub kind: SymbolKind,
138    pub file_path: String,
139    pub start_line: i64,
140    pub end_line: i64,
141    pub signature: Option<String>,
142    pub summary: Option<String>,
143}
144
145/// Symbol with resolved parent name (for symbol comparison across git refs)
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct SymbolWithParent {
148    pub id: i64,
149    pub name: String,
150    pub kind: SymbolKind,
151    pub file_path: String,
152    pub start_line: i64,
153    pub end_line: i64,
154    pub signature: Option<String>,
155    pub parent_name: Option<String>,
156}
157
158/// Impact analysis result
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct ImpactEntry {
161    pub symbol_id: i64,
162    pub symbol_name: String,
163    pub symbol_kind: SymbolKind,
164    pub file_path: String,
165    pub depth: i64,
166    pub dep_type: DepType,
167}
168
169/// Dependency path entry for `why` command
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct PathStep {
172    pub symbol_id: i64,
173    pub symbol_name: String,
174    pub file_path: String,
175    pub dep_type: DepType,
176}
177
178/// Inspect result: a symbol with its children.
179#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct InspectResult {
181    pub symbol: Symbol,
182    pub children: Vec<Symbol>,
183}
184
185/// Impact analysis result for a specific symbol.
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct ImpactResult {
188    pub symbol_id: i64,
189    pub entries: Vec<ImpactEntry>,
190}
191
192/// Dependency path result for `why` command.
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct WhyResult {
195    pub from_name: String,
196    pub to_name: String,
197    pub path: Vec<PathStep>,
198}
199
200/// A file changed in VCS (git diff output mapped to domain types).
201#[derive(Debug, Clone)]
202pub struct VcsFileChange {
203    pub path: String,
204    pub old_path: Option<String>,
205    pub status: FileChangeStatus,
206    pub changed_lines: Vec<(i64, i64)>,
207}
208
209// ── Extraction types (parser ↔ scanner contract) ──
210
211/// Extracted symbol from AST
212#[derive(Debug, Clone)]
213pub struct ExtractedSymbol {
214    pub name: String,
215    pub kind: SymbolKind,
216    pub signature: Option<String>,
217    pub start_line: usize,
218    pub end_line: usize,
219    pub start_byte: usize,
220    pub end_byte: usize,
221    pub parent_index: Option<usize>, // index into the symbols vec
222}
223
224/// Extracted import statement
225#[derive(Debug, Clone)]
226pub struct ExtractedImport {
227    pub path: String,
228    pub imported_symbols: Vec<String>, // specific names imported (e.g., ["Repository", "DbError"])
229}
230
231/// A function/method call found in the source
232#[derive(Debug, Clone)]
233pub struct ExtractedCall {
234    pub caller_name: String, // function/method containing the call
235    pub callee_name: String, // function/method being called
236    pub line: usize,
237}
238
239/// A type reference found in the source (parameter types, return types, field types)
240#[derive(Debug, Clone)]
241pub struct ExtractedTypeRef {
242    pub from_symbol: String, // symbol containing the reference
243    pub to_type: String,     // referenced type name
244    pub line: usize,
245}
246
247/// Result of parsing a single file
248#[derive(Debug)]
249pub struct ParseResult {
250    pub symbols: Vec<ExtractedSymbol>,
251    pub imports: Vec<ExtractedImport>,
252    pub calls: Vec<ExtractedCall>,
253    pub type_refs: Vec<ExtractedTypeRef>,
254    pub has_errors: bool,
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260
261    #[test]
262    fn test_symbol_kind_roundtrip() {
263        let variants = [
264            SymbolKind::Class,
265            SymbolKind::Struct,
266            SymbolKind::Trait,
267            SymbolKind::Enum,
268            SymbolKind::Function,
269            SymbolKind::Method,
270            SymbolKind::Module,
271            SymbolKind::Interface,
272            SymbolKind::Const,
273            SymbolKind::TypeAlias,
274            SymbolKind::Impl,
275            SymbolKind::Variable,
276            SymbolKind::Component,
277            SymbolKind::Annotation,
278        ];
279        for v in variants {
280            let s = v.to_string();
281            let parsed: SymbolKind = s.parse().unwrap();
282            assert_eq!(v, parsed, "roundtrip failed for {:?}", v);
283        }
284    }
285
286    #[test]
287    fn test_symbol_kind_unknown() {
288        let result = "bogus".parse::<SymbolKind>();
289        assert!(result.is_err());
290    }
291
292    #[test]
293    fn test_scan_status_roundtrip() {
294        for v in [
295            ScanStatus::Running,
296            ScanStatus::Completed,
297            ScanStatus::Failed,
298        ] {
299            let parsed: ScanStatus = v.to_string().parse().unwrap();
300            assert_eq!(v, parsed);
301        }
302    }
303
304    #[test]
305    fn test_scan_status_unknown() {
306        assert!("bogus".parse::<ScanStatus>().is_err());
307    }
308
309    #[test]
310    fn test_parse_status_roundtrip() {
311        for v in [ParseStatus::Ok, ParseStatus::Partial, ParseStatus::Error] {
312            let parsed: ParseStatus = v.to_string().parse().unwrap();
313            assert_eq!(v, parsed);
314        }
315    }
316
317    #[test]
318    fn test_parse_status_unknown() {
319        assert!("bogus".parse::<ParseStatus>().is_err());
320    }
321
322    #[test]
323    fn test_dep_type_roundtrip() {
324        for v in [
325            DepType::Imports,
326            DepType::Calls,
327            DepType::Extends,
328            DepType::Implements,
329            DepType::TypeRef,
330        ] {
331            let parsed: DepType = v.to_string().parse().unwrap();
332            assert_eq!(v, parsed);
333        }
334    }
335
336    #[test]
337    fn test_dep_type_unknown() {
338        assert!("bogus".parse::<DepType>().is_err());
339    }
340
341    #[test]
342    fn test_dep_level_roundtrip() {
343        for v in [DepLevel::File, DepLevel::Symbol] {
344            let parsed: DepLevel = v.to_string().parse().unwrap();
345            assert_eq!(v, parsed);
346        }
347    }
348
349    #[test]
350    fn test_dep_level_unknown() {
351        assert!("bogus".parse::<DepLevel>().is_err());
352    }
353
354    #[test]
355    fn test_file_change_status_roundtrip() {
356        for v in [
357            FileChangeStatus::Added,
358            FileChangeStatus::Modified,
359            FileChangeStatus::Deleted,
360            FileChangeStatus::Renamed,
361        ] {
362            let parsed: FileChangeStatus = v.to_string().parse().unwrap();
363            assert_eq!(v, parsed);
364        }
365    }
366
367    #[test]
368    fn test_file_change_status_unknown() {
369        assert!("bogus".parse::<FileChangeStatus>().is_err());
370    }
371}