1#![allow(dead_code)]
8use std::collections::{HashMap, HashSet, VecDeque};
9use std::path::Path;
10
11use serde::{Deserialize, Serialize};
12use torsh_core::error::{Result, TorshError};
13
14use crate::package::Package;
15use crate::version::{PackageVersion, VersionRequirement};
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
19pub struct DependencySpec {
20 pub name: String,
22 pub version_req: String,
24 pub features: Vec<String>,
26 pub optional: bool,
28 pub platform: Option<String>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct ResolvedDependency {
35 pub spec: DependencySpec,
37 pub resolved_version: String,
39 pub package_path: Option<String>,
41 pub dependencies: Vec<ResolvedDependency>,
43}
44
45#[derive(Debug, Clone, Copy)]
47pub enum ResolutionStrategy {
48 Highest,
50 Lowest,
52 Stable,
54}
55
56pub struct DependencyResolver {
58 strategy: ResolutionStrategy,
60 registry: Box<dyn PackageRegistry>,
62 max_depth: usize,
64 parallel_resolution: bool,
66}
67
68pub trait PackageRegistry: Send + Sync {
70 fn search_packages(&self, name_pattern: &str) -> Result<Vec<PackageInfo>>;
72
73 fn get_versions(&self, package_name: &str) -> Result<Vec<String>>;
75
76 fn download_package(&self, name: &str, version: &str, dest_path: &Path) -> Result<()>;
78
79 fn get_package_info(&self, name: &str, version: &str) -> Result<PackageInfo>;
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct PackageInfo {
86 pub name: String,
88 pub version: String,
90 pub description: Option<String>,
92 pub author: Option<String>,
94 pub dependencies: Vec<DependencySpec>,
96 pub size: u64,
98 pub checksum: String,
100 pub registry_url: String,
102}
103
104#[derive(Debug, Clone)]
106pub struct DependencyConflict {
107 pub package_name: String,
109 pub conflicts: Vec<(String, String)>, pub suggestion: Option<String>,
113}
114
115#[derive(Debug, Clone)]
117pub struct DependencyGraph {
118 nodes: HashMap<String, PackageInfo>,
120 edges: HashMap<String, Vec<String>>,
122 resolved_versions: HashMap<String, String>,
124}
125
126impl DependencySpec {
127 pub fn new(name: String, version_req: String) -> Self {
129 Self {
130 name,
131 version_req,
132 features: Vec::new(),
133 optional: false,
134 platform: None,
135 }
136 }
137
138 pub fn with_feature(mut self, feature: String) -> Self {
140 self.features.push(feature);
141 self
142 }
143
144 pub fn optional(mut self) -> Self {
146 self.optional = true;
147 self
148 }
149
150 pub fn for_platform(mut self, platform: String) -> Self {
152 self.platform = Some(platform);
153 self
154 }
155
156 pub fn is_compatible_platform(&self) -> bool {
158 self.platform
160 .as_ref()
161 .map_or(true, |p| p == "any" || p == std::env::consts::OS)
162 }
163
164 pub fn is_satisfied_by(&self, version: &str) -> Result<bool> {
166 let requirement = VersionRequirement::parse(&self.version_req).map_err(|e| {
167 TorshError::InvalidArgument(format!("Invalid version requirement: {}", e))
168 })?;
169 let package_version = PackageVersion::parse(version)
170 .map_err(|e| TorshError::InvalidArgument(format!("Invalid version: {}", e)))?;
171 Ok(requirement.matches(&package_version))
172 }
173}
174
175impl Default for DependencyResolver {
176 fn default() -> Self {
177 Self::new(Box::new(LocalPackageRegistry::default()))
178 }
179}
180
181impl DependencyResolver {
182 pub fn new(registry: Box<dyn PackageRegistry>) -> Self {
184 Self {
185 strategy: ResolutionStrategy::Highest,
186 registry,
187 max_depth: 100,
188 parallel_resolution: false,
189 }
190 }
191
192 pub fn with_strategy(mut self, strategy: ResolutionStrategy) -> Self {
194 self.strategy = strategy;
195 self
196 }
197
198 pub fn with_max_depth(mut self, max_depth: usize) -> Self {
200 self.max_depth = max_depth;
201 self
202 }
203
204 pub fn with_parallel_resolution(mut self, parallel: bool) -> Self {
206 self.parallel_resolution = parallel;
207 self
208 }
209
210 pub fn resolve_dependencies(&self, package: &Package) -> Result<Vec<ResolvedDependency>> {
212 let mut resolved = Vec::new();
213 let mut visited = HashSet::new();
214 let mut queue = VecDeque::new();
215
216 for (name, version_req) in &package.manifest.dependencies {
218 let spec = DependencySpec::new(name.clone(), version_req.clone());
219 queue.push_back((spec, 0)); }
221
222 while let Some((spec, depth)) = queue.pop_front() {
223 if depth >= self.max_depth {
224 return Err(TorshError::InvalidArgument(format!(
225 "Maximum dependency depth exceeded for package: {}",
226 spec.name
227 )));
228 }
229
230 if visited.contains(&spec.name) {
231 continue;
232 }
233
234 if !spec.is_compatible_platform() {
235 continue; }
237
238 let resolved_version = self.resolve_version(&spec)?;
240 let package_info = self
241 .registry
242 .get_package_info(&spec.name, &resolved_version)?;
243
244 for dep in &package_info.dependencies {
246 if !visited.contains(&dep.name) && !dep.optional {
247 queue.push_back((dep.clone(), depth + 1));
248 }
249 }
250
251 let resolved_dep = ResolvedDependency {
252 spec: spec.clone(),
253 resolved_version,
254 package_path: None, dependencies: Vec::new(), };
257
258 resolved.push(resolved_dep);
259 visited.insert(spec.name.clone());
260 }
261
262 self.check_conflicts(&resolved)?;
264
265 Ok(resolved)
266 }
267
268 fn resolve_version(&self, spec: &DependencySpec) -> Result<String> {
270 let available_versions = self.registry.get_versions(&spec.name)?;
271
272 if available_versions.is_empty() {
273 return Err(TorshError::InvalidArgument(format!(
274 "No versions found for package: {}",
275 spec.name
276 )));
277 }
278
279 let mut compatible_versions = Vec::new();
281 for version in &available_versions {
282 if spec.is_satisfied_by(version)? {
283 compatible_versions.push(version.clone());
284 }
285 }
286
287 if compatible_versions.is_empty() {
288 return Err(TorshError::InvalidArgument(format!(
289 "No compatible versions found for package: {} with requirement: {}",
290 spec.name, spec.version_req
291 )));
292 }
293
294 let selected_version = match self.strategy {
296 ResolutionStrategy::Highest => self.select_highest_version(&compatible_versions)?,
297 ResolutionStrategy::Lowest => self.select_lowest_version(&compatible_versions)?,
298 ResolutionStrategy::Stable => self.select_stable_version(&compatible_versions)?,
299 };
300
301 Ok(selected_version)
302 }
303
304 fn select_highest_version(&self, versions: &[String]) -> Result<String> {
306 let mut parsed_versions: Vec<_> = versions
307 .iter()
308 .map(|v| (v, PackageVersion::parse(v)))
309 .filter_map(|(v, parsed)| parsed.ok().map(|p| (v.clone(), p)))
310 .collect();
311
312 parsed_versions.sort_by(|a, b| b.1.cmp(&a.1));
313
314 parsed_versions
315 .first()
316 .map(|(version, _)| version.clone())
317 .ok_or_else(|| TorshError::InvalidArgument("No valid versions found".to_string()))
318 }
319
320 fn select_lowest_version(&self, versions: &[String]) -> Result<String> {
322 let mut parsed_versions: Vec<_> = versions
323 .iter()
324 .map(|v| (v, PackageVersion::parse(v)))
325 .filter_map(|(v, parsed)| parsed.ok().map(|p| (v.clone(), p)))
326 .collect();
327
328 parsed_versions.sort_by(|a, b| a.1.cmp(&b.1));
329
330 parsed_versions
331 .first()
332 .map(|(version, _)| version.clone())
333 .ok_or_else(|| TorshError::InvalidArgument("No valid versions found".to_string()))
334 }
335
336 fn select_stable_version(&self, versions: &[String]) -> Result<String> {
338 let mut stable_versions: Vec<_> = versions
339 .iter()
340 .map(|v| (v, PackageVersion::parse(v)))
341 .filter_map(|(v, parsed)| {
342 parsed.ok().and_then(|p| {
343 if p.pre_release.is_none() {
344 Some((v.clone(), p))
346 } else {
347 None
348 }
349 })
350 })
351 .collect();
352
353 if stable_versions.is_empty() {
354 return self.select_highest_version(versions);
356 }
357
358 stable_versions.sort_by(|a, b| b.1.cmp(&a.1));
359
360 stable_versions
361 .first()
362 .map(|(version, _)| version.clone())
363 .ok_or_else(|| TorshError::InvalidArgument("No stable versions found".to_string()))
364 }
365
366 fn check_conflicts(&self, resolved: &[ResolvedDependency]) -> Result<()> {
368 let mut package_versions: HashMap<String, Vec<String>> = HashMap::new();
369
370 for dep in resolved {
371 package_versions
372 .entry(dep.spec.name.clone())
373 .or_default()
374 .push(dep.resolved_version.clone());
375 }
376
377 let mut conflicts = Vec::new();
378 for (package_name, versions) in &package_versions {
379 let unique_versions: HashSet<_> = versions.iter().collect();
380 if unique_versions.len() > 1 {
381 let conflict = DependencyConflict {
382 package_name: package_name.clone(),
383 conflicts: versions
384 .iter()
385 .map(|v| (package_name.clone(), v.clone()))
386 .collect(),
387 suggestion: Some(format!("Use version {}", versions[0])),
388 };
389 conflicts.push(conflict);
390 }
391 }
392
393 if !conflicts.is_empty() {
394 let conflict_descriptions: Vec<String> = conflicts
395 .iter()
396 .map(|c| {
397 format!(
398 "Package '{}' has conflicting version requirements",
399 c.package_name
400 )
401 })
402 .collect();
403
404 return Err(TorshError::InvalidArgument(format!(
405 "Dependency conflicts detected: {}",
406 conflict_descriptions.join(", ")
407 )));
408 }
409
410 Ok(())
411 }
412
413 pub fn install_dependencies(
415 &self,
416 resolved: &mut [ResolvedDependency],
417 install_dir: &Path,
418 ) -> Result<()> {
419 for dep in resolved {
420 let package_path = install_dir.join(format!(
421 "{}-{}.torshpkg",
422 dep.spec.name, dep.resolved_version
423 ));
424
425 self.registry
427 .download_package(&dep.spec.name, &dep.resolved_version, &package_path)?;
428
429 dep.package_path = Some(package_path.to_string_lossy().to_string());
431 }
432
433 Ok(())
434 }
435
436 pub fn build_dependency_graph(&self, package: &Package) -> Result<DependencyGraph> {
438 let resolved = self.resolve_dependencies(package)?;
439 let mut graph = DependencyGraph::new();
440
441 for dep in &resolved {
442 let package_info = self
443 .registry
444 .get_package_info(&dep.spec.name, &dep.resolved_version)?;
445 graph.add_package(package_info);
446 }
447
448 Ok(graph)
449 }
450}
451
452impl DependencyGraph {
453 pub fn new() -> Self {
455 Self {
456 nodes: HashMap::new(),
457 edges: HashMap::new(),
458 resolved_versions: HashMap::new(),
459 }
460 }
461
462 pub fn add_package(&mut self, package_info: PackageInfo) {
464 let package_name = package_info.name.clone();
465 self.resolved_versions
466 .insert(package_name.clone(), package_info.version.clone());
467
468 let mut dependencies = Vec::new();
470 for dep in &package_info.dependencies {
471 dependencies.push(dep.name.clone());
472 }
473 self.edges.insert(package_name.clone(), dependencies);
474
475 self.nodes.insert(package_name, package_info);
476 }
477
478 pub fn topological_sort(&self) -> Result<Vec<String>> {
480 let mut result = Vec::new();
481 let mut visited = HashSet::new();
482 let mut in_stack = HashSet::new();
483
484 for package_name in self.nodes.keys() {
485 if !visited.contains(package_name) {
486 self.topological_sort_util(package_name, &mut visited, &mut in_stack, &mut result)?;
487 }
488 }
489
490 result.reverse();
491 Ok(result)
492 }
493
494 fn topological_sort_util(
496 &self,
497 package_name: &str,
498 visited: &mut HashSet<String>,
499 in_stack: &mut HashSet<String>,
500 result: &mut Vec<String>,
501 ) -> Result<()> {
502 if in_stack.contains(package_name) {
503 return Err(TorshError::InvalidArgument(format!(
504 "Circular dependency detected involving package: {}",
505 package_name
506 )));
507 }
508
509 if visited.contains(package_name) {
510 return Ok(());
511 }
512
513 visited.insert(package_name.to_string());
514 in_stack.insert(package_name.to_string());
515
516 if let Some(dependencies) = self.edges.get(package_name) {
517 for dep in dependencies {
518 self.topological_sort_util(dep, visited, in_stack, result)?;
519 }
520 }
521
522 in_stack.remove(package_name);
523 result.push(package_name.to_string());
524 Ok(())
525 }
526
527 pub fn get_packages(&self) -> &HashMap<String, PackageInfo> {
529 &self.nodes
530 }
531
532 pub fn get_dependencies(&self, package_name: &str) -> Option<&Vec<String>> {
534 self.edges.get(package_name)
535 }
536}
537
538#[derive(Debug, Default)]
540pub struct LocalPackageRegistry {
541 cache_dir: Option<String>,
543 packages: HashMap<String, Vec<PackageInfo>>,
545}
546
547impl LocalPackageRegistry {
548 pub fn new() -> Self {
550 Self::default()
551 }
552
553 pub fn add_package(&mut self, package_info: PackageInfo) {
555 self.packages
556 .entry(package_info.name.clone())
557 .or_default()
558 .push(package_info);
559 }
560}
561
562impl PackageRegistry for LocalPackageRegistry {
563 fn search_packages(&self, name_pattern: &str) -> Result<Vec<PackageInfo>> {
564 let mut results = Vec::new();
565
566 for (name, packages) in &self.packages {
567 if name.contains(name_pattern) {
568 results.extend(packages.iter().cloned());
569 }
570 }
571
572 Ok(results)
573 }
574
575 fn get_versions(&self, package_name: &str) -> Result<Vec<String>> {
576 let versions = self
577 .packages
578 .get(package_name)
579 .map(|packages| packages.iter().map(|p| p.version.clone()).collect())
580 .unwrap_or_default();
581
582 Ok(versions)
583 }
584
585 fn download_package(&self, _name: &str, _version: &str, _dest_path: &Path) -> Result<()> {
586 Ok(())
588 }
589
590 fn get_package_info(&self, name: &str, version: &str) -> Result<PackageInfo> {
591 let packages = self
592 .packages
593 .get(name)
594 .ok_or_else(|| TorshError::InvalidArgument(format!("Package not found: {}", name)))?;
595
596 packages
597 .iter()
598 .find(|p| p.version == version)
599 .cloned()
600 .ok_or_else(|| {
601 TorshError::InvalidArgument(format!(
602 "Version {} not found for package: {}",
603 version, name
604 ))
605 })
606 }
607}
608
609#[cfg(test)]
610mod tests {
611 use super::*;
612
613 fn create_test_package_info(name: &str, version: &str) -> PackageInfo {
614 PackageInfo {
615 name: name.to_string(),
616 version: version.to_string(),
617 description: None,
618 author: None,
619 dependencies: Vec::new(),
620 size: 1024,
621 checksum: "abc123".to_string(),
622 registry_url: "http://localhost".to_string(),
623 }
624 }
625
626 #[test]
627 fn test_dependency_spec_creation() {
628 let spec = DependencySpec::new("test".to_string(), "^1.0.0".to_string())
629 .with_feature("test-feature".to_string())
630 .optional()
631 .for_platform("linux".to_string());
632
633 assert_eq!(spec.name, "test");
634 assert_eq!(spec.version_req, "^1.0.0");
635 assert_eq!(spec.features, vec!["test-feature"]);
636 assert!(spec.optional);
637 assert_eq!(spec.platform, Some("linux".to_string()));
638 }
639
640 #[test]
641 fn test_dependency_spec_version_satisfaction() {
642 let spec = DependencySpec::new("test".to_string(), "^1.0.0".to_string());
643
644 assert!(spec.is_satisfied_by("1.0.0").unwrap());
645 assert!(spec.is_satisfied_by("1.5.0").unwrap());
646 assert!(!spec.is_satisfied_by("2.0.0").unwrap());
647 assert!(!spec.is_satisfied_by("0.9.0").unwrap());
648 }
649
650 #[test]
651 fn test_local_package_registry() {
652 let mut registry = LocalPackageRegistry::new();
653 let package_info = create_test_package_info("test-package", "1.0.0");
654
655 registry.add_package(package_info.clone());
656
657 let versions = registry.get_versions("test-package").unwrap();
658 assert_eq!(versions, vec!["1.0.0"]);
659
660 let retrieved_info = registry.get_package_info("test-package", "1.0.0").unwrap();
661 assert_eq!(retrieved_info.name, package_info.name);
662 assert_eq!(retrieved_info.version, package_info.version);
663 }
664
665 #[test]
666 fn test_dependency_resolution_strategy() {
667 let registry = Box::new(LocalPackageRegistry::new());
668 let resolver = DependencyResolver::new(registry)
669 .with_strategy(ResolutionStrategy::Highest)
670 .with_max_depth(50);
671
672 match resolver.strategy {
674 ResolutionStrategy::Highest => (),
675 _ => panic!("Strategy not set correctly"),
676 }
677
678 assert_eq!(resolver.max_depth, 50);
679 }
680
681 #[test]
682 fn test_dependency_graph() {
683 let mut graph = DependencyGraph::new();
684 let package_info = create_test_package_info("test-package", "1.0.0");
685
686 graph.add_package(package_info.clone());
687
688 assert_eq!(graph.nodes.len(), 1);
689 assert!(graph.nodes.contains_key("test-package"));
690 }
691
692 #[test]
693 fn test_version_selection() {
694 let resolver = DependencyResolver::default();
695 let versions = vec![
696 "1.0.0".to_string(),
697 "1.5.0".to_string(),
698 "2.0.0".to_string(),
699 ];
700
701 let highest = resolver.select_highest_version(&versions).unwrap();
702 assert_eq!(highest, "2.0.0");
703
704 let lowest = resolver.select_lowest_version(&versions).unwrap();
705 assert_eq!(lowest, "1.0.0");
706 }
707}