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+
17use derivative:: Derivative ;
28use semver:: Version ;
39use 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 = "" ) ) ]
741pub 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
1248impl < 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`
94191fn 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