use crate::Error::UnsupportedMatcher;
use crate::Result;
#[cfg(feature = "theseus")]
use crate::configuration::theseus;
#[cfg(feature = "zonky")]
use crate::configuration::zonky;
use semver::Version;
use std::sync::{Arc, LazyLock, Mutex, RwLock};
static REGISTRY: LazyLock<Arc<Mutex<MatchersRegistry>>> =
LazyLock::new(|| Arc::new(Mutex::new(MatchersRegistry::default())));
pub type SupportsFn = fn(&str) -> Result<bool>;
pub type MatcherFn = fn(&str, &str, &Version) -> Result<bool>;
#[expect(clippy::type_complexity)]
struct MatchersRegistry {
matchers: Vec<(Arc<RwLock<SupportsFn>>, Arc<RwLock<MatcherFn>>)>,
}
impl MatchersRegistry {
fn new() -> Self {
Self {
matchers: Vec::new(),
}
}
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)),
),
);
}
fn get<S: AsRef<str>>(&self, url: S) -> Result<MatcherFn> {
let url = url.as_ref();
for (supports_fn, matcher_fn) in &self.matchers {
let supports_function = supports_fn.read()?;
if supports_function(url)? {
let matcher_function = matcher_fn.read()?;
return Ok(*matcher_function);
}
}
Err(UnsupportedMatcher(url.to_string()))
}
}
impl Default for MatchersRegistry {
fn default() -> Self {
let mut registry = Self::new();
#[cfg(feature = "theseus")]
registry.register(|url| Ok(url == theseus::URL), theseus::matcher);
#[cfg(feature = "zonky")]
registry.register(|url| Ok(url == zonky::URL), zonky::matcher);
registry
}
}
pub fn register(supports_fn: SupportsFn, matcher_fn: MatcherFn) -> Result<()> {
REGISTRY.lock()?.register(supports_fn, matcher_fn);
Ok(())
}
pub fn get<S: AsRef<str>>(url: S) -> Result<MatcherFn> {
REGISTRY.lock()?.get(url)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_register() -> Result<()> {
register(
|url| Ok(url == "https://foo.com"),
|_url, name, _version| Ok(name == "foo"),
)?;
let matcher = get("https://foo.com")?;
let version = Version::new(16, 3, 0);
assert!(matcher("", "foo", &version)?);
Ok(())
}
#[test]
fn test_get_error() {
let result = get("foo").unwrap_err();
assert_eq!("unsupported matcher for 'foo'", result.to_string());
}
#[test]
#[cfg(feature = "theseus")]
fn test_get_theseus_postgresql_binaries() {
assert!(get(theseus::URL).is_ok());
}
#[test]
#[cfg(feature = "zonky")]
fn test_get_zonyk_postgresql_binaries() {
assert!(get(zonky::URL).is_ok());
}
}