xref: /aosp_15_r20/development/tools/external_crates/crate_tool/src/upgradable.rs (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
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