1 // Copyright (C) 2023 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 std::{
16     borrow::Borrow,
17     cmp::Ordering,
18     hash::{Hash, Hasher},
19 };
20 
21 #[cfg(test)]
22 use anyhow::Result;
23 
24 use semver::{BuildMetadata, Prerelease, Version, VersionReq};
25 
26 static MIN_VERSION: Version =
27     Version { major: 0, minor: 0, patch: 0, pre: Prerelease::EMPTY, build: BuildMetadata::EMPTY };
28 
29 pub trait NamedAndVersioned {
name(&self) -> &str30     fn name(&self) -> &str;
version(&self) -> &Version31     fn version(&self) -> &Version;
key<'a>(&'a self) -> NameAndVersionRef<'a>32     fn key<'a>(&'a self) -> NameAndVersionRef<'a>;
33 }
34 
35 #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Clone)]
36 pub struct NameAndVersion {
37     name: String,
38     version: Version,
39 }
40 
41 #[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
42 pub struct NameAndVersionRef<'a> {
43     name: &'a str,
44     version: &'a Version,
45 }
46 
47 impl NameAndVersion {
new(name: String, version: Version) -> Self48     pub fn new(name: String, version: Version) -> Self {
49         NameAndVersion { name, version }
50     }
from(nv: &impl NamedAndVersioned) -> Self51     pub fn from(nv: &impl NamedAndVersioned) -> Self {
52         NameAndVersion { name: nv.name().to_string(), version: nv.version().clone() }
53     }
min_version(name: String) -> Self54     pub fn min_version(name: String) -> Self {
55         NameAndVersion { name, version: MIN_VERSION.clone() }
56     }
57     #[cfg(test)]
try_from_str(name: &str, version: &str) -> Result<Self>58     pub fn try_from_str(name: &str, version: &str) -> Result<Self> {
59         Ok(NameAndVersion::new(name.to_string(), Version::parse(version)?))
60     }
61 }
62 
63 impl NamedAndVersioned for NameAndVersion {
name(&self) -> &str64     fn name(&self) -> &str {
65         self.name.as_str()
66     }
67 
version(&self) -> &Version68     fn version(&self) -> &Version {
69         &self.version
70     }
key<'k>(&'k self) -> NameAndVersionRef<'k>71     fn key<'k>(&'k self) -> NameAndVersionRef<'k> {
72         NameAndVersionRef::new(self.name(), self.version())
73     }
74 }
75 
76 impl<'a> NameAndVersionRef<'a> {
new(name: &'a str, version: &'a Version) -> Self77     pub fn new(name: &'a str, version: &'a Version) -> Self {
78         NameAndVersionRef { name, version }
79     }
80 }
81 
82 impl<'a> NamedAndVersioned for NameAndVersionRef<'a> {
name(&self) -> &str83     fn name(&self) -> &str {
84         self.name
85     }
version(&self) -> &Version86     fn version(&self) -> &Version {
87         self.version
88     }
key<'k>(&'k self) -> NameAndVersionRef<'k>89     fn key<'k>(&'k self) -> NameAndVersionRef<'k> {
90         *self
91     }
92 }
93 
94 impl<'a> Borrow<dyn NamedAndVersioned + 'a> for NameAndVersion {
borrow(&self) -> &(dyn NamedAndVersioned + 'a)95     fn borrow(&self) -> &(dyn NamedAndVersioned + 'a) {
96         self
97     }
98 }
99 
100 impl<'a> PartialEq for (dyn NamedAndVersioned + 'a) {
eq(&self, other: &Self) -> bool101     fn eq(&self, other: &Self) -> bool {
102         self.key().eq(&other.key())
103     }
104 }
105 
106 impl<'a> Eq for (dyn NamedAndVersioned + 'a) {}
107 
108 impl<'a> PartialOrd for (dyn NamedAndVersioned + 'a) {
partial_cmp(&self, other: &Self) -> Option<Ordering>109     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
110         self.key().partial_cmp(&other.key())
111     }
112 }
113 
114 impl<'a> Ord for (dyn NamedAndVersioned + 'a) {
cmp(&self, other: &Self) -> Ordering115     fn cmp(&self, other: &Self) -> Ordering {
116         self.key().cmp(&other.key())
117     }
118 }
119 
120 impl<'a> Hash for (dyn NamedAndVersioned + 'a) {
hash<H: Hasher>(&self, state: &mut H)121     fn hash<H: Hasher>(&self, state: &mut H) {
122         self.key().hash(state)
123     }
124 }
125 
126 pub trait IsUpgradableTo: NamedAndVersioned {
is_upgradable_to(&self, other: &impl NamedAndVersioned) -> bool127     fn is_upgradable_to(&self, other: &impl NamedAndVersioned) -> bool {
128         self.name() == other.name()
129             && VersionReq::parse(&self.version().to_string())
130                 .is_ok_and(|req| req.matches(other.version()))
131     }
132 }
133 
134 impl<'a> IsUpgradableTo for NameAndVersion {}
135 impl<'a> IsUpgradableTo for NameAndVersionRef<'a> {}
136 
137 #[cfg(test)]
138 mod tests {
139     use super::*;
140     use anyhow::Result;
141 
142     #[test]
test_name_version_ref() -> Result<()>143     fn test_name_version_ref() -> Result<()> {
144         let version = Version::parse("2.3.4")?;
145         let compat1 = Version::parse("2.3.5")?;
146         let compat2 = Version::parse("2.4.0")?;
147         let incompat = Version::parse("3.0.0")?;
148         let older = Version::parse("2.3.3")?;
149         let nvp = NameAndVersionRef::new("foo", &version);
150         assert_eq!(nvp.name(), "foo");
151         assert_eq!(nvp.version().to_string(), "2.3.4");
152         assert!(nvp.is_upgradable_to(&NameAndVersionRef::new("foo", &compat1)), "Patch update");
153         assert!(
154             nvp.is_upgradable_to(&NameAndVersionRef::new("foo", &compat2)),
155             "Minor version update"
156         );
157         assert!(
158             !nvp.is_upgradable_to(&NameAndVersionRef::new("foo", &incompat)),
159             "Incompatible (major version) update"
160         );
161         assert!(!nvp.is_upgradable_to(&NameAndVersionRef::new("foo", &older)), "Downgrade");
162         assert!(!nvp.is_upgradable_to(&NameAndVersionRef::new("bar", &compat1)), "Different name");
163         Ok(())
164     }
165 
166     #[test]
test_name_and_version() -> Result<()>167     fn test_name_and_version() -> Result<()> {
168         let version = Version::parse("2.3.4")?;
169         let compat1 = Version::parse("2.3.5")?;
170         let compat2 = Version::parse("2.4.0")?;
171         let incompat = Version::parse("3.0.0")?;
172         let older = Version::parse("2.3.3")?;
173         let nvp = NameAndVersion::new("foo".to_string(), version);
174         assert_eq!(nvp.name(), "foo");
175         assert_eq!(nvp.version().to_string(), "2.3.4");
176         assert!(nvp.is_upgradable_to(&NameAndVersionRef::new("foo", &compat1)), "Patch update");
177         assert!(
178             nvp.is_upgradable_to(&NameAndVersionRef::new("foo", &compat2)),
179             "Minor version update"
180         );
181         assert!(
182             !nvp.is_upgradable_to(&NameAndVersionRef::new("foo", &incompat)),
183             "Incompatible (major version) update"
184         );
185         assert!(!nvp.is_upgradable_to(&NameAndVersionRef::new("foo", &older)), "Downgrade");
186         assert!(!nvp.is_upgradable_to(&NameAndVersionRef::new("bar", &compat1)), "Different name");
187         Ok(())
188     }
189 }
190