ricecoder_storage/industry/
adapter.rs

1//! Industry file adapter trait and implementations
2//!
3//! This module defines the interface for reading and converting configuration files
4//! from other AI coding tools (Cursor, Claude, Windsurf, etc.) into RiceCoder's
5//! internal configuration format.
6
7use crate::config::Config;
8use crate::error::StorageResult;
9use std::path::Path;
10
11/// Trait for adapting industry-standard configuration files to RiceCoder format
12pub trait IndustryFileAdapter: Send + Sync {
13    /// Get the name of this adapter (e.g., "cursor", "claude", "windsurf")
14    fn name(&self) -> &'static str;
15
16    /// Check if this adapter can handle files in the given directory
17    fn can_handle(&self, project_root: &Path) -> bool;
18
19    /// Read and convert industry-standard config to RiceCoder Config
20    fn read_config(&self, project_root: &Path) -> StorageResult<Config>;
21
22    /// Get the priority of this adapter (higher = higher priority)
23    /// Used when multiple adapters can handle the same directory
24    fn priority(&self) -> u32 {
25        0
26    }
27}
28
29/// File detection result
30#[derive(Debug, Clone)]
31pub struct FileDetectionResult {
32    /// Name of the adapter that can handle this file
33    pub adapter_name: String,
34    /// Priority of the adapter
35    pub priority: u32,
36    /// Path to the detected file
37    pub file_path: std::path::PathBuf,
38}
39
40/// Industry file detector
41pub struct IndustryFileDetector {
42    adapters: Vec<Box<dyn IndustryFileAdapter>>,
43}
44
45impl IndustryFileDetector {
46    /// Create a new detector with the given adapters
47    pub fn new(adapters: Vec<Box<dyn IndustryFileAdapter>>) -> Self {
48        IndustryFileDetector { adapters }
49    }
50
51    /// Detect which industry files exist in the project root
52    pub fn detect_files(&self, project_root: &Path) -> Vec<FileDetectionResult> {
53        let mut results = Vec::new();
54
55        for adapter in &self.adapters {
56            if adapter.can_handle(project_root) {
57                // For now, we just record that this adapter can handle the directory
58                // The actual file path detection is done by each adapter
59                results.push(FileDetectionResult {
60                    adapter_name: adapter.name().to_string(),
61                    priority: adapter.priority(),
62                    file_path: project_root.to_path_buf(),
63                });
64            }
65        }
66
67        // Sort by priority (highest first)
68        results.sort_by(|a, b| b.priority.cmp(&a.priority));
69        results
70    }
71
72    /// Get the highest priority adapter that can handle the project
73    pub fn get_best_adapter(&self, project_root: &Path) -> Option<&dyn IndustryFileAdapter> {
74        self.adapters
75            .iter()
76            .filter(|adapter| adapter.can_handle(project_root))
77            .max_by_key(|adapter| adapter.priority())
78            .map(|adapter| adapter.as_ref())
79    }
80
81    /// Register a new adapter
82    pub fn register_adapter(&mut self, adapter: Box<dyn IndustryFileAdapter>) {
83        self.adapters.push(adapter);
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    struct MockAdapter {
92        name: &'static str,
93        priority: u32,
94        can_handle: bool,
95    }
96
97    impl IndustryFileAdapter for MockAdapter {
98        fn name(&self) -> &'static str {
99            self.name
100        }
101
102        fn can_handle(&self, _project_root: &Path) -> bool {
103            self.can_handle
104        }
105
106        fn read_config(&self, _project_root: &Path) -> StorageResult<Config> {
107            Ok(Config::default())
108        }
109
110        fn priority(&self) -> u32 {
111            self.priority
112        }
113    }
114
115    #[test]
116    fn test_detector_sorts_by_priority() {
117        let adapters: Vec<Box<dyn IndustryFileAdapter>> = vec![
118            Box::new(MockAdapter {
119                name: "low",
120                priority: 1,
121                can_handle: true,
122            }),
123            Box::new(MockAdapter {
124                name: "high",
125                priority: 10,
126                can_handle: true,
127            }),
128            Box::new(MockAdapter {
129                name: "medium",
130                priority: 5,
131                can_handle: true,
132            }),
133        ];
134
135        let detector = IndustryFileDetector::new(adapters);
136        let results = detector.detect_files(Path::new("."));
137
138        assert_eq!(results.len(), 3);
139        assert_eq!(results[0].adapter_name, "high");
140        assert_eq!(results[1].adapter_name, "medium");
141        assert_eq!(results[2].adapter_name, "low");
142    }
143
144    #[test]
145    fn test_detector_filters_by_can_handle() {
146        let adapters: Vec<Box<dyn IndustryFileAdapter>> = vec![
147            Box::new(MockAdapter {
148                name: "yes",
149                priority: 1,
150                can_handle: true,
151            }),
152            Box::new(MockAdapter {
153                name: "no",
154                priority: 10,
155                can_handle: false,
156            }),
157        ];
158
159        let detector = IndustryFileDetector::new(adapters);
160        let results = detector.detect_files(Path::new("."));
161
162        assert_eq!(results.len(), 1);
163        assert_eq!(results[0].adapter_name, "yes");
164    }
165
166    #[test]
167    fn test_get_best_adapter() {
168        let adapters: Vec<Box<dyn IndustryFileAdapter>> = vec![
169            Box::new(MockAdapter {
170                name: "low",
171                priority: 1,
172                can_handle: true,
173            }),
174            Box::new(MockAdapter {
175                name: "high",
176                priority: 10,
177                can_handle: true,
178            }),
179        ];
180
181        let detector = IndustryFileDetector::new(adapters);
182        let best = detector.get_best_adapter(Path::new("."));
183
184        assert!(best.is_some());
185        assert_eq!(best.unwrap().name(), "high");
186    }
187
188    #[test]
189    fn test_get_best_adapter_respects_can_handle() {
190        let adapters: Vec<Box<dyn IndustryFileAdapter>> = vec![
191            Box::new(MockAdapter {
192                name: "low",
193                priority: 1,
194                can_handle: true,
195            }),
196            Box::new(MockAdapter {
197                name: "high",
198                priority: 10,
199                can_handle: false,
200            }),
201        ];
202
203        let detector = IndustryFileDetector::new(adapters);
204        let best = detector.get_best_adapter(Path::new("."));
205
206        assert!(best.is_some());
207        assert_eq!(best.unwrap().name(), "low");
208    }
209}