1+ use dashmap:: DashMap ;
2+ use serde:: { Deserialize , Serialize } ;
3+ use std:: collections:: HashMap ;
4+ use std:: fs;
5+ use std:: path:: Path ;
6+ use std:: sync:: Mutex ;
7+
8+ /// Trait for caching analysis results.
9+ /// Key is typically a file path or module identifier.
10+ /// Value is the serialized analysis result.
11+ pub trait Cache < K , V > : Send + Sync
12+ where
13+ K : Eq + std:: hash:: Hash + Clone + Send + Sync ,
14+ V : Clone + Send + Sync ,
15+ {
16+ fn get ( & self , key : & K ) -> Option < V > ;
17+ fn set ( & self , key : K , value : V ) ;
18+ fn clear ( & self ) ;
19+ }
20+
21+ /// In-memory cache using DashMap for concurrent access.
22+ pub struct InMemoryCache < K , V > {
23+ map : DashMap < K , V > ,
24+ }
25+
26+ impl < K , V > InMemoryCache < K , V >
27+ where
28+ K : Eq + std:: hash:: Hash + Clone + Send + Sync ,
29+ V : Clone + Send + Sync ,
30+ {
31+ pub fn new ( ) -> Self {
32+ Self {
33+ map : DashMap :: new ( ) ,
34+ }
35+ }
36+ }
37+
38+ impl < K , V > Default for InMemoryCache < K , V >
39+ where
40+ K : Eq + std:: hash:: Hash + Clone + Send + Sync ,
41+ V : Clone + Send + Sync ,
42+ {
43+ fn default ( ) -> Self {
44+ Self :: new ( )
45+ }
46+ }
47+ }
48+
49+ impl < K , V > Default for InMemoryCache < K , V >
50+ where
51+ K : Eq + std:: hash:: Hash + Clone + Send + Sync ,
52+ V : Clone + Send + Sync ,
53+ {
54+ fn default ( ) -> Self {
55+ Self :: new ( )
56+ }
57+ }
58+ Self {
59+ map : DashMap :: new ( ) ,
60+ }
61+ }
62+ }
63+
64+ impl < K , V > Cache < K , V > for InMemoryCache < K , V >
65+ where
66+ K : Eq + std:: hash:: Hash + Clone + Send + Sync ,
67+ V : Clone + Send + Sync ,
68+ {
69+ fn get ( & self , key : & K ) -> Option < V > {
70+ self . map . get ( key) . map ( |v| v. clone ( ) )
71+ }
72+
73+ fn set ( & self , key : K , value : V ) {
74+ self . map . insert ( key, value) ;
75+ }
76+
77+ fn clear ( & self ) {
78+ self . map . clear ( ) ;
79+ }
80+ }
81+
82+ /// File-based cache that stores data in a directory.
83+ /// Uses JSON serialization. Stores key-value pairs in files.
84+ #[ derive( Serialize , Deserialize ) ]
85+ struct CacheEntry < K , V > {
86+ key : K ,
87+ value : V ,
88+ }
89+
90+ pub struct FileCache < K , V > {
91+ dir : String ,
92+ cache : Mutex < HashMap < K , V > > ,
93+ }
94+
95+ impl < K , V > FileCache < K , V >
96+ where
97+ K : Eq + std:: hash:: Hash + Clone + Serialize + for < ' de > Deserialize < ' de > + Send + Sync ,
98+ V : Clone + Serialize + for < ' de > Deserialize < ' de > + Send + Sync ,
99+ {
100+ pub fn new ( dir : & str ) -> Self {
101+ let mut cache = HashMap :: new ( ) ;
102+ if Path :: new ( dir) . exists ( ) {
103+ // Load existing cache from files
104+ if let Ok ( entries) = fs:: read_dir ( dir) {
105+ for entry in entries. flatten ( ) {
106+ let path = entry. path ( ) ;
107+ if path. extension ( ) == Some ( std:: ffi:: OsStr :: new ( "json" ) ) {
108+ if let Ok ( content) = fs:: read_to_string ( & path) {
109+ if let Ok ( entry) = serde_json:: from_str :: < CacheEntry < K , V > > ( & content) {
110+ cache. insert ( entry. key , entry. value ) ;
111+ }
112+ }
113+ }
114+ }
115+ }
116+ } else {
117+ fs:: create_dir_all ( dir) . ok ( ) ;
118+ }
119+ Self {
120+ dir : dir. to_string ( ) ,
121+ cache : Mutex :: new ( cache) ,
122+ }
123+ }
124+
125+ fn key_to_filename ( & self , key : & K ) -> String {
126+ // Simple hash for filename
127+ use std:: collections:: hash_map:: DefaultHasher ;
128+ use std:: hash:: Hasher ;
129+ let mut hasher = DefaultHasher :: new ( ) ;
130+ key. hash ( & mut hasher) ;
131+ format ! ( "{:x}.json" , hasher. finish( ) )
132+ }
133+ }
134+
135+ impl < K , V > Cache < K , V > for FileCache < K , V >
136+ where
137+ K : Eq + std:: hash:: Hash + Clone + Serialize + for < ' de > Deserialize < ' de > + Send + Sync ,
138+ V : Clone + Serialize + for < ' de > Deserialize < ' de > + Send + Sync ,
139+ {
140+ fn get ( & self , key : & K ) -> Option < V > {
141+ let cache = self . cache . lock ( ) . unwrap ( ) ;
142+ cache. get ( key) . cloned ( )
143+ }
144+
145+ fn set ( & self , key : K , value : V ) {
146+ let mut cache = self . cache . lock ( ) . unwrap ( ) ;
147+ cache. insert ( key. clone ( ) , value. clone ( ) ) ;
148+ // Persist to file
149+ let filename = self . key_to_filename ( & key) ;
150+ let file_path = Path :: new ( & self . dir ) . join ( filename) ;
151+ let entry = CacheEntry { key, value } ;
152+ if let Ok ( json) = serde_json:: to_string ( & entry) {
153+ fs:: write ( file_path, json) . ok ( ) ;
154+ }
155+ }
156+
157+ fn clear ( & self ) {
158+ let mut cache = self . cache . lock ( ) . unwrap ( ) ;
159+ cache. clear ( ) ;
160+ fs:: remove_dir_all ( & self . dir ) . ok ( ) ;
161+ fs:: create_dir_all ( & self . dir ) . ok ( ) ;
162+ }
163+ }
164+
165+ /// Example analysis result structure.
166+ /// This can be customized based on what agents analyze.
167+ #[ derive( Debug , Clone , PartialEq , Serialize , Deserialize ) ]
168+ pub struct AnalysisResult {
169+ pub file_path : String ,
170+ pub issues : Vec < String > ,
171+ pub metrics : HashMap < String , f64 > ,
172+ }
173+
174+ #[ cfg( test) ]
175+ mod tests {
176+ use super :: * ;
177+
178+ #[ test]
179+ fn test_in_memory_cache ( ) {
180+ let cache = InMemoryCache :: new ( ) ;
181+ let key = "test.rs" ;
182+ let value = AnalysisResult {
183+ file_path : key. to_string ( ) ,
184+ issues : vec ! [ "TODO" . to_string( ) ] ,
185+ metrics : [ ( "loc" . to_string ( ) , 100.0 ) ] . into ( ) ,
186+ } ;
187+ cache. set ( key. to_string ( ) , value. clone ( ) ) ;
188+ assert_eq ! ( cache. get( & key. to_string( ) ) , Some ( value) ) ;
189+ cache. clear ( ) ;
190+ assert_eq ! ( cache. get( & key. to_string( ) ) , None ) ;
191+ }
192+
193+ #[ test]
194+ fn test_file_cache ( ) {
195+ let temp_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
196+ let cache_dir = temp_dir. path ( ) . to_string_lossy ( ) . to_string ( ) ;
197+ let cache = FileCache :: new ( & cache_dir) ;
198+ let key = "test.rs" . to_string ( ) ;
199+ let value = AnalysisResult {
200+ file_path : key. clone ( ) ,
201+ issues : vec ! [ "FIXME" . to_string( ) ] ,
202+ metrics : [ ( "complexity" . to_string ( ) , 5.0 ) ] . into ( ) ,
203+ } ;
204+ cache. set ( key. clone ( ) , value. clone ( ) ) ;
205+ // Simulate new instance to test persistence
206+ let cache2 = FileCache :: new ( & cache_dir) ;
207+ assert_eq ! ( cache2. get( & key) , Some ( value) ) ;
208+ }
209+ }
0 commit comments