Skip to content

Commit 05a8696

Browse files
committed
document semver
1 parent 48abe88 commit 05a8696

File tree

2 files changed

+103
-5
lines changed

2 files changed

+103
-5
lines changed

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
mod graph;
22
mod path;
3-
mod semver;
3+
pub mod semver;
44
mod trampoline;
55

66
pub use graph::*;

src/semver.rs

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,60 @@
1+
//! A specialized map for semantic versions with alternate version lookup support.
2+
//!
3+
//! This module provides `VersionMap<T>`, which stores values indexed by semantic versions
4+
//! and supports fallback lookups through version alternates (e.g., 1.2.3 can be found
5+
//! via 1.0.0 if it's the latest patch for major version 1).
6+
17
use derivative::Derivative;
28
use semver::Version;
39
use std::collections::{BTreeMap, BTreeSet, HashMap};
410

11+
/// A map that stores values indexed by semantic versions with support for alternate lookups.
12+
///
13+
/// The `VersionMap` maintains a primary mapping from versions to values, and a secondary
14+
/// mapping that groups versions by their "alternate" keys for fallback lookups.
15+
///
16+
/// # Alternate Lookup Logic
17+
///
18+
/// - For major versions > 0: alternate is `major.0.0`
19+
/// - For minor versions > 0 (when major is 0): alternate is `0.minor.0`
20+
/// - Otherwise: alternate is `0.0.patch`
21+
/// - Pre-release versions have no alternates
22+
///
23+
/// # Example
24+
///
25+
/// ```rust
26+
/// use semver::Version;
27+
/// # use wac_trampoline::semver::VersionMap;
28+
///
29+
/// let mut map = VersionMap::new();
30+
/// map.insert(Version::new(1, 0, 1), "v1.0.1");
31+
/// map.insert(Version::new(1, 2, 0), "v1.2.0");
32+
///
33+
/// // Exact lookups
34+
/// assert_eq!(map.get_exact(&Version::new(1, 0, 1)), Some(&"v1.0.1"));
35+
///
36+
/// // Alternate lookups (finds latest patch for major version 1)
37+
/// assert_eq!(map.get(&Version::new(1, 0, 0)), Some(&"v1.2.0"));
38+
/// ```
539
#[derive(Clone, Derivative, Debug)]
640
#[derivative(Default(bound = ""))]
741
pub struct VersionMap<T> {
42+
/// Primary storage mapping versions to values
843
versions: BTreeMap<Version, T>,
44+
/// Secondary mapping for alternate version lookups
945
alternates: HashMap<Version, BTreeSet<Version>>,
1046
}
1147

