ConstraintResolver

Struct ConstraintResolver 

Source
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

  1. Collect constraints: Gather all constraints for each dependency
  2. Validate availability: Ensure versions exist for all dependencies
  3. Apply constraint sets: Use each dependency’s constraints to filter versions
  4. Select best matches: Choose optimal versions for each dependency
  5. 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

Source

pub fn new() -> Self

Creates a new constraint resolver

§Returns

Returns a new ConstraintResolver with empty constraint and resolution maps

Source

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 constrain
  • constraint - 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.

Source

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:

  1. Verify availability: Check that versions exist for the dependency
  2. Apply constraints: Filter versions using the dependency’s constraint set
  3. Select best match: Choose the highest compatible version
  4. 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§

Source§

impl Default for ConstraintResolver

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more