Skip to main content

the_code_graph_domain/use_cases/
community.rs

1use crate::analysis::community::detect_communities;
2use crate::error::Result;
3use crate::model::{Community, CommunityAnalysis, CommunityConfig};
4use crate::ports::GraphStore;
5
6pub struct CommunityUseCase<S> {
7    store: S,
8}
9
10impl<S: GraphStore> CommunityUseCase<S> {
11    pub fn new(store: S) -> Self {
12        Self { store }
13    }
14
15    pub fn analyze(&self, config: &CommunityConfig) -> Result<CommunityAnalysis> {
16        let symbols = self.store.all_symbols()?;
17        let edges = self.store.all_edges()?;
18        Ok(detect_communities(&symbols, &edges, config))
19    }
20
21    pub fn community_of(
22        &self,
23        symbol: &str,
24        config: &CommunityConfig,
25    ) -> Result<Option<Community>> {
26        let analysis = self.analyze(config)?;
27        Ok(analysis
28            .communities
29            .into_iter()
30            .find(|c| c.members.contains(&symbol.to_string())))
31    }
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37    use crate::model::*;
38    use crate::test_support::InMemoryGraphStore;
39
40    fn build_test_store() -> InMemoryGraphStore {
41        let mut store = InMemoryGraphStore::new();
42        for i in 0..3 {
43            store.insert_symbol(SymbolNode {
44                name: format!("a{i}"),
45                qualified_name: format!("src/auth.rs::a{i}"),
46                kind: SymbolKind::Function,
47                location: Location {
48                    file: "src/auth.rs".into(),
49                    line_start: i * 10 + 1,
50                    line_end: i * 10 + 10,
51                    col_start: 0,
52                    col_end: 0,
53                },
54                visibility: Visibility::Public,
55                is_exported: true,
56                is_async: false,
57                is_test: false,
58                decorators: vec![],
59                signature: None,
60            });
61        }
62        for i in 0..3 {
63            for j in (i + 1)..3 {
64                store.insert_edge(Edge {
65                    kind: EdgeKind::Calls,
66                    source: format!("src/auth.rs::a{i}"),
67                    target: format!("src/auth.rs::a{j}"),
68                    metadata: None,
69                });
70            }
71        }
72        store
73    }
74
75    #[test]
76    fn analyze_returns_communities() {
77        let store = build_test_store();
78        let uc = CommunityUseCase::new(store);
79        let result = uc.analyze(&CommunityConfig::default()).unwrap();
80        assert!(result.stats.count > 0 || result.stats.isolated_nodes > 0);
81    }
82
83    #[test]
84    fn community_of_finds_symbol() {
85        let store = build_test_store();
86        let uc = CommunityUseCase::new(store);
87        let c = uc
88            .community_of("src/auth.rs::a0", &CommunityConfig::default())
89            .unwrap();
90        assert!(c.is_some());
91        assert!(c.unwrap().members.contains(&"src/auth.rs::a0".to_string()));
92    }
93
94    #[test]
95    fn community_of_returns_none_for_unknown() {
96        let store = build_test_store();
97        let uc = CommunityUseCase::new(store);
98        let c = uc
99            .community_of("nonexistent::symbol", &CommunityConfig::default())
100            .unwrap();
101        assert!(c.is_none());
102    }
103}