diff --git a/postgresql_archive/src/error.rs b/postgresql_archive/src/error.rs index 6cc7ea8..e6683d4 100644 --- a/postgresql_archive/src/error.rs +++ b/postgresql_archive/src/error.rs @@ -22,6 +22,9 @@ pub enum Error { /// Parse error #[error(transparent)] ParseError(anyhow::Error), + /// Poisoned lock + #[error("poisoned lock '{0}'")] + PoisonedLock(String), /// Repository failure #[error("{0}")] RepositoryFailure(String), diff --git a/postgresql_archive/src/hasher/registry.rs b/postgresql_archive/src/hasher/registry.rs index cf6709b..90f3264 100644 --- a/postgresql_archive/src/hasher/registry.rs +++ b/postgresql_archive/src/hasher/registry.rs @@ -1,4 +1,5 @@ use crate::hasher::{blake2b_512, blake2s_256, sha2_256, sha2_512, sha3_256, sha3_512}; +use crate::Error::PoisonedLock; use crate::Result; use lazy_static::lazy_static; use std::collections::HashMap; @@ -33,13 +34,29 @@ impl HasherRegistry { } /// Get a hasher for the specified extension. - fn get>(&self, extension: S) -> Option { + /// + /// # 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) { - return Some(*hasher.read().unwrap()); + let hasher = *hasher + .read() + .map_err(|error| PoisonedLock(error.to_string()))?; + return Ok(Some(hasher)); } - None + 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() } } @@ -60,29 +77,56 @@ impl Default for HasherRegistry { /// Registers a hasher for an extension. Newly registered hashers with the same extension will /// override existing ones. /// -/// # Panics +/// # Errors /// * If the registry is poisoned. #[allow(dead_code)] -pub fn register>(extension: S, hasher_fn: HasherFn) { - let mut registry = REGISTRY.lock().unwrap(); +pub fn register>(extension: S, hasher_fn: HasherFn) -> Result<()> { + let mut registry = REGISTRY + .lock() + .map_err(|error| PoisonedLock(error.to_string()))?; registry.register(extension, hasher_fn); + Ok(()) } /// Get a hasher for the specified extension. /// -/// # Panics +/// # Errors /// * If the registry is poisoned. -pub fn get>(extension: S) -> Option { - let registry = REGISTRY.lock().unwrap(); +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. +/// +/// # 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::*; fn test_hasher(extension: &str, expected: &str) -> Result<()> { - let hasher = get(extension).unwrap(); + let hasher = get(extension)?.unwrap(); let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; let hash = hasher(&data)?; assert_eq!(expected, hash); @@ -92,12 +136,16 @@ mod tests { #[test] fn test_register() -> Result<()> { let extension = "sha256"; - let hashers = REGISTRY.lock().unwrap().hashers.len(); - assert!(!REGISTRY.lock().unwrap().hashers.is_empty()); - REGISTRY.lock().unwrap().hashers.remove(extension); - assert_ne!(hashers, REGISTRY.lock().unwrap().hashers.len()); - register(extension, sha2_256::hash); - assert_eq!(hashers, REGISTRY.lock().unwrap().hashers.len()); + 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, diff --git a/postgresql_archive/src/matcher/registry.rs b/postgresql_archive/src/matcher/registry.rs index 3793b03..98671bb 100644 --- a/postgresql_archive/src/matcher/registry.rs +++ b/postgresql_archive/src/matcher/registry.rs @@ -1,4 +1,5 @@ use crate::matcher::{default, postgresql_binaries}; +use crate::Error::PoisonedLock; use crate::{Result, DEFAULT_POSTGRESQL_URL}; use lazy_static::lazy_static; use semver::Version; @@ -34,16 +35,35 @@ impl MatchersRegistry { /// Get a matcher for the specified URL, or the default matcher if no matcher is /// registered for the URL. - fn get>(&self, url: S) -> MatcherFn { + /// + /// # 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) { - return *matcher.read().unwrap(); + let matcher = *matcher + .read() + .map_err(|error| PoisonedLock(error.to_string()))?; + return Ok(matcher); } - match self.matchers.get(&None) { - Some(matcher) => *matcher.read().unwrap(), + 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() } } @@ -60,39 +80,71 @@ impl Default for MatchersRegistry { /// Registers a matcher for a URL. Newly registered matchers with the same url will override /// existing ones. /// -/// # Panics +/// # Errors /// * If the registry is poisoned. #[allow(dead_code)] -pub fn register>(url: Option, matcher_fn: MatcherFn) { - let mut registry = REGISTRY.lock().unwrap(); +pub fn register>(url: Option, matcher_fn: MatcherFn) -> Result<()> { + let mut registry = REGISTRY + .lock() + .map_err(|error| PoisonedLock(error.to_string()))?; registry.register(url, matcher_fn); + Ok(()) } /// Get a matcher for the specified URL, or the default matcher if no matcher is /// registered for the URL. /// -/// # Panics +/// # Errors /// * If the registry is poisoned. -pub fn get>(url: S) -> MatcherFn { - let registry = REGISTRY.lock().unwrap(); +pub fn get>(url: S) -> Result { + let registry = REGISTRY + .lock() + .map_err(|error| PoisonedLock(error.to_string()))?; 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 = REGISTRY.lock().unwrap().matchers.len(); - assert!(!REGISTRY.lock().unwrap().matchers.is_empty()); - REGISTRY.lock().unwrap().matchers.remove(&None::); - assert_ne!(matchers, REGISTRY.lock().unwrap().matchers.len()); - register(None::<&str>, default::matcher); - assert_eq!(matchers, REGISTRY.lock().unwrap().matchers.len()); - - let matcher = get(DEFAULT_POSTGRESQL_URL); + 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)?; let version = Version::new(16, 3, 0); let target = target_triple::TARGET; let name = format!("postgresql-{version}-{target}.tar.gz"); @@ -103,7 +155,7 @@ mod tests { #[test] fn test_default_matcher() -> Result<()> { - let matcher = get("https://foo.com"); + let matcher = get("https://foo.com")?; let version = Version::new(16, 3, 0); let os = env::consts::OS; let arch = env::consts::ARCH; diff --git a/postgresql_archive/src/repository/github/repository.rs b/postgresql_archive/src/repository/github/repository.rs index 7e73277..c97f31e 100644 --- a/postgresql_archive/src/repository/github/repository.rs +++ b/postgresql_archive/src/repository/github/repository.rs @@ -174,7 +174,7 @@ impl GitHub { version: &Version, release: &Release, ) -> Result<(Asset, Option, Option)> { - let matcher = matcher::registry::get(&self.url); + let matcher = matcher::registry::get(&self.url)?; let mut release_asset: Option = None; for asset in &release.assets { if matcher(asset.name.as_str(), version)? { @@ -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 Some(hasher_fn) = hasher::registry::get(extension)? { 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 3ca4dce..5406645 100644 --- a/postgresql_archive/src/repository/registry.rs +++ b/postgresql_archive/src/repository/registry.rs @@ -1,6 +1,6 @@ use crate::repository::github::repository::GitHub; use crate::repository::model::Repository; -use crate::Error::UnsupportedRepository; +use crate::Error::{PoisonedLock, UnsupportedRepository}; use crate::Result; use lazy_static::lazy_static; use std::sync::{Arc, Mutex, RwLock}; @@ -39,17 +39,34 @@ impl RepositoryRegistry { } /// Gets a repository that supports the specified URL + /// + /// # Errors + /// * If the URL is not supported. fn get(&self, url: &str) -> Result> { for (supports_fn, new_fn) in &self.repositories { - let supports_function = supports_fn.read().unwrap(); + let supports_function = supports_fn + .read() + .map_err(|error| PoisonedLock(error.to_string()))?; if supports_function(url) { - let new_function = new_fn.read().unwrap(); + let new_function = new_fn + .read() + .map_err(|error| PoisonedLock(error.to_string()))?; return new_function(url); } } 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 { @@ -63,38 +80,66 @@ impl Default for RepositoryRegistry { /// Registers a repository. Newly registered repositories can override existing ones. /// -/// # Panics -/// * If the repository registry is poisoned. +/// # Errors +/// * If the registry is poisoned. #[allow(dead_code)] -pub fn register(supports_fn: Box, new_fn: Box) { - let mut registry = REGISTRY.lock().unwrap(); +pub fn register(supports_fn: Box, new_fn: Box) -> Result<()> { + let mut registry = REGISTRY + .lock() + .map_err(|error| PoisonedLock(error.to_string()))?; registry.register(supports_fn, new_fn); + Ok(()) } /// Gets a repository that supports the specified URL /// /// # Errors /// * If the URL is not supported. -/// -/// # Panics -/// * If the repository registry is poisoned. pub fn get(url: &str) -> Result> { - let registry = REGISTRY.lock().unwrap(); + let registry = REGISTRY + .lock() + .map_err(|error| PoisonedLock(error.to_string()))?; 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::*; #[tokio::test] async fn test_register() -> Result<()> { - let repositories = REGISTRY.lock().unwrap().repositories.len(); - assert!(!REGISTRY.lock().unwrap().repositories.is_empty()); - REGISTRY.lock().unwrap().repositories.truncate(0); - assert_ne!(repositories, REGISTRY.lock().unwrap().repositories.len()); - register(Box::new(GitHub::supports), Box::new(GitHub::new)); - assert_eq!(repositories, REGISTRY.lock().unwrap().repositories.len()); + 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);