VersionResolver

Struct VersionResolver 

Source
pub struct VersionResolver { /* private fields */ }
Expand description

Resolves semantic versions from Git repository tags.

VersionResolver provides the core functionality for discovering, parsing, and resolving semantic versions from Git tags. It handles tag discovery, version parsing, constraint matching, and best-version selection.

§Tag Processing

The resolver automatically:

  • Fetches all tags from a Git repository
  • Normalizes tag names (removes v prefixes, handles common formats)
  • Parses valid semantic versions (skips invalid tags)
  • Sorts versions in descending order (newest first)
  • Categorizes versions as stable or prerelease

§Resolution Strategy

When resolving version constraints:

  1. Special keywords are handled first (latest, latest-prerelease)
  2. Exact versions are matched with or without v prefixes
  3. Semantic ranges are applied using semver matching rules
  4. Tag names are matched exactly as fallback
  5. Prerelease filtering is applied based on constraint type

§Examples

§Creating from Git Repository

use ccpm::version::VersionResolver;
use ccpm::git::GitRepo;
use std::path::PathBuf;

let repo = GitRepo::new(PathBuf::from("/path/to/repo"));
let resolver = VersionResolver::from_git_tags(&repo).await?;

println!("Found {} versions", resolver.list_all().len());

§Version Resolution


// Resolve various constraint types
if let Some(version) = resolver.resolve("^1.0.0")? {
    println!("Caret range resolved to: {} ({})", version.tag, version.version);
}

if let Some(version) = resolver.resolve("latest")? {
    println!("Latest stable: {} (prerelease: {})", version.tag, version.prerelease);
}

if let Some(version) = resolver.resolve("v1.2.3")? {
    println!("Exact match: {}", version.tag);
}

Implementations§

Source§

impl VersionResolver

Source

pub fn new() -> Self

Create a new empty resolver with no versions.

This constructor creates an empty resolver that contains no version information. It’s primarily useful for testing or as a starting point before adding versions manually. For normal usage, prefer from_git_tags which populates the resolver from a Git repository.

§Examples
use ccpm::version::VersionResolver;

let resolver = VersionResolver::new();
assert_eq!(resolver.list_all().len(), 0);
assert!(resolver.get_latest().is_none());
Source

pub async fn from_git_tags(repo: &GitRepo) -> Result<Self>

Create a resolver by discovering and parsing tags from a Git repository.

This method performs the complete tag discovery and parsing workflow:

  1. Fetch tags: Retrieve all Git tags from the repository
  2. Parse versions: Attempt to parse each tag as a semantic version
  3. Filter valid: Keep only tags that parse successfully
  4. Sort versions: Order by semantic version (newest first)
  5. Detect prereleases: Identify versions with prerelease components
§Arguments
  • repo - The GitRepo instance to discover tags from
§Returns

Returns Ok(VersionResolver) with parsed versions, or Err if Git operations fail. Individual tag parsing failures are silently ignored.

§Tag Parsing Rules
  • Common prefixes (v, V) are automatically stripped
  • Invalid semantic versions are skipped (not included in resolver)
  • Valid versions are sorted in descending order
  • Prerelease status is detected from version components
§Examples
use ccpm::version::VersionResolver;
use ccpm::git::GitRepo;
use std::path::PathBuf;

let repo = GitRepo::new(PathBuf::from("/path/to/repo"));
let resolver = VersionResolver::from_git_tags(&repo).await?;

println!("Discovered {} valid versions", resolver.list_all().len());

if let Some(latest) = resolver.get_latest() {
    println!("Latest version: {} ({})", latest.tag, latest.version);
}
§Error Handling

This method returns errors for Git operations (repository access, tag listing) but handles individual tag parsing failures gracefully by skipping invalid tags.

Source

pub fn resolve(&self, requirement: &str) -> Result<Option<VersionInfo>>

Resolve a version requirement string to a specific version from available tags.

This method applies version constraint logic to find the best matching version from the resolver’s collection of parsed Git tags. It supports various constraint formats and applies appropriate matching rules for each type.

§Constraint Resolution Order
  1. Special keywords: "latest", "latest-prerelease" are handled first
  2. Exact versions: Direct semantic version matches (with/without v prefix)
  3. Version requirements: Semver ranges like "^1.0.0", "~1.2.0"
  4. Tag names: Exact tag string matching as fallback
§Arguments
  • requirement - The version constraint string to resolve
§Returns

Returns Ok(Some(VersionInfo)) if a matching version is found, Ok(None) if no version satisfies the requirement, or Err for invalid requirements.

§Prerelease Handling
  • Default behavior: Prereleases are excluded from semver range matching
  • Explicit inclusion: Use "latest-prerelease" to include prereleases
  • Exact matches: Direct version/tag matches include prereleases
§Examples
use ccpm::version::VersionResolver;
use ccpm::git::GitRepo;
use std::path::PathBuf;

