acp/
query.rs

1//! @acp:module "Query"
2//! @acp:summary "Programmatic cache access and querying (schema-compliant)"
3//! @acp:domain cli
4//! @acp:layer service
5//!
6//! Provides type-safe queries similar to jq but in Rust.
7
8use crate::cache::{Cache, DomainEntry, FileEntry, SymbolEntry};
9
10/// Query builder for cache
11pub struct Query<'a> {
12    cache: &'a Cache,
13}
14
15impl<'a> Query<'a> {
16    pub fn new(cache: &'a Cache) -> Self {
17        Self { cache }
18    }
19
20    /// Get symbol by name
21    pub fn symbol(&self, name: &str) -> Option<&SymbolEntry> {
22        self.cache.get_symbol(name)
23    }
24
25    /// Get file by path with cross-platform path normalization
26    ///
27    /// Accepts various path formats:
28    /// - `src/file.ts` or `./src/file.ts`
29    /// - Windows paths: `src\file.ts`
30    /// - Paths with `..`: `src/../src/file.ts`
31    pub fn file(&self, path: &str) -> Option<&FileEntry> {
32        self.cache.get_file(path)
33    }
34
35    /// Get callers of a symbol
36    pub fn callers(&self, symbol: &str) -> Vec<&str> {
37        self.cache
38            .get_callers(symbol)
39            .map(|v| v.iter().map(|s| s.as_str()).collect())
40            .unwrap_or_default()
41    }
42
43    /// Get callees of a symbol
44    pub fn callees(&self, symbol: &str) -> Vec<&str> {
45        self.cache
46            .get_callees(symbol)
47            .map(|v| v.iter().map(|s| s.as_str()).collect())
48            .unwrap_or_default()
49    }
50
51    /// Get domain by name
52    pub fn domain(&self, name: &str) -> Option<&DomainEntry> {
53        self.cache.domains.get(name)
54    }
55
56    /// Get all domains
57    pub fn domains(&self) -> impl Iterator<Item = &DomainEntry> {
58        self.cache.domains.values()
59    }
60
61    /// Get files by domain
62    pub fn files_in_domain(&self, domain: &str) -> Vec<&str> {
63        self.cache
64            .get_domain_files(domain)
65            .map(|v| v.iter().map(|s| s.as_str()).collect())
66            .unwrap_or_default()
67    }
68
69    /// Get files by layer (from file entries)
70    pub fn files_in_layer(&self, layer: &str) -> Vec<&str> {
71        self.cache
72            .files
73            .values()
74            .filter(|f| f.layer.as_deref() == Some(layer))
75            .map(|f| f.path.as_str())
76            .collect()
77    }
78
79    /// Search symbols by name pattern
80    pub fn search_symbols(&self, pattern: &str) -> Vec<&SymbolEntry> {
81        let p = pattern.to_lowercase();
82        self.cache
83            .symbols
84            .values()
85            .filter(|s| s.name.to_lowercase().contains(&p))
86            .collect()
87    }
88
89    /// Get hotpath symbols (symbols with many callers)
90    pub fn hotpaths(&self) -> impl Iterator<Item = &str> {
91        // Compute hotpaths from call graph
92        self.cache
93            .graph
94            .as_ref()
95            .map(|g| {
96                let mut callee_counts: Vec<(&String, usize)> =
97                    g.reverse.iter().map(|(k, v)| (k, v.len())).collect();
98                callee_counts.sort_by(|a, b| b.1.cmp(&a.1));
99                callee_counts
100                    .into_iter()
101                    .take(10)
102                    .map(|(k, _)| k.as_str())
103                    .collect::<Vec<_>>()
104            })
105            .unwrap_or_default()
106            .into_iter()
107    }
108}