diff --git a/postgresql_archive/src/error.rs b/postgresql_archive/src/error.rs index e6683d4..22df208 100644 --- a/postgresql_archive/src/error.rs +++ b/postgresql_archive/src/error.rs @@ -31,6 +31,12 @@ pub enum Error { /// Unexpected error #[error("{0}")] Unexpected(String), + /// Unsupported hasher + #[error("unsupported hasher for '{0}'")] + UnsupportedHasher(String), + /// Unsupported hasher + #[error("unsupported matcher for '{0}'")] + UnsupportedMatcher(String), /// Unsupported repository #[error("unsupported repository for '{0}'")] UnsupportedRepository(String), diff --git a/postgresql_archive/src/hasher/registry.rs b/postgresql_archive/src/hasher/registry.rs index 90f3264..e937001 100644 --- a/postgresql_archive/src/hasher/registry.rs +++ b/postgresql_archive/src/hasher/registry.rs @@ -1,8 +1,7 @@ use crate::hasher::{blake2b_512, blake2s_256, sha2_256, sha2_512, sha3_256, sha3_512}; -use crate::Error::PoisonedLock; +use crate::Error::{PoisonedLock, UnsupportedRepository}; use crate::Result; use lazy_static::lazy_static; -use std::collections::HashMap; use std::sync::{Arc, Mutex, RwLock}; lazy_static! { @@ -10,53 +9,55 @@ lazy_static! { Arc::new(Mutex::new(HasherRegistry::default())); } +pub type SupportsFn = fn(&str, &str) -> Result; pub type HasherFn = fn(&Vec) -> Result; /// Singleton struct to store hashers +#[allow(clippy::type_complexity)] struct HasherRegistry { - hashers: HashMap>>, + hashers: Vec<(Arc>, Arc>)>, } impl HasherRegistry { /// Creates a new hasher registry. fn new() -> Self { Self { - hashers: HashMap::new(), + hashers: Vec::new(), } } - /// Registers a hasher for an extension. Newly registered hashers with the same extension will - /// override existing ones. - fn register>(&mut self, extension: S, hasher_fn: HasherFn) { - let extension = extension.as_ref().to_string(); - self.hashers - .insert(extension, Arc::new(RwLock::new(hasher_fn))); + /// Registers a hasher for a supports function. Newly registered hashers will take precedence + /// over existing ones. + fn register(&mut self, supports_fn: SupportsFn, hasher_fn: HasherFn) { + self.hashers.insert( + 0, + ( + Arc::new(RwLock::new(supports_fn)), + Arc::new(RwLock::new(hasher_fn)), + ), + ); } - /// Get a hasher for the specified extension. + /// Get a hasher for the specified url and extension. /// /// # Errors /// * If the registry is poisoned. - fn get>(&self, extension: S) -> Result> { - let extension = extension.as_ref().to_string(); - if let Some(hasher) = self.hashers.get(&extension) { - let hasher = *hasher + fn get>(&self, url: S, extension: S) -> Result { + let url = url.as_ref(); + let extension = extension.as_ref(); + for (supports_fn, hasher_fn) in &self.hashers { + let supports_function = supports_fn .read() .map_err(|error| PoisonedLock(error.to_string()))?; - return Ok(Some(hasher)); + if supports_function(url, extension)? { + let hasher_function = hasher_fn + .read() + .map_err(|error| PoisonedLock(error.to_string()))?; + return Ok(*hasher_function); + } } - Ok(None) - } - - /// Get the number of hashers in the registry. - fn len(&self) -> usize { - self.hashers.len() - } - - /// Check if the registry is empty. - fn is_empty(&self) -> bool { - self.hashers.is_empty() + Err(UnsupportedRepository(url.to_string())) } } @@ -64,61 +65,39 @@ impl Default for HasherRegistry { /// Creates a new hasher registry with the default hashers registered. fn default() -> Self { let mut registry = Self::new(); - registry.register("blake2s", blake2s_256::hash); - registry.register("blake2b", blake2b_512::hash); - registry.register("sha256", sha2_256::hash); - registry.register("sha512", sha2_512::hash); - registry.register("sha3-256", sha3_256::hash); - registry.register("sha3-512", sha3_512::hash); + registry.register(|_, extension| Ok(extension == "blake2s"), blake2s_256::hash); + registry.register(|_, extension| Ok(extension == "blake2b"), blake2b_512::hash); + registry.register(|_, extension| Ok(extension == "sha256"), sha2_256::hash); + registry.register(|_, extension| Ok(extension == "sha512"), sha2_512::hash); + registry.register(|_, extension| Ok(extension == "sha3-256"), sha3_256::hash); + registry.register(|_, extension| Ok(extension == "sha3-512"), sha3_512::hash); registry } } -/// Registers a hasher for an extension. Newly registered hashers with the same extension will -/// override existing ones. +/// Registers a hasher for a supports function. Newly registered hashers will take precedence +/// over existing ones. /// /// # Errors /// * If the registry is poisoned. #[allow(dead_code)] -pub fn register>(extension: S, hasher_fn: HasherFn) -> Result<()> { +pub fn register(supports_fn: SupportsFn, hasher_fn: HasherFn) -> Result<()> { let mut registry = REGISTRY .lock() .map_err(|error| PoisonedLock(error.to_string()))?; - registry.register(extension, hasher_fn); + registry.register(supports_fn, hasher_fn); Ok(()) } -/// Get a hasher for the specified extension. -/// -/// # Errors -/// * If the registry is poisoned. -pub fn get>(extension: S) -> Result> { - let registry = REGISTRY - .lock() - .map_err(|error| PoisonedLock(error.to_string()))?; - registry.get(extension) -} - -/// Get the number of matchers in the registry. +/// Get a hasher for the specified url and extension. /// /// # Errors /// * If the registry is poisoned. -pub fn len() -> Result { +pub fn get>(url: S, extension: S) -> Result { let registry = REGISTRY .lock() .map_err(|error| PoisonedLock(error.to_string()))?; - Ok(registry.len()) -} - -/// Check if the registry is empty. -/// -/// # Errors -/// * If the registry is poisoned. -pub fn is_empty() -> Result { - let registry = REGISTRY - .lock() - .map_err(|error| PoisonedLock(error.to_string()))?; - Ok(registry.is_empty()) + registry.get(url, extension) } #[cfg(test)] @@ -126,7 +105,7 @@ mod tests { use super::*; fn test_hasher(extension: &str, expected: &str) -> Result<()> { - let hasher = get(extension)?.unwrap(); + let hasher = get("https://foo.com", extension)?; let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; let hash = hasher(&data)?; assert_eq!(expected, hash); @@ -135,22 +114,11 @@ mod tests { #[test] fn test_register() -> Result<()> { - let extension = "sha256"; - let hashers = len()?; - assert!(!is_empty()?); - REGISTRY - .lock() - .map_err(|error| PoisonedLock(error.to_string()))? - .hashers - .remove(extension); - assert_ne!(hashers, len()?); - register(extension, sha2_256::hash)?; - assert_eq!(hashers, len()?); - - test_hasher( - extension, - "9a89c68c4c5e28b8c4a5567673d462fff515db46116f9900624d09c474f593fb", - ) + register( + |_, extension| Ok(extension == "foo"), + |_| Ok("42".to_string()), + )?; + test_hasher("foo", "42") } #[test] diff --git a/postgresql_archive/src/matcher/registry.rs b/postgresql_archive/src/matcher/registry.rs index 98671bb..7059344 100644 --- a/postgresql_archive/src/matcher/registry.rs +++ b/postgresql_archive/src/matcher/registry.rs @@ -1,9 +1,8 @@ use crate::matcher::{default, postgresql_binaries}; -use crate::Error::PoisonedLock; +use crate::Error::{PoisonedLock, UnsupportedMatcher}; use crate::{Result, DEFAULT_POSTGRESQL_URL}; use lazy_static::lazy_static; use semver::Version; -use std::collections::HashMap; use std::sync::{Arc, Mutex, RwLock}; lazy_static! { @@ -11,59 +10,54 @@ lazy_static! { Arc::new(Mutex::new(MatchersRegistry::default())); } +pub type SupportsFn = fn(&str) -> Result; pub type MatcherFn = fn(&str, &Version) -> Result; /// Singleton struct to store matchers +#[allow(clippy::type_complexity)] struct MatchersRegistry { - matchers: HashMap, Arc>>, + matchers: Vec<(Arc>, Arc>)>, } impl MatchersRegistry { /// Creates a new matcher registry. fn new() -> Self { Self { - matchers: HashMap::new(), + matchers: Vec::new(), } } - /// Registers a matcher for a URL. Newly registered matchers with the same url will override - /// existing ones. - fn register>(&mut self, url: Option, matcher_fn: MatcherFn) { - let url: Option = url.map(|s| s.as_ref().to_string()); - self.matchers.insert(url, Arc::new(RwLock::new(matcher_fn))); + /// Registers a matcher for a supports function. Newly registered matchers with the take + /// precedence over existing ones. + fn register(&mut self, supports_fn: SupportsFn, matcher_fn: MatcherFn) { + self.matchers.insert( + 0, + ( + Arc::new(RwLock::new(supports_fn)), + Arc::new(RwLock::new(matcher_fn)), + ), + ); } - /// Get a matcher for the specified URL, or the default matcher if no matcher is - /// registered for the URL. + /// Get a matcher for the specified URL. /// /// # Errors /// * If the registry is poisoned. fn get>(&self, url: S) -> Result { - let url = Some(url.as_ref().to_string()); - if let Some(matcher) = self.matchers.get(&url) { - let matcher = *matcher + let url = url.as_ref(); + for (supports_fn, matcher_fn) in &self.matchers { + let supports_function = supports_fn .read() .map_err(|error| PoisonedLock(error.to_string()))?; - return Ok(matcher); + if supports_function(url)? { + let matcher_function = matcher_fn + .read() + .map_err(|error| PoisonedLock(error.to_string()))?; + return Ok(*matcher_function); + } } - let matcher = match self.matchers.get(&None) { - Some(matcher) => *matcher - .read() - .map_err(|error| PoisonedLock(error.to_string()))?, - None => default::matcher, - }; - Ok(matcher) - } - - /// Get the number of matchers in the registry. - fn len(&self) -> usize { - self.matchers.len() - } - - /// Check if the registry is empty. - fn is_empty(&self) -> bool { - self.matchers.is_empty() + Err(UnsupportedMatcher(url.to_string())) } } @@ -71,28 +65,30 @@ impl Default for MatchersRegistry { /// Creates a new matcher registry with the default matchers registered. fn default() -> Self { let mut registry = Self::new(); - registry.register(None::<&str>, default::matcher); - registry.register(Some(DEFAULT_POSTGRESQL_URL), postgresql_binaries::matcher); + registry.register( + |url| Ok(url == DEFAULT_POSTGRESQL_URL), + postgresql_binaries::matcher, + ); + registry.register(|_| Ok(true), default::matcher); registry } } -/// Registers a matcher for a URL. Newly registered matchers with the same url will override -/// existing ones. +/// Registers a matcher for a supports function. Newly registered matchers with the take +/// precedence over existing ones. /// /// # Errors /// * If the registry is poisoned. #[allow(dead_code)] -pub fn register>(url: Option, matcher_fn: MatcherFn) -> Result<()> { +pub fn register(supports_fn: SupportsFn, matcher_fn: MatcherFn) -> Result<()> { let mut registry = REGISTRY .lock() .map_err(|error| PoisonedLock(error.to_string()))?; - registry.register(url, matcher_fn); + registry.register(supports_fn, matcher_fn); Ok(()) } -/// Get a matcher for the specified URL, or the default matcher if no matcher is -/// registered for the URL. +/// Get a matcher for the specified URL. /// /// # Errors /// * If the registry is poisoned. @@ -103,53 +99,22 @@ pub fn get>(url: S) -> Result { registry.get(url) } -/// Get the number of matchers in the registry. -/// -/// # Errors -/// * If the registry is poisoned. -pub fn len() -> Result { - let registry = REGISTRY - .lock() - .map_err(|error| PoisonedLock(error.to_string()))?; - Ok(registry.len()) -} - -/// Check if the registry is empty. -/// -/// # Errors -/// * If the registry is poisoned. -pub fn is_empty() -> Result { - let registry = REGISTRY - .lock() - .map_err(|error| PoisonedLock(error.to_string()))?; - Ok(registry.is_empty()) -} - #[cfg(test)] mod tests { use super::*; - use crate::Error::PoisonedLock; use std::env; #[test] fn test_register() -> Result<()> { - let matchers = len()?; - assert!(!is_empty()?); - REGISTRY - .lock() - .map_err(|error| PoisonedLock(error.to_string()))? - .matchers - .remove(&None::); - assert_ne!(matchers, len()?); - register(None::<&str>, default::matcher)?; - assert_eq!(matchers, len()?); - - let matcher = get(DEFAULT_POSTGRESQL_URL)?; + register( + |url| Ok(url == "https://foo.com"), + |name, _| Ok(name == "foo"), + )?; + + let matcher = get("https://foo.com")?; let version = Version::new(16, 3, 0); - let target = target_triple::TARGET; - let name = format!("postgresql-{version}-{target}.tar.gz"); - assert!(matcher(name.as_str(), &version)?, "{}", name); + assert!(matcher("foo", &version)?); Ok(()) } diff --git a/postgresql_archive/src/repository/github/repository.rs b/postgresql_archive/src/repository/github/repository.rs index c97f31e..b072cd8 100644 --- a/postgresql_archive/src/repository/github/repository.rs +++ b/postgresql_archive/src/repository/github/repository.rs @@ -199,7 +199,7 @@ impl GitHub { .strip_prefix(format!("{}.", asset.name.as_str()).as_str()) .unwrap_or_default(); - if let Some(hasher_fn) = hasher::registry::get(extension)? { + if let Ok(hasher_fn) = hasher::registry::get(&self.url, &extension.to_string()) { asset_hash = Some(release_asset.clone()); asset_hasher_fn = Some(hasher_fn); break; diff --git a/postgresql_archive/src/repository/registry.rs b/postgresql_archive/src/repository/registry.rs index 5406645..9b9019e 100644 --- a/postgresql_archive/src/repository/registry.rs +++ b/postgresql_archive/src/repository/registry.rs @@ -27,7 +27,7 @@ impl RepositoryRegistry { } } - /// Registers a repository. Newly registered repositories can override existing ones. + /// Registers a repository. Newly registered repositories take precedence over existing ones. fn register(&mut self, supports_fn: Box, new_fn: Box) { self.repositories.insert( 0, @@ -57,16 +57,6 @@ impl RepositoryRegistry { Err(UnsupportedRepository(url.to_string())) } - - /// Get the number of repositories in the registry. - fn len(&self) -> usize { - self.repositories.len() - } - - /// Check if the registry is empty. - fn is_empty(&self) -> bool { - self.repositories.is_empty() - } } impl Default for RepositoryRegistry { @@ -102,48 +92,57 @@ pub fn get(url: &str) -> Result> { registry.get(url) } -/// Get the number of repositories in the registry. -/// -/// # Errors -/// * If the registry is poisoned. -pub fn len() -> Result { - let registry = REGISTRY - .lock() - .map_err(|error| PoisonedLock(error.to_string()))?; - Ok(registry.len()) -} - -/// Check if the registry is empty. -/// -/// # Errors -/// * If the registry is poisoned. -pub fn is_empty() -> Result { - let registry = REGISTRY - .lock() - .map_err(|error| PoisonedLock(error.to_string()))?; - Ok(registry.is_empty()) -} - #[cfg(test)] mod tests { use super::*; + use crate::repository::Archive; + use async_trait::async_trait; + use semver::{Version, VersionReq}; + use std::fmt::Debug; + + #[derive(Debug)] + struct TestRepository; + + impl TestRepository { + #[allow(clippy::new_ret_no_self)] + #[allow(clippy::unnecessary_wraps)] + fn new(_url: &str) -> Result> { + Ok(Box::new(Self)) + } + + fn supports(url: &str) -> bool { + url == "https://foo.com" + } + } + + #[async_trait] + impl Repository for TestRepository { + fn name(&self) -> &str { + "test" + } + + async fn get_version(&self, _version_req: &VersionReq) -> Result { + Ok(Version::new(0, 0, 42)) + } + + async fn get_archive(&self, _version_req: &VersionReq) -> Result { + Ok(Archive::new( + "test".to_string(), + Version::new(0, 0, 42), + Vec::new(), + )) + } + } #[tokio::test] async fn test_register() -> Result<()> { - let repositories = len()?; - assert!(!is_empty()?); - REGISTRY - .lock() - .map_err(|error| PoisonedLock(error.to_string()))? - .repositories - .truncate(0); - assert_ne!(repositories, len()?); - register(Box::new(GitHub::supports), Box::new(GitHub::new))?; - assert_eq!(repositories, len()?); - - let url = "https://github.com/theseus-rs/postgresql-binaries"; - let result = get(url); - assert!(result.is_ok()); + register( + Box::new(TestRepository::supports), + Box::new(TestRepository::new), + )?; + let url = "https://foo.com"; + let repository = get(url)?; + assert_eq!("test", repository.name()); Ok(()) }