let repo = GitRepo::new(PathBuf::from("/path/to/repo"));
let resolver = VersionResolver::from_git_tags(&repo).await?;

// Special keywords
if let Some(version) = resolver.resolve("latest")? {
    println!("Latest stable: {}", version.tag);
}

// Exact version matching
if let Some(version) = resolver.resolve("1.2.3")? {
    println!("Found exact version: {}", version.tag);
}

// Semver ranges
if let Some(version) = resolver.resolve("^1.0.0")? {
    println!("Compatible version: {} ({})", version.tag, version.version);
}

// Tag name matching
if let Some(version) = resolver.resolve("v1.0.0-beta.1")? {
    println!("Tag match: {}", version.tag);
}
§Resolution Precedence

When multiple versions could match:

  • Highest version wins: Newer semantic versions are preferred
  • Stable over prerelease: Stable versions preferred unless prereleases explicitly allowed
  • First match for tags: Tag name matching returns the first occurrence
Source

pub fn get_latest(&self) -> Option<VersionInfo>

Get the latest version including prereleases.

This method returns the absolute newest version from the resolver’s collection, including prerelease versions. Since versions are sorted in descending order, this simply returns the first version in the list.

§Returns

Returns Some(VersionInfo) with the highest version, or None if no versions are available in the resolver.

§Prerelease Inclusion

Unlike get_latest_stable, this method includes prerelease versions in consideration. If the highest version happens to be a prerelease (e.g., 2.0.0-beta.1 when 1.9.0 is the latest stable), the prerelease version will be returned.

§Examples
use ccpm::version::VersionResolver;
use ccpm::git::GitRepo;
use std::path::PathBuf;

let repo = GitRepo::new(PathBuf::from("/path/to/repo"));
let resolver = VersionResolver::from_git_tags(&repo).await?;

if let Some(latest) = resolver.get_latest() {
    println!("Absolute latest: {} (prerelease: {})",
             latest.tag, latest.prerelease);
} else {
    println!("No versions found in repository");
}
§Use Cases

This method is useful when:

  • You want the cutting-edge version regardless of stability
  • Implementing latest-prerelease constraint resolution
  • Analyzing the most recent development activity
Source

pub fn get_latest_stable(&self) -> Option<VersionInfo>

Get the latest stable version excluding prereleases.

This method finds the newest version that doesn’t contain prerelease identifiers (such as -alpha, -beta, -rc). It’s the preferred method for production environments where stability is prioritized over cutting-edge features.

§Returns

Returns Some(VersionInfo) with the highest stable version, or None if no stable versions are available (only prereleases exist).

§Stability Definition

A version is considered stable if its prerelease component is empty. This means:

  • 1.0.0 is stable
  • 1.0.0-beta.1 is not stable (has prerelease suffix)
  • 1.0.0+build.123 is stable (build metadata doesn’t affect stability)
§Examples
use ccpm::version::VersionResolver;
use ccpm::git::GitRepo;
use std::path::PathBuf;

let repo = GitRepo::new(PathBuf::from("/path/to/repo"));
let resolver = VersionResolver::from_git_tags(&repo).await?;

match resolver.get_latest_stable() {
    Some(stable) => {
        println!("Latest stable version: {}", stable.tag);
        assert!(!stable.prerelease); // Always false for stable versions
    }
    None => println!("No stable versions found (only prereleases available)"),
}
§Comparison with get_latest()

let latest = resolver.get_latest();
let stable = resolver.get_latest_stable();

// Latest might be a prerelease version
// Stable will always be a non-prerelease version (or None)

if let (Some(l), Some(s)) = (latest, stable) {
    if l.version > s.version {
        println!("Newest version {} is a prerelease", l.tag);
        println!("Latest stable version is {}", s.tag);
    }
}
§Use Cases

This method is ideal for:

  • Production dependency resolution
  • Implementing "latest" constraint resolution
  • Default version selection in package managers
  • Stable release identification
Source

pub fn list_all(&self) -> Vec<VersionInfo>

List all versions discovered from Git tags.

This method returns a complete list of all successfully parsed versions from the Git repository, including both stable and prerelease versions. The list is sorted in descending order by semantic version (newest first).

§Returns

Returns Vec<VersionInfo> containing all parsed versions. The vector may be empty if no valid semantic versions were found in the repository tags.

§Sorting Order

Versions are sorted by semantic version precedence in descending order:

  • Higher major versions first (e.g., 2.0.0 before 1.9.0)
  • Higher minor versions within same major (e.g., 1.5.0 before 1.2.0)
  • Higher patch versions within same minor (e.g., 1.2.3 before 1.2.1)
  • Release versions before prereleases (e.g., 1.0.0 before 1.0.0-beta.1)
§Examples
use ccpm::version::VersionResolver;
use ccpm::git::GitRepo;
use std::path::PathBuf;

let repo = GitRepo::new(PathBuf::from("/path/to/repo"));
let resolver = VersionResolver::from_git_tags(&repo).await?;

