1use crate::{
4 graph::{DependencyGraph, ResolutionResult},
5 types::*,
6 Error, Result,
7};
8use std::collections::HashMap;
9use std::sync::Arc;
10use std::time::{Duration, Instant};
11use tokio::sync::RwLock;
12
13pub struct DependencyResolver {
15 graph: Arc<RwLock<DependencyGraph>>,
17 tool_registry: Arc<RwLock<HashMap<String, ToolSpec>>>,
19 resolution_cache: Arc<RwLock<HashMap<String, CachedResolution>>>,
21 availability_checker: Option<Arc<dyn AvailabilityChecker>>,
23 options: ResolutionOptions,
25}
26
27#[derive(Debug, Clone)]
29struct CachedResolution {
30 result: ResolutionResult,
32 cached_at: Instant,
34 ttl: Duration,
36}
37
38#[derive(Debug, Clone)]
40pub struct ResolutionOptions {
41 pub include_optional: bool,
43 pub include_dev: bool,
45 pub max_depth: usize,
47 pub cache_ttl: Duration,
49 pub enable_parallel: bool,
51 pub platform_filter: Option<String>,
53 pub allow_prerelease: bool,
55}
56
57#[async_trait::async_trait]
59pub trait AvailabilityChecker: Send + Sync {
60 async fn is_available(&self, tool_name: &str) -> Result<bool>;
62
63 async fn get_version(&self, tool_name: &str) -> Result<Option<String>>;
65
66 async fn get_path(&self, tool_name: &str) -> Result<Option<String>>;
68}
69
70impl DependencyResolver {
71 pub fn new() -> Self {
73 Self {
74 graph: Arc::new(RwLock::new(DependencyGraph::new())),
75 tool_registry: Arc::new(RwLock::new(HashMap::new())),
76 resolution_cache: Arc::new(RwLock::new(HashMap::new())),
77 availability_checker: None,
78 options: ResolutionOptions::default(),
79 }
80 }
81
82 pub fn with_options(options: ResolutionOptions) -> Self {
84 Self {
85 options,
86 ..Self::new()
87 }
88 }
89
90 pub fn with_availability_checker(mut self, checker: Arc<dyn AvailabilityChecker>) -> Self {
92 self.availability_checker = Some(checker);
93 self
94 }
95
96 pub async fn register_tool(&self, tool_spec: ToolSpec) -> Result<()> {
98 let tool_name = tool_spec.name.clone();
99
100 {
102 let mut registry = self.tool_registry.write().await;
103 registry.insert(tool_name.clone(), tool_spec.clone());
104 }
105
106 {
108 let mut graph = self.graph.write().await;
109 graph.add_tool(tool_spec)?;
110 }
111
112 if let Some(checker) = &self.availability_checker {
114 let available = checker.is_available(&tool_name).await.unwrap_or(false);
115 let version = if available {
116 checker.get_version(&tool_name).await.unwrap_or(None)
117 } else {
118 None
119 };
120
121 let mut graph = self.graph.write().await;
122 graph.set_tool_available(&tool_name, available, version);
123 }
124
125 Ok(())
126 }
127
128 pub async fn register_tools(&self, tools: Vec<ToolSpec>) -> Result<()> {
130 for tool in tools {
131 self.register_tool(tool).await?;
132 }
133 Ok(())
134 }
135
136 pub async fn resolve(&self, tool_name: &str) -> Result<ResolutionResult> {
138 if let Some(cached) = self.get_cached_resolution(tool_name).await {
140 if cached.cached_at.elapsed() < cached.ttl {
141 return Ok(cached.result);
142 }
143 }
144
145 let result = self.resolve_uncached(tool_name).await?;
147
148 self.cache_resolution(tool_name, result.clone()).await;
150
151 Ok(result)
152 }
153
154 async fn resolve_uncached(&self, tool_name: &str) -> Result<ResolutionResult> {
156 if !self.is_tool_registered(tool_name).await {
158 return Err(Error::ToolNotFound {
159 tool: tool_name.to_string(),
160 });
161 }
162
163 self.update_availability().await?;
165
166 let mut graph = self.graph.write().await;
168 let mut result = graph.resolve_dependencies(tool_name)?;
169
170 self.filter_resolution(&mut result).await;
172
173 Ok(result)
174 }
175
176 pub async fn resolve_multiple(&self, tool_names: &[String]) -> Result<ResolutionResult> {
178 let mut combined_result = ResolutionResult {
179 install_order: Vec::new(),
180 missing_tools: Vec::new(),
181 available_tools: Vec::new(),
182 circular_dependencies: Vec::new(),
183 version_conflicts: Vec::new(),
184 };
185
186 for tool_name in tool_names {
188 let result = self.resolve(tool_name).await?;
189
190 for tool in result.install_order {
192 if !combined_result.install_order.contains(&tool) {
193 combined_result.install_order.push(tool);
194 }
195 }
196
197 for tool in result.missing_tools {
199 if !combined_result.missing_tools.contains(&tool) {
200 combined_result.missing_tools.push(tool);
201 }
202 }
203
204 for tool in result.available_tools {
205 if !combined_result.available_tools.contains(&tool) {
206 combined_result.available_tools.push(tool);
207 }
208 }
209
210 combined_result
211 .circular_dependencies
212 .extend(result.circular_dependencies);
213 combined_result
214 .version_conflicts
215 .extend(result.version_conflicts);
216 }
217
218 let final_order = {
220 let mut graph = self.graph.write().await;
221 graph.get_install_order(&combined_result.install_order)?
222 };
223 combined_result.install_order = final_order;
224
225 Ok(combined_result)
226 }
227
228 pub async fn is_tool_registered(&self, tool_name: &str) -> bool {
230 let registry = self.tool_registry.read().await;
231 registry.contains_key(tool_name)
232 }
233
234 pub async fn get_tool_spec(&self, tool_name: &str) -> Option<ToolSpec> {
236 let registry = self.tool_registry.read().await;
237 registry.get(tool_name).cloned()
238 }
239
240 pub async fn get_all_tools(&self) -> Vec<String> {
242 let registry = self.tool_registry.read().await;
243 registry.keys().cloned().collect()
244 }
245
246 pub async fn clear_cache(&self) {
248 let mut cache = self.resolution_cache.write().await;
249 cache.clear();
250 }
251
252 pub async fn get_stats(&self) -> crate::graph::GraphStats {
254 let graph = self.graph.read().await;
255 graph.get_stats()
256 }
257
258 async fn get_cached_resolution(&self, tool_name: &str) -> Option<CachedResolution> {
261 let cache = self.resolution_cache.read().await;
262 cache.get(tool_name).cloned()
263 }
264
265 async fn cache_resolution(&self, tool_name: &str, result: ResolutionResult) {
266 let mut cache = self.resolution_cache.write().await;
267 cache.insert(
268 tool_name.to_string(),
269 CachedResolution {
270 result,
271 cached_at: Instant::now(),
272 ttl: self.options.cache_ttl,
273 },
274 );
275 }
276
277 async fn update_availability(&self) -> Result<()> {
278 if let Some(checker) = &self.availability_checker {
279 let tools = self.get_all_tools().await;
280 let mut graph = self.graph.write().await;
281
282 for tool_name in tools {
283 let available = checker.is_available(&tool_name).await.unwrap_or(false);
284 let version = if available {
285 checker.get_version(&tool_name).await.unwrap_or(None)
286 } else {
287 None
288 };
289
290 graph.set_tool_available(&tool_name, available, version);
291 }
292 }
293 Ok(())
294 }
295
296 async fn filter_resolution(&self, result: &mut ResolutionResult) {
297 if let Some(platform) = &self.options.platform_filter {
299 let registry = self.tool_registry.read().await;
300
301 result.install_order.retain(|tool_name| {
302 if let Some(tool_spec) = registry.get(tool_name) {
303 tool_spec
304 .dependencies
305 .iter()
306 .all(|dep| dep.applies_to_platform(platform))
307 } else {
308 true
309 }
310 });
311 }
312
313 if !self.options.include_optional || !self.options.include_dev {
315 }
317 }
318}
319
320impl Default for ResolutionOptions {
321 fn default() -> Self {
322 Self {
323 include_optional: false,
324 include_dev: false,
325 max_depth: 10,
326 cache_ttl: Duration::from_secs(300), enable_parallel: true,
328 platform_filter: None,
329 allow_prerelease: false,
330 }
331 }
332}
333
334impl Default for DependencyResolver {
335 fn default() -> Self {
336 Self::new()
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 struct MockAvailabilityChecker {
345 available_tools: HashMap<String, (bool, Option<String>)>,
346 }
347
348 impl MockAvailabilityChecker {
349 fn new() -> Self {
350 let mut available_tools = HashMap::new();
351 available_tools.insert("node".to_string(), (true, Some("18.0.0".to_string())));
352 available_tools.insert("python".to_string(), (true, Some("3.9.0".to_string())));
353
354 Self { available_tools }
355 }
356 }
357
358 #[async_trait::async_trait]
359 impl AvailabilityChecker for MockAvailabilityChecker {
360 async fn is_available(&self, tool_name: &str) -> Result<bool> {
361 Ok(self
362 .available_tools
363 .get(tool_name)
364 .map(|(available, _)| *available)
365 .unwrap_or(false))
366 }
367
368 async fn get_version(&self, tool_name: &str) -> Result<Option<String>> {
369 Ok(self
370 .available_tools
371 .get(tool_name)
372 .and_then(|(_, version)| version.clone()))
373 }
374
375 async fn get_path(&self, _tool_name: &str) -> Result<Option<String>> {
376 Ok(None)
377 }
378 }
379
380 fn create_test_tool(name: &str, deps: Vec<&str>) -> ToolSpec {
381 ToolSpec {
382 name: name.to_string(),
383 dependencies: deps
384 .into_iter()
385 .map(|dep| DependencySpec::required(dep, format!("{} requires {}", name, dep)))
386 .collect(),
387 ..Default::default()
388 }
389 }
390
391 #[tokio::test]
392 async fn test_resolver_basic_functionality() {
393 let resolver = DependencyResolver::new()
394 .with_availability_checker(Arc::new(MockAvailabilityChecker::new()));
395
396 resolver
398 .register_tool(create_test_tool("node", vec![]))
399 .await
400 .unwrap();
401 resolver
402 .register_tool(create_test_tool("yarn", vec!["node"]))
403 .await
404 .unwrap();
405
406 let result = resolver.resolve("yarn").await.unwrap();
408
409 assert_eq!(result.install_order, vec!["node", "yarn"]);
410 assert_eq!(result.available_tools, vec!["node"]);
411 assert_eq!(result.missing_tools, vec!["yarn"]);
412 }
413
414 #[tokio::test]
415 async fn test_resolver_multiple_tools() {
416 let resolver = DependencyResolver::new()
417 .with_availability_checker(Arc::new(MockAvailabilityChecker::new()));
418
419 resolver
421 .register_tool(create_test_tool("node", vec![]))
422 .await
423 .unwrap();
424 resolver
425 .register_tool(create_test_tool("python", vec![]))
426 .await
427 .unwrap();
428 resolver
429 .register_tool(create_test_tool("yarn", vec!["node"]))
430 .await
431 .unwrap();
432 resolver
433 .register_tool(create_test_tool("pip", vec!["python"]))
434 .await
435 .unwrap();
436
437 let result = resolver
439 .resolve_multiple(&["yarn".to_string(), "pip".to_string()])
440 .await
441 .unwrap();
442
443 assert!(result.install_order.contains(&"node".to_string()));
445 assert!(result.install_order.contains(&"python".to_string()));
446 assert!(result.install_order.contains(&"yarn".to_string()));
447 assert!(result.install_order.contains(&"pip".to_string()));
448 }
449
450 #[tokio::test]
451 async fn test_resolver_caching() {
452 let resolver = DependencyResolver::new()
453 .with_availability_checker(Arc::new(MockAvailabilityChecker::new()));
454
455 resolver
456 .register_tool(create_test_tool("node", vec![]))
457 .await
458 .unwrap();
459 resolver
460 .register_tool(create_test_tool("yarn", vec!["node"]))
461 .await
462 .unwrap();
463
464 let start = Instant::now();
466 let result1 = resolver.resolve("yarn").await.unwrap();
467 let first_duration = start.elapsed();
468
469 let start = Instant::now();
471 let result2 = resolver.resolve("yarn").await.unwrap();
472 let second_duration = start.elapsed();
473
474 assert_eq!(result1.install_order, result2.install_order);
476
477 assert!(second_duration < first_duration);
479 }
480
481 #[tokio::test]
482 async fn test_resolver_unregistered_tool() {
483 let resolver = DependencyResolver::new();
484
485 let result = resolver.resolve("nonexistent").await;
486 assert!(result.is_err());
487
488 if let Err(Error::ToolNotFound { tool }) = result {
489 assert_eq!(tool, "nonexistent");
490 } else {
491 panic!("Expected ToolNotFound error");
492 }
493 }
494}