1248
impl<T> VersionMap<T> {
49+
/// Creates a new empty `VersionMap`.
1350
pub fn new() -> Self {
1451
Self {
1552
versions: BTreeMap::new(),
1653
alternates: HashMap::new(),
1754
}
1855
}
1956

57+
/// Attempts to insert a version-value pair, returning an error if the version already exists.
2058
pub fn try_insert(&mut self, version: Version, value: T) -> Result<(), (Version, T)> {
2159
if self.versions.contains_key(&version) {
2260
return Err((version, value));
@@ -34,6 +72,9 @@ impl<T> VersionMap<T> {
3472
Ok(())
3573
}
3674

75+
/// Inserts a version-value pair, returning the previous value if the version existed.
76+
///
77+
/// Updates the alternates mapping appropriately.
3778
pub fn insert(&mut self, version: Version, value: T) -> Option<T> {
3879
if let Some(alternate) = version_alternate(&version) {
3980
self.alternates
@@ -45,6 +86,26 @@ impl<T> VersionMap<T> {
4586
self.versions.insert(version, value)
4687
}
4788

89+
/// Gets a value by version, using alternate lookup if exact match is not found.
90+
/// # Examples
91+
///
92+
/// ```rust
93+
/// use semver::Version;
94+
/// # use wac_trampoline::semver::VersionMap;
95+
///
96+
/// let mut map = VersionMap::new();
97+
/// map.insert(Version::new(0, 0, 1), "v0.0.9");
98+
/// map.insert(Version::new(0, 1, 1), "v0.1.1");
99+
/// map.insert(Version::new(1, 2, 0), "v1.2.1");
100+
///
101+
/// // Get latest patch
102+
/// assert_eq!(map.get(&Version::new(0, 0, 1)), Some(&"v0.0.9"));
103+
///
104+
/// // Get latest minor
105+
/// assert_eq!(map.get(&Version::new(0, 1, 0)), Some(&"v0.1.1"));
106+
///
107+
/// // Get latest major
108+
/// assert_eq!(map.get(&Version::new(1, 0, 0)), Some(&"v1.2.1"));
48109
pub fn get(&self, version: &Version) -> Option<&T> {
49110
if version.build.is_empty() {
50111
let maybe_value = version_alternate(version)
@@ -61,18 +122,47 @@ impl<T> VersionMap<T> {
61122
self.get_exact(version)
62123
}
63124

125+
/// Gets a value by version or returns the latest version if no specific version is provided.
126+
///
127+
/// # Examples
128+
///
129+
/// ```rust
130+
/// use semver::Version;
131+
/// # use wac_trampoline::semver::VersionMap;
132+
///
133+
/// let mut map = VersionMap::new();
134+
/// map.insert(Version::new(0, 0, 1), "v0.0.9");
135+
/// map.insert(Version::new(0, 1, 0), "v0.1.0");
136+
/// map.insert(Version::new(0, 1, 1), "v0.1.1");
137+
/// map.insert(Version::new(0, 5, 1), "v0.5.1");
138+
/// map.insert(Version::new(1, 0, 0), "v1.0.0");
139+
/// map.insert(Version::new(1, 2, 0), "v1.2.0");
140+
///
141+
/// // Get latest patch
142+
/// assert_eq!(map.get_or_latest(Some(&Version::new(0, 0, 1))), Some(&"v0.0.9"));
143+
///
144+
/// // Get latest minor
145+
/// assert_eq!(map.get_or_latest(Some(&Version::new(0, 1, 0))), Some(&"v0.1.1"));
146+
///
147+
/// // Get latest major
148+
/// assert_eq!(map.get_or_latest(Some(&Version::new(1, 0, 0))), Some(&"v1.2.0"));
149+
///
150+
/// // Get the latest version
151+
/// assert_eq!(map.get_or_latest(None), Some(&"v1.2.0"));
152+
/// ```
64153
pub fn get_or_latest(&self, version: Option<&Version>) -> Option<&T> {
65-
if let Some(version) = version {
66-
self.get(version)
67-
} else {
68-
self.get_latest().map(|(_, value)| value)
154+
match version {
155+
Some(v) => self.get(v),
156+
None => self.get_latest().map(|(_, value)| value),
69157
}
70158
}
71159

160+
/// Returns the latest version and its associated value.
72161
pub fn get_latest(&self) -> Option<(&Version, &T)> {
73162
self.versions.last_key_value()
74163
}
75164

165+
/// Gets a value by exact version match only, without alternate lookup.
76166
pub fn get_exact(&self, version: &Version) -> Option<&T> {
77167
self.versions.get(version)
78168
}
@@ -91,7 +181,15 @@ impl<T> VersionMap<T> {
91181
}
92182
}
93183

184+
/// Computes the alternate version key for fallback lookups.
185+
///
186+
/// This function implements the alternate lookup logic:
187+
/// - Pre-release versions return `None` (no alternates)
188+
/// - Major versions > 0: return `major.0.0`
189+
/// - Minor versions > 0 (when major is 0): return `0.minor.0`
190+
/// - Otherwise: return `0.0.patch`
94191
fn version_alternate(version: &Version) -> Option<Version> {
192+
// Pre-release versions don't have alternates
95193
if !version.pre.is_empty() {
96194
None
97195
} else if version.major > 0 {

0 commit comments

Comments
 (0)