Skip to content

Commit

Permalink
fix: convert possible panics to errors
Browse files Browse the repository at this point in the history
  • Loading branch information
brianheineman committed Jun 29, 2024
1 parent 0f6a51e commit 423f8a1
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 55 deletions.
3 changes: 3 additions & 0 deletions postgresql_archive/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
80 changes: 64 additions & 16 deletions postgresql_archive/src/hasher/registry.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -33,13 +34,29 @@ impl HasherRegistry {
}

/// Get a hasher for the specified extension.
fn get<S: AsRef<str>>(&self, extension: S) -> Option<HasherFn> {
///
/// # Errors
/// * If the registry is poisoned.
fn get<S: AsRef<str>>(&self, extension: S) -> Result<Option<HasherFn>> {
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()
}
}

Expand All @@ -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<S: AsRef<str>>(extension: S, hasher_fn: HasherFn) {
let mut registry = REGISTRY.lock().unwrap();
pub fn register<S: AsRef<str>>(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<S: AsRef<str>>(extension: S) -> Option<HasherFn> {
let registry = REGISTRY.lock().unwrap();
pub fn get<S: AsRef<str>>(extension: S) -> Result<Option<HasherFn>> {
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<usize> {
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<bool> {
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);
Expand All @@ -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,
Expand Down
92 changes: 72 additions & 20 deletions postgresql_archive/src/matcher/registry.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<S: AsRef<str>>(&self, url: S) -> MatcherFn {
///
/// # Errors
/// * If the registry is poisoned.
fn get<S: AsRef<str>>(&self, url: S) -> Result<MatcherFn> {
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()
}
}

Expand All @@ -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<S: AsRef<str>>(url: Option<S>, matcher_fn: MatcherFn) {
let mut registry = REGISTRY.lock().unwrap();
pub fn register<S: AsRef<str>>(url: Option<S>, 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<S: AsRef<str>>(url: S) -> MatcherFn {
let registry = REGISTRY.lock().unwrap();
pub fn get<S: AsRef<str>>(url: S) -> Result<MatcherFn> {
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<usize> {
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<bool> {
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::<String>);
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::<String>);
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");
Expand All @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions postgresql_archive/src/repository/github/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ impl GitHub {
version: &Version,
release: &Release,
) -> Result<(Asset, Option<Asset>, Option<HasherFn>)> {
let matcher = matcher::registry::get(&self.url);
let matcher = matcher::registry::get(&self.url)?;
let mut release_asset: Option<Asset> = None;
for asset in &release.assets {
if matcher(asset.name.as_str(), version)? {
Expand All @@ -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;
Expand Down
Loading

0 comments on commit 423f8a1

Please sign in to comment.