use crate::Error::UnsupportedExtractor;
use crate::Result;
#[cfg(feature = "theseus")]
use crate::configuration::theseus;
#[cfg(feature = "zonky")]
use crate::configuration::zonky;
use crate::extractor::ExtractDirectories;
use std::path::PathBuf;
use std::sync::{Arc, LazyLock, Mutex, RwLock};
static REGISTRY: LazyLock<Arc<Mutex<RepositoryRegistry>>> =
LazyLock::new(|| Arc::new(Mutex::new(RepositoryRegistry::default())));
type SupportsFn = fn(&str) -> Result<bool>;
type ExtractFn = fn(&Vec<u8>, &ExtractDirectories) -> Result<Vec<PathBuf>>;
#[expect(clippy::type_complexity)]
struct RepositoryRegistry {
extractors: Vec<(Arc<RwLock<SupportsFn>>, Arc<RwLock<ExtractFn>>)>,
}
impl RepositoryRegistry {
fn new() -> Self {
Self {
extractors: Vec::new(),
}
}
fn register(&mut self, supports_fn: SupportsFn, extract_fn: ExtractFn) {
self.extractors.insert(
0,
(
Arc::new(RwLock::new(supports_fn)),
Arc::new(RwLock::new(extract_fn)),
),
);
}
fn get(&self, url: &str) -> Result<ExtractFn> {
for (supports_fn, extractor_fn) in &self.extractors {
let supports_function = supports_fn.read()?;
if supports_function(url)? {
let extractor_function = extractor_fn.read()?;
return Ok(*extractor_function);
}
}
Err(UnsupportedExtractor(url.to_string()))
}
}
impl Default for RepositoryRegistry {
fn default() -> Self {
let mut registry = Self::new();
#[cfg(feature = "theseus")]
registry.register(|url| Ok(url.starts_with(theseus::URL)), theseus::extract);
#[cfg(feature = "zonky")]
registry.register(|url| Ok(url.starts_with(zonky::URL)), zonky::extract);
registry
}
}
pub fn register(supports_fn: SupportsFn, extractor_fn: ExtractFn) -> Result<()> {
REGISTRY.lock()?.register(supports_fn, extractor_fn);
Ok(())
}
pub fn get(url: &str) -> Result<ExtractFn> {
REGISTRY.lock()?.get(url)
}
#[cfg(test)]
mod tests {
use super::*;
use regex_lite::Regex;
#[test]
fn test_register() -> Result<()> {
register(|url| Ok(url == "https://foo.com"), |_, _| Ok(Vec::new()))?;
let url = "https://foo.com";
let extractor = get(url)?;
let mut extract_directories = ExtractDirectories::default();
extract_directories.add_mapping(Regex::new(".*")?, PathBuf::from("test"));
assert!(extractor(&Vec::new(), &extract_directories).is_ok());
Ok(())
}
#[test]
fn test_get_error() {
let error = get("foo").unwrap_err();
assert_eq!("unsupported extractor for 'foo'", error.to_string());
}
#[test]
#[cfg(feature = "theseus")]
fn test_get_theseus_postgresql_binaries() {
assert!(get(theseus::URL).is_ok());
}
}