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 clap::ValueEnum; 16 use semver::{Version, VersionReq}; 17 18 /// How strictly to enforce semver compatibility. 19 #[derive(Copy, Clone, ValueEnum)] 20 pub enum SemverCompatibilityRule { 21 /// Ignore semantic version. Consider any two versions compatible. 22 Ignore, 23 /// Consider 0.x and 0.y to be compatible, but otherwise follow standard rules. 24 Loose, 25 /// Follow standard semantic version rules, under which 0.x and 0.y are incompatible. 26 Strict, 27 } 28 /// A trait for determining semver compatibility. 29 pub trait IsUpgradableTo { 30 /// Returns true if the object version is upgradable to 'other', according to 31 /// the specified semantic version compatibility strictness. is_upgradable_to( &self, other: &Version, semver_compatibility: SemverCompatibilityRule, ) -> bool32 fn is_upgradable_to( 33 &self, 34 other: &Version, 35 semver_compatibility: SemverCompatibilityRule, 36 ) -> bool; 37 } 38 39 impl IsUpgradableTo for semver::Version { is_upgradable_to( &self, other: &Version, semver_compatibility: SemverCompatibilityRule, ) -> bool40 fn is_upgradable_to( 41 &self, 42 other: &Version, 43 semver_compatibility: SemverCompatibilityRule, 44 ) -> bool { 45 other > self 46 && VersionReq::parse(&self.to_string()) 47 .is_ok_and(|req| req.matches_with_compatibility_rule(other, semver_compatibility)) 48 } 49 } 50 51 /// A trait for custom semver compatibility logic, allowing it to be ignored or relaxed. 52 pub trait MatchesWithCompatibilityRule { 53 /// Returns true if the version matches the req, according to the 54 /// custom compatibility requirements of 'semver_compatibility'. matches_with_compatibility_rule( &self, version: &Version, semver_compatibility: SemverCompatibilityRule, ) -> bool55 fn matches_with_compatibility_rule( 56 &self, 57 version: &Version, 58 semver_compatibility: SemverCompatibilityRule, 59 ) -> bool; 60 } 61 impl MatchesWithCompatibilityRule for VersionReq { matches_with_compatibility_rule( &self, version: &Version, semver_compatibility: SemverCompatibilityRule, ) -> bool62 fn matches_with_compatibility_rule( 63 &self, 64 version: &Version, 65 semver_compatibility: SemverCompatibilityRule, 66 ) -> bool { 67 match semver_compatibility { 68 SemverCompatibilityRule::Ignore => true, 69 SemverCompatibilityRule::Loose => { 70 if self.comparators.len() == 1 71 && self.comparators[0].major == 0 72 && version.major == 0 73 { 74 let mut fake_v = version.clone(); 75 fake_v.major = 1; 76 let mut fake_req = self.clone(); 77 fake_req.comparators[0].major = 1; 78 fake_req.matches(&fake_v) 79 } else { 80 self.matches(version) 81 } 82 } 83 SemverCompatibilityRule::Strict => self.matches(version), 84 } 85 } 86 } 87 88 #[cfg(test)] 89 mod tests { 90 use super::*; 91 92 use anyhow::Result; 93 94 #[test] test_is_upgradable() -> Result<()>95 fn test_is_upgradable() -> Result<()> { 96 let version = Version::parse("2.3.4")?; 97 let patch = Version::parse("2.3.5")?; 98 let minor = Version::parse("2.4.0")?; 99 let major = Version::parse("3.0.0")?; 100 let older = Version::parse("2.3.3")?; 101 102 // All have same behavior for SemverCompatibility::LOOSE. 103 assert!( 104 version.is_upgradable_to(&patch, SemverCompatibilityRule::Strict), 105 "Patch update, strict" 106 ); 107 assert!( 108 version.is_upgradable_to(&patch, SemverCompatibilityRule::Loose), 109 "Patch update, loose" 110 ); 111 assert!( 112 version.is_upgradable_to(&patch, SemverCompatibilityRule::Ignore), 113 "Patch update, ignore" 114 ); 115 116 assert!( 117 version.is_upgradable_to(&minor, SemverCompatibilityRule::Strict), 118 "Minor version update, strict" 119 ); 120 assert!( 121 version.is_upgradable_to(&minor, SemverCompatibilityRule::Loose), 122 "Minor version update, loose" 123 ); 124 assert!( 125 version.is_upgradable_to(&minor, SemverCompatibilityRule::Ignore), 126 "Minor version update, ignore" 127 ); 128 129 assert!( 130 !version.is_upgradable_to(&major, SemverCompatibilityRule::Strict), 131 "Incompatible (major version) update, strict" 132 ); 133 assert!( 134 !version.is_upgradable_to(&major, SemverCompatibilityRule::Loose), 135 "Incompatible (major version) update, loose" 136 ); 137 assert!( 138 version.is_upgradable_to(&major, SemverCompatibilityRule::Ignore), 139 "Incompatible (major version) update, ignore" 140 ); 141 142 assert!( 143 !version.is_upgradable_to(&older, SemverCompatibilityRule::Strict), 144 "Downgrade, strict" 145 ); 146 assert!( 147 !version.is_upgradable_to(&older, SemverCompatibilityRule::Loose), 148 "Downgrade, loose" 149 ); 150 assert!( 151 !version.is_upgradable_to(&older, SemverCompatibilityRule::Ignore), 152 "Downgrade, ignore" 153 ); 154 155 Ok(()) 156 } 157 158 #[test] test_is_upgradable_major_zero() -> Result<()>159 fn test_is_upgradable_major_zero() -> Result<()> { 160 let version = Version::parse("0.3.4")?; 161 let patch = Version::parse("0.3.5")?; 162 let minor = Version::parse("0.4.0")?; 163 let major = Version::parse("1.0.0")?; 164 let older = Version::parse("0.3.3")?; 165 166 assert!( 167 version.is_upgradable_to(&patch, SemverCompatibilityRule::Strict), 168 "Patch update, strict" 169 ); 170 assert!( 171 version.is_upgradable_to(&patch, SemverCompatibilityRule::Loose), 172 "Patch update, loose" 173 ); 174 assert!( 175 version.is_upgradable_to(&patch, SemverCompatibilityRule::Ignore), 176 "Patch update, ignore" 177 ); 178 179 // Different behavior for minor version changes. 180 assert!( 181 !version.is_upgradable_to(&minor, SemverCompatibilityRule::Strict), 182 "Minor version update, strict" 183 ); 184 assert!( 185 version.is_upgradable_to(&minor, SemverCompatibilityRule::Loose), 186 "Minor version update, loose" 187 ); 188 assert!( 189 version.is_upgradable_to(&minor, SemverCompatibilityRule::Ignore), 190 "Minor version update, ignore" 191 ); 192 193 assert!( 194 !version.is_upgradable_to(&major, SemverCompatibilityRule::Strict), 195 "Incompatible (major version) update, strict" 196 ); 197 assert!( 198 !version.is_upgradable_to(&major, SemverCompatibilityRule::Loose), 199 "Incompatible (major version) update, loose" 200 ); 201 assert!( 202 version.is_upgradable_to(&major, SemverCompatibilityRule::Ignore), 203 "Incompatible (major version) update, ignore" 204 ); 205 206 assert!( 207 !version.is_upgradable_to(&older, SemverCompatibilityRule::Strict), 208 "Downgrade, strict" 209 ); 210 assert!( 211 !version.is_upgradable_to(&older, SemverCompatibilityRule::Loose), 212 "Downgrade, loose" 213 ); 214 assert!( 215 !version.is_upgradable_to(&older, SemverCompatibilityRule::Ignore), 216 "Downgrade, ignore" 217 ); 218 219 Ok(()) 220 } 221 } 222