1 // Copyright (C) 2024 The Android Open Source Project 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 use semver::{Version, VersionReq}; 16 17 /// A trait for determining semver compatibility. 18 pub trait IsUpgradableTo { 19 /// Returns true if the object version is semver-compatible with 'other'. is_upgradable_to(&self, other: &Version) -> bool20 fn is_upgradable_to(&self, other: &Version) -> bool; 21 /// Returns true if the object version is semver-compatible with 'other', or if 22 /// both have a major version of 0 and the other version is greater. is_upgradable_to_relaxed(&self, other: &Version) -> bool23 fn is_upgradable_to_relaxed(&self, other: &Version) -> bool; 24 } 25 26 impl IsUpgradableTo for semver::Version { is_upgradable_to(&self, other: &Version) -> bool27 fn is_upgradable_to(&self, other: &Version) -> bool { 28 VersionReq::parse(&self.to_string()).is_ok_and(|req| req.matches(other)) 29 } is_upgradable_to_relaxed(&self, other: &Version) -> bool30 fn is_upgradable_to_relaxed(&self, other: &Version) -> bool { 31 VersionReq::parse(&self.to_string()).is_ok_and(|req| req.matches_relaxed(other)) 32 } 33 } 34 35 /// A trait for relaxed semver compatibility. Major versions of 0 are treated as if they were non-zero. 36 pub trait MatchesRelaxed { 37 /// Returns true if the version matches the req, but treats 38 /// major version of zero as if it were non-zero. matches_relaxed(&self, version: &Version) -> bool39 fn matches_relaxed(&self, version: &Version) -> bool; 40 } 41 impl MatchesRelaxed for VersionReq { matches_relaxed(&self, version: &Version) -> bool42 fn matches_relaxed(&self, version: &Version) -> bool { 43 if self.matches(version) { 44 return true; 45 } 46 if self.comparators.len() == 1 && self.comparators[0].major == 0 && version.major == 0 { 47 let mut fake_v = version.clone(); 48 fake_v.major = 1; 49 let mut fake_req = self.clone(); 50 fake_req.comparators[0].major = 1; 51 return fake_req.matches(&fake_v); 52 } 53 false 54 } 55 } 56 57 #[cfg(test)] 58 mod tests { 59 use super::*; 60 61 use anyhow::Result; 62 63 #[test] test_is_upgradable() -> Result<()>64 fn test_is_upgradable() -> Result<()> { 65 let version = Version::parse("2.3.4")?; 66 let patch = Version::parse("2.3.5")?; 67 let minor = Version::parse("2.4.0")?; 68 let major = Version::parse("3.0.0")?; 69 let older = Version::parse("2.3.3")?; 70 71 // All have same behavior for is_upgradable_to_relaxed 72 assert!(version.is_upgradable_to(&patch), "Patch update"); 73 assert!(version.is_upgradable_to_relaxed(&patch), "Patch update"); 74 75 assert!(version.is_upgradable_to(&minor), "Minor version update"); 76 assert!(version.is_upgradable_to_relaxed(&minor), "Minor version update"); 77 78 assert!(!version.is_upgradable_to(&major), "Incompatible (major version) update"); 79 assert!(!version.is_upgradable_to_relaxed(&major), "Incompatible (major version) update"); 80 81 assert!(!version.is_upgradable_to(&older), "Downgrade"); 82 assert!(!version.is_upgradable_to_relaxed(&older), "Downgrade"); 83 84 Ok(()) 85 } 86 87 #[test] test_is_upgradable_major_zero() -> Result<()>88 fn test_is_upgradable_major_zero() -> Result<()> { 89 let version = Version::parse("0.3.4")?; 90 let patch = Version::parse("0.3.5")?; 91 let minor = Version::parse("0.4.0")?; 92 let major = Version::parse("1.0.0")?; 93 let older = Version::parse("0.3.3")?; 94 95 assert!(version.is_upgradable_to(&patch), "Patch update"); 96 assert!(version.is_upgradable_to_relaxed(&patch), "Patch update"); 97 98 // Different behavior for minor version changes. 99 assert!(!version.is_upgradable_to(&minor), "Minor version update"); 100 assert!(version.is_upgradable_to_relaxed(&minor), "Minor version update"); 101 102 assert!(!version.is_upgradable_to(&major), "Incompatible (major version) update"); 103 assert!(!version.is_upgradable_to_relaxed(&major), "Incompatible (major version) update"); 104 105 assert!(!version.is_upgradable_to(&older), "Downgrade"); 106 assert!(!version.is_upgradable_to_relaxed(&older), "Downgrade"); 107 108 Ok(()) 109 } 110 } 111