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