cuenv_core/tools/
registry.rs1use std::collections::HashMap;
7use std::sync::Arc;
8
9use super::provider::{ToolProvider, ToolSource};
10
11#[derive(Default)]
16pub struct ToolRegistry {
17 providers: HashMap<&'static str, Arc<dyn ToolProvider>>,
19}
20
21impl ToolRegistry {
22 #[must_use]
24 pub fn new() -> Self {
25 Self::default()
26 }
27
28 pub fn register<P: ToolProvider + 'static>(&mut self, provider: P) {
32 let name = provider.name();
33 self.providers.insert(name, Arc::new(provider));
34 }
35
36 pub fn register_arc(&mut self, provider: Arc<dyn ToolProvider>) {
40 let name = provider.name();
41 self.providers.insert(name, provider);
42 }
43
44 #[must_use]
46 pub fn get(&self, name: &str) -> Option<&Arc<dyn ToolProvider>> {
47 self.providers.get(name)
48 }
49
50 #[must_use]
52 pub fn find_for_source(&self, source: &ToolSource) -> Option<&Arc<dyn ToolProvider>> {
53 self.providers.values().find(|p| p.can_handle(source))
54 }
55
56 pub fn iter(&self) -> impl Iterator<Item = &Arc<dyn ToolProvider>> {
58 self.providers.values()
59 }
60
61 #[must_use]
63 pub fn len(&self) -> usize {
64 self.providers.len()
65 }
66
67 #[must_use]
69 pub fn is_empty(&self) -> bool {
70 self.providers.is_empty()
71 }
72
73 #[must_use]
75 pub fn names(&self) -> Vec<&'static str> {
76 self.providers.keys().copied().collect()
77 }
78}
79
80impl std::fmt::Debug for ToolRegistry {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 f.debug_struct("ToolRegistry")
83 .field("providers", &self.names())
84 .finish()
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use crate::tools::provider::{FetchedTool, Platform, ResolvedTool, ToolOptions};
92 use async_trait::async_trait;
93
94 struct MockProvider {
95 name: &'static str,
96 }
97
98 #[async_trait]
99 impl ToolProvider for MockProvider {
100 fn name(&self) -> &'static str {
101 self.name
102 }
103
104 fn description(&self) -> &'static str {
105 "Mock provider for testing"
106 }
107
108 fn can_handle(&self, source: &ToolSource) -> bool {
109 matches!(source, ToolSource::GitHub { .. }) && self.name == "github"
110 }
111
112 async fn resolve(
113 &self,
114 _tool_name: &str,
115 _version: &str,
116 _platform: &Platform,
117 _config: &serde_json::Value,
118 ) -> crate::Result<ResolvedTool> {
119 unimplemented!()
120 }
121
122 async fn fetch(
123 &self,
124 _resolved: &ResolvedTool,
125 _options: &ToolOptions,
126 ) -> crate::Result<FetchedTool> {
127 unimplemented!()
128 }
129
130 fn is_cached(&self, _resolved: &ResolvedTool, _options: &ToolOptions) -> bool {
131 false
132 }
133 }
134
135 #[test]
136 fn test_registry_register_and_get() {
137 let mut registry = ToolRegistry::new();
138 registry.register(MockProvider { name: "github" });
139
140 assert!(registry.get("github").is_some());
141 assert!(registry.get("nix").is_none());
142 }
143
144 #[test]
145 fn test_registry_find_for_source() {
146 let mut registry = ToolRegistry::new();
147 registry.register(MockProvider { name: "github" });
148
149 let source = ToolSource::GitHub {
150 repo: "org/repo".into(),
151 tag: "v1".into(),
152 asset: "file.zip".into(),
153 path: None,
154 };
155 assert!(registry.find_for_source(&source).is_some());
156
157 let source = ToolSource::Nix {
158 flake: "nixpkgs".into(),
159 package: "jq".into(),
160 output: None,
161 };
162 assert!(registry.find_for_source(&source).is_none());
163 }
164
165 #[test]
166 fn test_registry_iter() {
167 let mut registry = ToolRegistry::new();
168 registry.register(MockProvider { name: "github" });
169 registry.register(MockProvider { name: "nix" });
170
171 assert_eq!(registry.len(), 2);
172 assert!(!registry.is_empty());
173
174 let names: Vec<_> = registry.iter().map(|p| p.name()).collect();
175 assert!(names.contains(&"github"));
176 assert!(names.contains(&"nix"));
177 }
178
179 #[test]
180 fn test_registry_new() {
181 let registry = ToolRegistry::new();
182 assert!(registry.is_empty());
183 assert_eq!(registry.len(), 0);
184 }
185
186 #[test]
187 fn test_registry_default() {
188 let registry = ToolRegistry::default();
189 assert!(registry.is_empty());
190 assert_eq!(registry.len(), 0);
191 }
192
193 #[test]
194 fn test_registry_register_replaces_existing() {
195 let mut registry = ToolRegistry::new();
196 registry.register(MockProvider { name: "github" });
197 registry.register(MockProvider { name: "github" });
198
199 assert_eq!(registry.len(), 1);
201 }
202
203 #[test]
204 fn test_registry_register_arc() {
205 let mut registry = ToolRegistry::new();
206 let provider: Arc<dyn ToolProvider> = Arc::new(MockProvider { name: "github" });
207 registry.register_arc(provider);
208
209 assert!(registry.get("github").is_some());
210 assert_eq!(registry.len(), 1);
211 }
212
213 #[test]
214 fn test_registry_register_arc_replaces_existing() {
215 let mut registry = ToolRegistry::new();
216 registry.register(MockProvider { name: "github" });
217
218 let provider: Arc<dyn ToolProvider> = Arc::new(MockProvider { name: "github" });
219 registry.register_arc(provider);
220
221 assert_eq!(registry.len(), 1);
223 }
224
225 #[test]
226 fn test_registry_names() {
227 let mut registry = ToolRegistry::new();
228 registry.register(MockProvider { name: "github" });
229 registry.register(MockProvider { name: "nix" });
230 registry.register(MockProvider { name: "rustup" });
231
232 let names = registry.names();
233 assert_eq!(names.len(), 3);
234 assert!(names.contains(&"github"));
235 assert!(names.contains(&"nix"));
236 assert!(names.contains(&"rustup"));
237 }
238
239 #[test]
240 fn test_registry_names_empty() {
241 let registry = ToolRegistry::new();
242 let names = registry.names();
243 assert!(names.is_empty());
244 }
245
246 #[test]
247 fn test_registry_debug() {
248 let mut registry = ToolRegistry::new();
249 registry.register(MockProvider { name: "github" });
250
251 let debug_str = format!("{:?}", registry);
252 assert!(debug_str.contains("ToolRegistry"));
253 assert!(debug_str.contains("providers"));
254 assert!(debug_str.contains("github"));
255 }
256
257 #[test]
258 fn test_registry_debug_empty() {
259 let registry = ToolRegistry::new();
260 let debug_str = format!("{:?}", registry);
261 assert!(debug_str.contains("ToolRegistry"));
262 }
263
264 #[test]
265 fn test_registry_find_for_source_no_match() {
266 let mut registry = ToolRegistry::new();
267 registry.register(MockProvider { name: "github" });
269
270 let source = ToolSource::Rustup {
272 toolchain: "stable".into(),
273 profile: None,
274 components: vec![],
275 targets: vec![],
276 };
277 assert!(registry.find_for_source(&source).is_none());
278 }
279
280 #[test]
281 fn test_registry_find_for_source_oci() {
282 let registry = ToolRegistry::new();
283
284 let source = ToolSource::Oci {
285 image: "alpine:latest".into(),
286 path: "bin/sh".into(),
287 };
288 assert!(registry.find_for_source(&source).is_none());
290 }
291
292 #[test]
293 fn test_registry_get_nonexistent() {
294 let registry = ToolRegistry::new();
295 assert!(registry.get("nonexistent").is_none());
296 assert!(registry.get("github").is_none());
297 assert!(registry.get("").is_none());
298 }
299
300 #[test]
301 fn test_registry_iter_empty() {
302 let registry = ToolRegistry::new();
303 let count = registry.iter().count();
304 assert_eq!(count, 0);
305 }
306}