use anyhow::Result;
use semver::Version;
use std::collections::HashMap;
use super::{ConstraintSet, VersionConstraint};
use crate::core::AgpmError;
pub struct ConstraintResolver {
constraints: HashMap<String, ConstraintSet>,
}
impl Default for ConstraintResolver {
fn default() -> Self {
Self::new()
}
}
impl ConstraintResolver {
#[must_use]
pub fn new() -> Self {
Self {
constraints: HashMap::new(),
}
}
pub fn add_constraint(&mut self, dependency: &str, constraint: &str) -> Result<()> {
let parsed = VersionConstraint::parse(constraint)?;
self.constraints.entry(dependency.to_string()).or_default().add(parsed)?;
Ok(())
}
pub fn resolve(
&self,
available_versions: &HashMap<String, Vec<Version>>,
) -> Result<HashMap<String, Version>> {
let mut resolved = HashMap::new();
for (dep, constraint_set) in &self.constraints {
let versions = available_versions.get(dep).ok_or_else(|| AgpmError::Other {
message: format!("No versions available for dependency: {dep}"),
})?;
let best_match =
constraint_set.find_best_match(versions).ok_or_else(|| AgpmError::Other {
message: format!("No version satisfies constraints for dependency: {dep}"),
})?;
resolved.insert(dep.clone(), best_match.clone());
}
Ok(resolved)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn test_constraint_resolver() {
let mut resolver = ConstraintResolver::new();
resolver.add_constraint("dep1", "^1.0.0").unwrap();
resolver.add_constraint("dep2", "~2.1.0").unwrap();
let mut available = HashMap::new();
available.insert(
"dep1".to_string(),
vec![
Version::parse("0.9.0").unwrap(),
Version::parse("1.0.0").unwrap(),
Version::parse("1.5.0").unwrap(),
Version::parse("2.0.0").unwrap(),
],
);
available.insert(
"dep2".to_string(),
vec![
Version::parse("2.0.0").unwrap(),
Version::parse("2.1.0").unwrap(),
Version::parse("2.1.5").unwrap(),
Version::parse("2.2.0").unwrap(),
],
);
let resolved = resolver.resolve(&available).unwrap();
assert_eq!(resolved.get("dep1"), Some(&Version::parse("1.5.0").unwrap()));
assert_eq!(resolved.get("dep2"), Some(&Version::parse("2.1.5").unwrap()));
}
#[test]
fn test_constraint_resolver_missing_dependency() {
let mut resolver = ConstraintResolver::new();
resolver.add_constraint("dep1", "^1.0.0").unwrap();
let available = HashMap::new();
let result = resolver.resolve(&available);
assert!(result.is_err());
}
#[test]
fn test_constraint_resolver_no_satisfying_version() {
let mut resolver = ConstraintResolver::new();
resolver.add_constraint("dep1", "^2.0.0").unwrap();
let mut available = HashMap::new();
available.insert(
"dep1".to_string(),
vec![Version::parse("1.0.0").unwrap()], );
let result = resolver.resolve(&available);
assert!(result.is_err());
}
#[test]
fn test_constraint_resolver_add_constraint_error() {
let mut resolver = ConstraintResolver::new();
resolver.add_constraint("dep1", "1.0.0").unwrap();
let result = resolver.add_constraint("dep1", "2.0.0");
assert!(result.is_err());
}
}