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, ResolvedTool, ToolOptions, ToolResolveRequest};
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(&self, _request: &ToolResolveRequest<'_>) -> crate::Result<ResolvedTool> {
113 unimplemented!()
114 }
115
116 async fn fetch(
117 &self,
118 _resolved: &ResolvedTool,
119 _options: &ToolOptions,
120 ) -> crate::Result<FetchedTool> {
121 unimplemented!()
122 }
123
124 fn is_cached(&self, _resolved: &ResolvedTool, _options: &ToolOptions) -> bool {
125 false
126 }
127 }
128
129 #[test]
130 fn test_registry_register_and_get() {
131 let mut registry = ToolRegistry::new();
132 registry.register(MockProvider { name: "github" });
133
134 assert!(registry.get("github").is_some());
135 assert!(registry.get("nix").is_none());
136 }
137
138 #[test]
139 fn test_registry_find_for_source() {
140 let mut registry = ToolRegistry::new();
141 registry.register(MockProvider { name: "github" });
142
143 let source = ToolSource::GitHub {
144 repo: "org/repo".into(),
145 tag: "v1".into(),
146 asset: "file.zip".into(),
147 extract: vec![],
148 };
149 assert!(registry.find_for_source(&source).is_some());
150
151 let source = ToolSource::Nix {
152 flake: "nixpkgs".into(),
153 package: "jq".into(),
154 output: None,
155 };
156 assert!(registry.find_for_source(&source).is_none());
157 }
158
159 #[test]
160 fn test_registry_iter() {
161 let mut registry = ToolRegistry::new();
162 registry.register(MockProvider { name: "github" });
163 registry.register(MockProvider { name: "nix" });
164
165 assert_eq!(registry.len(), 2);
166 assert!(!registry.is_empty());
167
168 let names: Vec<_> = registry.iter().map(|p| p.name()).collect();
169 assert!(names.contains(&"github"));
170 assert!(names.contains(&"nix"));
171 }
172
173 #[test]
174 fn test_registry_new() {
175 let registry = ToolRegistry::new();
176 assert!(registry.is_empty());
177 assert_eq!(registry.len(), 0);
178 }
179
180 #[test]
181 fn test_registry_default() {
182 let registry = ToolRegistry::default();
183 assert!(registry.is_empty());
184 assert_eq!(registry.len(), 0);
185 }
186
187 #[test]
188 fn test_registry_register_replaces_existing() {
189 let mut registry = ToolRegistry::new();
190 registry.register(MockProvider { name: "github" });
191 registry.register(MockProvider { name: "github" });
192
193 assert_eq!(registry.len(), 1);
195 }
196
197 #[test]
198 fn test_registry_register_arc() {
199 let mut registry = ToolRegistry::new();
200 let provider: Arc<dyn ToolProvider> = Arc::new(MockProvider { name: "github" });
201 registry.register_arc(provider);
202
203 assert!(registry.get("github").is_some());
204 assert_eq!(registry.len(), 1);
205 }
206
207 #[test]
208 fn test_registry_register_arc_replaces_existing() {
209 let mut registry = ToolRegistry::new();
210 registry.register(MockProvider { name: "github" });
211
212 let provider: Arc<dyn ToolProvider> = Arc::new(MockProvider { name: "github" });
213 registry.register_arc(provider);
214
215 assert_eq!(registry.len(), 1);
217 }
218
219 #[test]
220 fn test_registry_names() {
221 let mut registry = ToolRegistry::new();
222 registry.register(MockProvider { name: "github" });
223 registry.register(MockProvider { name: "nix" });
224 registry.register(MockProvider { name: "rustup" });
225
226 let names = registry.names();
227 assert_eq!(names.len(), 3);
228 assert!(names.contains(&"github"));
229 assert!(names.contains(&"nix"));
230 assert!(names.contains(&"rustup"));
231 }
232
233 #[test]
234 fn test_registry_names_empty() {
235 let registry = ToolRegistry::new();
236 let names = registry.names();
237 assert!(names.is_empty());
238 }
239
240 #[test]
241 fn test_registry_debug() {
242 let mut registry = ToolRegistry::new();
243 registry.register(MockProvider { name: "github" });
244
245 let debug_str = format!("{:?}", registry);
246 assert!(debug_str.contains("ToolRegistry"));
247 assert!(debug_str.contains("providers"));
248 assert!(debug_str.contains("github"));
249 }
250
251 #[test]
252 fn test_registry_debug_empty() {
253 let registry = ToolRegistry::new();
254 let debug_str = format!("{:?}", registry);
255 assert!(debug_str.contains("ToolRegistry"));
256 }
257
258 #[test]
259 fn test_registry_find_for_source_no_match() {
260 let mut registry = ToolRegistry::new();
261 registry.register(MockProvider { name: "github" });
263
264 let source = ToolSource::Rustup {
266 toolchain: "stable".into(),
267 profile: None,
268 components: vec![],
269 targets: vec![],
270 };
271 assert!(registry.find_for_source(&source).is_none());
272 }
273
274 #[test]
275 fn test_registry_find_for_source_oci() {
276 let registry = ToolRegistry::new();
277
278 let source = ToolSource::Oci {
279 image: "alpine:latest".into(),
280 path: "bin/sh".into(),
281 };
282 assert!(registry.find_for_source(&source).is_none());
284 }
285
286 #[test]
287 fn test_registry_get_nonexistent() {
288 let registry = ToolRegistry::new();
289 assert!(registry.get("nonexistent").is_none());
290 assert!(registry.get("github").is_none());
291 assert!(registry.get("").is_none());
292 }
293
294 #[test]
295 fn test_registry_iter_empty() {
296 let registry = ToolRegistry::new();
297 let count = registry.iter().count();
298 assert_eq!(count, 0);
299 }
300}