• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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