pub struct ConstraintResolver { /* private fields */ }Expand description
Manages version constraints for multiple dependencies and resolves them simultaneously.
ConstraintResolver coordinates version resolution across an entire dependency graph,
ensuring that all constraints are satisfied and conflicts are detected. It maintains
separate ConstraintSets for each dependency and resolves them against available
version catalogs.
§Multi-Dependency Resolution
Unlike ConstraintSet which manages constraints for a single dependency,
ConstraintResolver handles multiple dependencies simultaneously:
- Each dependency gets its own constraint set
- Constraints can be added incrementally
- Resolution happens across the entire dependency graph
- Missing dependencies are detected and reported
§Resolution Process
- Collect constraints: Gather all constraints for each dependency
- Validate availability: Ensure versions exist for all dependencies
- Apply constraint sets: Use each dependency’s constraints to filter versions
- Select best matches: Choose optimal versions for each dependency
- Return resolution map: Provide final version selections
§Examples
§Basic Multi-Dependency Resolution
use agpm_cli::version::constraints::ConstraintResolver;
use semver::Version;
use std::collections::HashMap;
let mut resolver = ConstraintResolver::new();
// Add constraints for multiple dependencies
resolver.add_constraint("dep1", "^1.0.0")?;
resolver.add_constraint("dep2", "~2.1.0")?;
resolver.add_constraint("dep3", "main")?;
// Provide available versions for each dependency
let mut available = HashMap::new();
available.insert("dep1".to_string(), vec![Version::parse("1.5.0")?]);
available.insert("dep2".to_string(), vec![Version::parse("2.1.3")?]);
available.insert("dep3".to_string(), vec![Version::parse("3.0.0")?]);
// Resolve all dependencies
let resolved = resolver.resolve(&available)?;
assert_eq!(resolved.len(), 3);§Incremental Constraint Addition
use agpm_cli::version::constraints::ConstraintResolver;
let mut resolver = ConstraintResolver::new();
// Add multiple constraints for the same dependency
resolver.add_constraint("my-dep", ">=1.0.0")?;
resolver.add_constraint("my-dep", "<2.0.0")?;
resolver.add_constraint("my-dep", "^1.5.0")?;
// All constraints will be combined into a single constraint set§Error Conditions
The resolver reports several types of errors:
- Missing dependencies: A constraint exists but no versions are available
- Unsatisfiable constraints: No available version meets all requirements
- Conflicting constraints: Impossible constraint combinations
§Use Cases
This resolver is particularly useful for:
- Package managers resolving dependency graphs
- Build systems selecting compatible versions
- Configuration management ensuring consistent environments
- Update analysis determining safe upgrade paths
Implementations§
Source§impl ConstraintResolver
impl ConstraintResolver
Sourcepub fn new() -> Self
pub fn new() -> Self
Creates a new constraint resolver
§Returns
Returns a new ConstraintResolver with empty constraint and resolution maps
Sourcepub fn add_constraint(
&mut self,
dependency: &str,
constraint: &str,
) -> Result<()>
pub fn add_constraint( &mut self, dependency: &str, constraint: &str, ) -> Result<()>
Add a version constraint for a specific dependency.
This method parses constraint string and adds it to the constraint set for the named dependency. If this is the first constraint for the dependency, a new constraint set is created. Multiple constraints for the same dependency are combined into a single set with conflict detection.
§Arguments
dependency- The name of the dependency to constrainconstraint- The constraint string to parse and add (e.g., “^1.0.0”, “latest”)
§Returns
Returns Ok(()) if the constraint was added successfully, or Err if:
- The constraint string is invalid
- The constraint conflicts with existing constraints for this dependency
§Examples
use agpm_cli::version::constraints::ConstraintResolver;
let mut resolver = ConstraintResolver::new();
// Add constraints for different dependencies
resolver.add_constraint("web-framework", "^2.0.0")?;
resolver.add_constraint("database", "~1.5.0")?;
resolver.add_constraint("auth-lib", "main")?;
// Add multiple constraints for the same dependency
resolver.add_constraint("api-client", ">=1.0.0")?;
resolver.add_constraint("api-client", "<2.0.0")?; // Compatible range
// This would fail - conflicting exact versions
resolver.add_constraint("my-dep", "1.0.0")?;
let result = resolver.add_constraint("my-dep", "2.0.0");
assert!(result.is_err());§Constraint Combination
When multiple constraints are added for the same dependency, they are combined using AND logic. The final constraint set requires that all individual constraints be satisfied simultaneously.
Sourcepub fn resolve(
&self,
available_versions: &HashMap<String, Vec<Version>>,
) -> Result<HashMap<String, Version>>
pub fn resolve( &self, available_versions: &HashMap<String, Vec<Version>>, ) -> Result<HashMap<String, Version>>
Resolve all dependency constraints and return the best version for each.
This method performs the core resolution algorithm, taking all accumulated constraints and finding the best matching version for each dependency from the provided catalog of available versions.
§Resolution Algorithm
For each dependency with constraints:
- Verify availability: Check that versions exist for the dependency
- Apply constraints: Filter versions using the dependency’s constraint set
- Select best match: Choose the highest compatible version
- Handle prereleases: Apply prerelease policies appropriately
§Arguments
available_versions- Map from dependency names to lists of available versions
§Returns
Returns Ok(HashMap<String, Version>) with the resolved version for each
dependency, or Err if resolution fails.
§Error Conditions
- Missing dependency: Constraint exists but no versions are available
- No satisfying version: Available versions don’t meet constraints
- Internal errors: Constraint set conflicts or parsing failures
§Examples
use agpm_cli::version::constraints::ConstraintResolver;
use semver::Version;
use std::collections::HashMap;
let mut resolver = ConstraintResolver::new();
resolver.add_constraint("web-server", "^1.0.0")?;
resolver.add_constraint("database", "~2.1.0")?;
// Provide version catalog
let mut available = HashMap::new();
available.insert(
"web-server".to_string(),
vec![
Version::parse("1.0.0")?,
Version::parse("1.2.0")?,
Version::parse("1.5.0")?, // Best match for ^1.0.0
Version::parse("2.0.0")?, // Too new
],
);
available.insert(
"database".to_string(),
vec![
Version::parse("2.1.0")?,
Version::parse("2.1.3")?, // Best match for ~2.1.0
Version::parse("2.2.0")?, // Too new
],
);
// Resolve dependencies
let resolved = resolver.resolve(&available)?;
assert_eq!(resolved["web-server"], Version::parse("1.5.0")?);
assert_eq!(resolved["database"], Version::parse("2.1.3")?);§Error Handling
use agpm_cli::version::constraints::ConstraintResolver;
use std::collections::HashMap;
let mut resolver = ConstraintResolver::new();
resolver.add_constraint("missing-dep", "^1.0.0")?;
let available = HashMap::new(); // No versions provided
let result = resolver.resolve(&available);
assert!(result.is_err()); // Missing dependency error§Performance Considerations
- Resolution is performed independently for each dependency
- Version filtering and sorting may be expensive for large version lists
- Consider pre-filtering available versions if catalogs are very large
Trait Implementations§
Auto Trait Implementations§
impl Freeze for ConstraintResolver
impl RefUnwindSafe for ConstraintResolver
impl Send for ConstraintResolver
impl Sync for ConstraintResolver
impl Unpin for ConstraintResolver
impl UnwindSafe for ConstraintResolver
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more