1use serde::{Deserialize, Serialize};
4use std::fmt;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct PackageVersion {
9 pub major: u32,
11 pub minor: u32,
13 pub patch: u32,
15 pub pre_release: Option<String>,
17 pub build_metadata: Option<String>,
19}
20
21impl PackageVersion {
22 pub fn new(major: u32, minor: u32, patch: u32) -> Self {
24 Self {
25 major,
26 minor,
27 patch,
28 pre_release: None,
29 build_metadata: None,
30 }
31 }
32
33 pub fn parse(version: &str) -> Result<Self, String> {
35 let semver = semver::Version::parse(version)
36 .map_err(|e| format!("Invalid version format: {}", e))?;
37
38 Ok(Self {
39 major: semver.major as u32,
40 minor: semver.minor as u32,
41 patch: semver.patch as u32,
42 pre_release: if semver.pre.is_empty() {
43 None
44 } else {
45 Some(semver.pre.to_string())
46 },
47 build_metadata: if semver.build.is_empty() {
48 None
49 } else {
50 Some(semver.build.to_string())
51 },
52 })
53 }
54
55 pub fn is_compatible_with(&self, requirement: &VersionRequirement) -> bool {
57 requirement.matches(self)
58 }
59
60 pub fn next_major(&self) -> Self {
62 Self::new(self.major + 1, 0, 0)
63 }
64
65 pub fn next_minor(&self) -> Self {
67 Self::new(self.major, self.minor + 1, 0)
68 }
69
70 pub fn next_patch(&self) -> Self {
72 Self::new(self.major, self.minor, self.patch + 1)
73 }
74
75 pub fn is_pre_release(&self) -> bool {
77 self.pre_release.is_some()
78 }
79}
80
81impl PartialEq for PackageVersion {
82 fn eq(&self, other: &Self) -> bool {
83 self.major == other.major
85 && self.minor == other.minor
86 && self.patch == other.patch
87 && self.pre_release == other.pre_release
88 }
90}
91
92impl Eq for PackageVersion {}
93
94impl std::cmp::PartialOrd for PackageVersion {
95 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
96 Some(self.cmp(other))
97 }
98}
99
100impl std::cmp::Ord for PackageVersion {
101 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
102 use std::cmp::Ordering;
103
104 match (self.major, self.minor, self.patch).cmp(&(other.major, other.minor, other.patch)) {
106 Ordering::Equal => {
107 match (&self.pre_release, &other.pre_release) {
109 (None, None) => Ordering::Equal,
110 (None, Some(_)) => Ordering::Greater, (Some(_), None) => Ordering::Less, (Some(a), Some(b)) => a.cmp(b), }
114 }
116 other_ordering => other_ordering,
117 }
118 }
119}
120
121impl fmt::Display for PackageVersion {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
124
125 if let Some(pre) = &self.pre_release {
126 write!(f, "-{}", pre)?;
127 }
128
129 if let Some(build) = &self.build_metadata {
130 write!(f, "+{}", build)?;
131 }
132
133 Ok(())
134 }
135}
136
137impl From<semver::Version> for PackageVersion {
138 fn from(v: semver::Version) -> Self {
139 Self {
140 major: v.major as u32,
141 minor: v.minor as u32,
142 patch: v.patch as u32,
143 pre_release: if v.pre.is_empty() {
144 None
145 } else {
146 Some(v.pre.to_string())
147 },
148 build_metadata: if v.build.is_empty() {
149 None
150 } else {
151 Some(v.build.to_string())
152 },
153 }
154 }
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct VersionRequirement {
160 pub comparator: VersionComparator,
162 pub version: PackageVersion,
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
168pub enum VersionComparator {
169 Exact,
171 GreaterThan,
173 GreaterThanOrEqual,
175 LessThan,
177 LessThanOrEqual,
179 Compatible,
181}
182
183impl VersionRequirement {
184 pub fn new(comparator: VersionComparator, version: PackageVersion) -> Self {
186 Self {
187 comparator,
188 version,
189 }
190 }
191
192 pub fn parse(req: &str) -> Result<Self, String> {
194 let req = req.trim();
195
196 if req.is_empty() {
197 return Err("Empty version requirement".to_string());
198 }
199
200 if let Some(version_str) = req.strip_prefix("^") {
202 let version = PackageVersion::parse(version_str)?;
204 Ok(Self::new(VersionComparator::Compatible, version))
205 } else if let Some(version_str) = req.strip_prefix(">=") {
206 let version = PackageVersion::parse(version_str)?;
207 Ok(Self::new(VersionComparator::GreaterThanOrEqual, version))
208 } else if let Some(version_str) = req.strip_prefix(">") {
209 let version = PackageVersion::parse(version_str)?;
210 Ok(Self::new(VersionComparator::GreaterThan, version))
211 } else if let Some(version_str) = req.strip_prefix("<=") {
212 let version = PackageVersion::parse(version_str)?;
213 Ok(Self::new(VersionComparator::LessThanOrEqual, version))
214 } else if let Some(version_str) = req.strip_prefix("<") {
215 let version = PackageVersion::parse(version_str)?;
216 Ok(Self::new(VersionComparator::LessThan, version))
217 } else if let Some(version_str) = req.strip_prefix("=") {
218 let version = PackageVersion::parse(version_str)?;
219 Ok(Self::new(VersionComparator::Exact, version))
220 } else {
221 let version = PackageVersion::parse(req)?;
223 Ok(Self::new(VersionComparator::Exact, version))
224 }
225 }
226
227 pub fn matches(&self, version: &PackageVersion) -> bool {
229 match self.comparator {
230 VersionComparator::Exact => version == &self.version,
231 VersionComparator::GreaterThan => version > &self.version,
232 VersionComparator::GreaterThanOrEqual => version >= &self.version,
233 VersionComparator::LessThan => version < &self.version,
234 VersionComparator::LessThanOrEqual => version <= &self.version,
235 VersionComparator::Compatible => {
236 version.major == self.version.major
237 && (version.minor > self.version.minor
238 || (version.minor == self.version.minor
239 && version.patch >= self.version.patch))
240 }
241 }
242 }
243}
244
245impl fmt::Display for VersionRequirement {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 let prefix = match self.comparator {
248 VersionComparator::Exact => "=",
249 VersionComparator::GreaterThan => ">",
250 VersionComparator::GreaterThanOrEqual => ">=",
251 VersionComparator::LessThan => "<",
252 VersionComparator::LessThanOrEqual => "<=",
253 VersionComparator::Compatible => "^",
254 };
255
256 write!(f, "{}{}", prefix, self.version)
257 }
258}
259
260pub struct CompatibilityChecker {
262 requirements: Vec<VersionRequirement>,
263}
264
265impl CompatibilityChecker {
266 pub fn new() -> Self {
268 Self {
269 requirements: Vec::new(),
270 }
271 }
272
273 pub fn add_requirement(&mut self, requirement: VersionRequirement) {
275 self.requirements.push(requirement);
276 }
277
278 pub fn check(&self, version: &PackageVersion) -> bool {
280 self.requirements.iter().all(|req| req.matches(version))
281 }
282
283 pub fn requirements(&self) -> &[VersionRequirement] {
285 &self.requirements
286 }
287}
288
289impl Default for CompatibilityChecker {
290 fn default() -> Self {
291 Self::new()
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 #[test]
300 fn test_version_parsing() {
301 let v1 = PackageVersion::parse("1.2.3").expect("Failed to parse version in test");
302 assert_eq!(v1.major, 1);
303 assert_eq!(v1.minor, 2);
304 assert_eq!(v1.patch, 3);
305
306 let v2 = PackageVersion::parse("2.0.0-alpha.1").expect("Failed to parse version in test");
307 assert_eq!(v2.major, 2);
308 assert_eq!(v2.minor, 0);
309 assert_eq!(v2.patch, 0);
310 assert_eq!(v2.pre_release.as_deref(), Some("alpha.1"));
311 }
312
313 #[test]
314 fn test_version_comparison() {
315 let v1 = PackageVersion::new(1, 2, 3);
316 let v2 = PackageVersion::new(1, 2, 4);
317 let v3 = PackageVersion::new(1, 3, 0);
318 let v4 = PackageVersion::new(2, 0, 0);
319
320 assert!(v1 < v2);
321 assert!(v2 < v3);
322 assert!(v3 < v4);
323 }
324
325 #[test]
326 fn test_version_requirement() {
327 let req = VersionRequirement::parse("^1.2.3").expect("Failed to parse version in test");
328
329 assert!(req.matches(&PackageVersion::new(1, 2, 3)));
330 assert!(req.matches(&PackageVersion::new(1, 2, 4)));
331 assert!(req.matches(&PackageVersion::new(1, 3, 0)));
332 assert!(!req.matches(&PackageVersion::new(1, 2, 2)));
333 assert!(!req.matches(&PackageVersion::new(2, 0, 0)));
334
335 let req2 = VersionRequirement::parse(">=1.2.0").expect("Failed to parse version in test");
336 assert!(req2.matches(&PackageVersion::new(1, 2, 0)));
337 assert!(req2.matches(&PackageVersion::new(1, 3, 0)));
338 assert!(req2.matches(&PackageVersion::new(2, 0, 0)));
339 assert!(!req2.matches(&PackageVersion::new(1, 1, 9)));
340 }
341
342 #[test]
343 fn test_compatibility_checker() {
344 let mut checker = CompatibilityChecker::new();
345 checker.add_requirement(
346 VersionRequirement::parse(">=1.2.0").expect("Failed to parse version in test"),
347 );
348 checker.add_requirement(
349 VersionRequirement::parse("<2.0.0").expect("Failed to parse version in test"),
350 );
351
352 assert!(checker.check(&PackageVersion::new(1, 2, 0)));
353 assert!(checker.check(&PackageVersion::new(1, 5, 3)));
354 assert!(!checker.check(&PackageVersion::new(1, 1, 9)));
355 assert!(!checker.check(&PackageVersion::new(2, 0, 0)));
356 }
357}