let all_versions = resolver.list_all();
println!("Found {} versions:", all_versions.len());

for (i, version) in all_versions.iter().enumerate() {
    let status = if version.prerelease { "prerelease" } else { "stable" };
    println!("  {}. {} ({}) - {}", i + 1, version.tag, version.version, status);
}
§Filtering and Analysis

let all_versions = resolver.list_all();

// Count prereleases vs stable
let prerelease_count = all_versions.iter().filter(|v| v.prerelease).count();
let stable_count = all_versions.len() - prerelease_count;

println!("Stable versions: {}, Prereleases: {}", stable_count, prerelease_count);

// Find versions in a specific range
let v1_versions: Vec<_> = all_versions.iter()
    .filter(|v| v.version.major == 1)
    .collect();
println!("Found {} versions in v1.x.x series", v1_versions.len());
§Use Cases

This method is useful for:

  • Version analysis and reporting
  • Building version selection interfaces
  • Debugging version resolution issues
  • Implementing custom constraint logic
Source

pub fn list_stable(&self) -> Vec<VersionInfo>

List only stable versions excluding prereleases.

This method filters the complete version list to include only versions without prerelease components. It’s useful for scenarios where you need to work with production-ready versions only.

§Returns

Returns Vec<VersionInfo> containing only stable versions, sorted in descending order. The vector may be empty if no stable versions exist (only prereleases).

§Filtering Criteria

A version is included if:

  • Its prerelease component is empty (no -alpha, -beta, -rc suffixes)
  • It parses as a valid semantic version
  • It was successfully extracted from a Git tag
§Examples
use ccpm::version::VersionResolver;
use ccpm::git::GitRepo;
use std::path::PathBuf;

let repo = GitRepo::new(PathBuf::from("/path/to/repo"));
let resolver = VersionResolver::from_git_tags(&repo).await?;

let stable_versions = resolver.list_stable();
println!("Found {} stable versions:", stable_versions.len());

for version in stable_versions {
    println!("  {} ({})", version.tag, version.version);
    assert!(!version.prerelease); // Guaranteed to be false
}
§Comparison with All Versions

let all_versions = resolver.list_all();
let stable_versions = resolver.list_stable();

println!("Total versions: {}", all_versions.len());
println!("Stable versions: {}", stable_versions.len());
println!("Prerelease versions: {}", all_versions.len() - stable_versions.len());

if stable_versions.len() < all_versions.len() {
    println!("Repository contains prerelease versions");
}
§Use Cases

This method is particularly useful for:

  • Production environment version selection
  • Conservative update strategies
  • Compliance requirements that exclude prereleases
  • User interfaces that hide development versions by default
Source

pub fn has_version(&self, version: &str) -> bool

Check if a specific version constraint can be resolved.

This method tests whether a given version constraint string can be successfully resolved against the available versions in this resolver. It’s a convenience method that combines resolution and existence checking.

§Arguments
  • version - The version constraint string to test
§Returns

Returns true if the version constraint resolves to an actual version, false if no matching version is found or if resolution fails.

§Resolution Types Tested

This method can verify existence of:

  • Exact versions: "1.0.0", "v1.2.3"
  • Version ranges: "^1.0.0", "~1.2.0", ">=1.0.0"
  • Special keywords: "latest", "latest-prerelease"
  • Tag names: Exact Git tag matches
§Examples
use ccpm::version::VersionResolver;
use ccpm::git::GitRepo;
use std::path::PathBuf;

let repo = GitRepo::new(PathBuf::from("/path/to/repo"));
let resolver = VersionResolver::from_git_tags(&repo).await?;

// Check if specific versions exist
if resolver.has_version("1.0.0") {
    println!("Version 1.0.0 is available");
}

if resolver.has_version("^1.0.0") {
    println!("Compatible versions with 1.0.0 exist");
}

if resolver.has_version("latest") {
    println!("At least one stable version exists");
}

// This will likely return false unless you have this exact tag
if resolver.has_version("v99.99.99") {
    println!("Unlikely version found!");
} else {
    println!("Version 99.99.99 not found (as expected)");
}
§Validation Before Resolution

let constraint = "^2.0.0";

if resolver.has_version(constraint) {
    // Safe to resolve - we know it will succeed
    let version = resolver.resolve(constraint)?.unwrap();
    println!("Resolved {} to {}", constraint, version.tag);
} else {
    println!("No versions satisfy constraint: {}", constraint);
}
§Error Handling

This method handles resolution errors gracefully by returning false rather than propagating errors. This makes it safe to use for validation without extensive error handling.

§Use Cases

This method is useful for:

  • Validating user input before processing
  • Pre-flight checks in dependency resolution
  • Conditional logic based on version availability
  • User interface validation and feedback

Trait Implementations§

Source§

impl Default for VersionResolver

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

impl<T> ErasedDestructor for T
where T: 'static,