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