Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions postgresql_extensions/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,13 @@
pub mod blocking;
mod error;
pub mod extensions;
mod matcher;
mod model;
pub mod repository;

pub use error::{Error, Result};
pub use extensions::{get_available_extensions, get_installed_extensions, install, uninstall};
pub use matcher::{matcher, tar_gz_matcher, zip_matcher};
#[cfg(test)]
pub use model::TestSettings;
pub use model::{AvailableExtension, InstalledConfiguration, InstalledExtension};
Expand Down
228 changes: 228 additions & 0 deletions postgresql_extensions/src/matcher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
use postgresql_archive::Result;
use regex::Regex;
use semver::Version;
use std::collections::HashMap;
use std::env::consts;
use url::Url;

/// .tar.gz asset matcher that matches the asset name to the postgresql major version, target triple
/// or OS/CPU architecture.
///
/// # Errors
/// * If the asset matcher fails.
#[allow(clippy::case_sensitive_file_extension_comparisons)]
pub fn tar_gz_matcher(url: &str, name: &str, version: &Version) -> Result<bool> {
if !matcher(url, name, version)? {
return Ok(false);
}

Ok(name.ends_with(".tar.gz"))
}

/// .zip asset matcher that matches the asset name to the postgresql major version, target triple or
/// OS/CPU architecture.
///
/// # Errors
/// * If the asset matcher fails.
#[allow(clippy::case_sensitive_file_extension_comparisons)]
pub fn zip_matcher(url: &str, name: &str, version: &Version) -> Result<bool> {
if !matcher(url, name, version)? {
return Ok(false);
}

Ok(name.ends_with(".zip"))
}

/// Default asset matcher that matches the asset name to the postgresql major version, target triple
/// or OS/CPU architecture.
///
/// # Errors
/// * If the asset matcher fails.
pub fn matcher(url: &str, name: &str, _version: &Version) -> Result<bool> {
let Ok(url) = Url::parse(url) else {
return Ok(false);
};
let query_parameters: HashMap<String, String> = url.query_pairs().into_owned().collect();
let Some(postgresql_version) = query_parameters.get("postgresql_version") else {
return Ok(false);
};
let postgresql_major_version = match postgresql_version.split_once('.') {
None => return Ok(false),
Some((major, _)) => major,
};

let postgresql_version = format!("pg{postgresql_major_version}");
let postgresql_version_re = regex(postgresql_version.as_str())?;
if !postgresql_version_re.is_match(name) {
return Ok(false);
}

let target_re = regex(target_triple::TARGET)?;
if target_re.is_match(name) {
return Ok(true);
}

let os = consts::OS;
let os_re = regex(os)?;
let matches_os = match os {
"macos" => {
let darwin_re = regex("darwin")?;
os_re.is_match(name) || darwin_re.is_match(name)
}
_ => os_re.is_match(name),
};

let arch = consts::ARCH;
let arch_re = regex(arch)?;
let matches_arch = match arch {
"x86_64" => {
let amd64_re = regex("amd64")?;
arch_re.is_match(name) || amd64_re.is_match(name)
}
"aarch64" => {
let arm64_re = regex("arm64")?;
arch_re.is_match(name) || arm64_re.is_match(name)
}
_ => arch_re.is_match(name),
};
if matches_os && matches_arch {
return Ok(true);
}

Ok(false)
}

/// Creates a new regex for the specified key.
///
/// # Arguments
/// * `key` - The key to create the regex for.
///
/// # Returns
/// * The regex.
///
/// # Errors
/// * If the regex cannot be created.
fn regex(key: &str) -> Result<Regex> {
let regex = Regex::new(format!(r"[\W_]{key}[\W_]").as_str())?;
Ok(regex)
}

#[cfg(test)]
mod tests {
use super::*;
use anyhow::Result;

#[test]
fn test_invalid_url() -> Result<()> {
let url = "^";
assert!(!matcher(url, "", &Version::new(0, 0, 0))?);
Ok(())
}

#[test]
fn test_no_version() -> Result<()> {
assert!(!matcher("https://foo", "", &Version::new(0, 0, 0))?);
Ok(())
}

#[test]
fn test_invalid_version() -> Result<()> {
assert!(!matcher(
"https://foo?postgresql_version=16",
"",
&Version::new(0, 0, 0)
)?);
Ok(())
}

#[test]
fn test_tar_gz_matcher() -> Result<()> {
let postgresql_major_version = 16;
let url = format!("https://foo?postgresql_version={postgresql_major_version}.3");
let version = Version::parse("1.2.3")?;
let target = target_triple::TARGET;

let valid_name = format!("postgresql-pg{postgresql_major_version}-{target}.tar.gz");
let invalid_name = format!("postgresql-pg{postgresql_major_version}-{target}.zip");
assert!(
tar_gz_matcher(url.as_str(), valid_name.as_str(), &version)?,
"{}",
valid_name
);
assert!(
!tar_gz_matcher(url.as_str(), invalid_name.as_str(), &version)?,
"{}",
invalid_name
);
Ok(())
}

#[test]
fn test_zip_matcher() -> Result<()> {
let postgresql_major_version = 16;
let url = format!("https://foo?postgresql_version={postgresql_major_version}.3");
let version = Version::parse("1.2.3")?;
let target = target_triple::TARGET;

let valid_name = format!("postgresql-pg{postgresql_major_version}-{target}.zip");
let invalid_name = format!("postgresql-pg{postgresql_major_version}-{target}.tar.gz");
assert!(
zip_matcher(url.as_str(), valid_name.as_str(), &version)?,
"{}",
valid_name
);
assert!(
!zip_matcher(url.as_str(), invalid_name.as_str(), &version)?,
"{}",
invalid_name
);
Ok(())
}

#[test]
fn test_matcher_success() -> Result<()> {
let postgresql_major_version = 16;
let url = format!("https://foo?postgresql_version={postgresql_major_version}.3");
let version = Version::parse("1.2.3")?;
let target = target_triple::TARGET;
let os = consts::OS;
let arch = consts::ARCH;
let names = vec![
format!("postgresql-pg{postgresql_major_version}-{target}.zip"),
format!("postgresql-pg{postgresql_major_version}-{os}-{arch}.zip"),
format!("postgresql-pg{postgresql_major_version}-{target}.tar.gz"),
format!("postgresql-pg{postgresql_major_version}-{os}-{arch}.tar.gz"),
format!("foo.{target}.pg{postgresql_major_version}.tar.gz"),
format!("foo.{os}.{arch}.pg{postgresql_major_version}.tar.gz"),
format!("foo-{arch}-{os}-pg{postgresql_major_version}.tar.gz"),
format!("foo_{arch}_{os}_pg{postgresql_major_version}.tar.gz"),
];

for name in names {
assert!(matcher(url.as_str(), name.as_str(), &version)?, "{}", name);
}
Ok(())
}

#[test]
fn test_matcher_errors() -> Result<()> {
let postgresql_major_version = 16;
let url = format!("https://foo?postgresql_version={postgresql_major_version}.3");
let version = Version::parse("1.2.3")?;
let target = target_triple::TARGET;
let os = consts::OS;
let arch = consts::ARCH;
let names = vec![
format!("foo{target}.tar.gz"),
format!("foo{os}-{arch}.tar.gz"),
format!("foo-{target}.tar"),
format!("foo-{os}-{arch}.tar"),
format!("foo-{os}{arch}.tar.gz"),
];

for name in names {
assert!(!matcher(url.as_str(), name.as_str(), &version)?, "{}", name);
}
Ok(())
}
}
89 changes: 0 additions & 89 deletions postgresql_extensions/src/repository/portal_corp/matcher.rs

This file was deleted.

3 changes: 0 additions & 3 deletions postgresql_extensions/src/repository/portal_corp/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
mod matcher;
pub mod repository;

pub const URL: &str = "https://github.com/portalcorp";

pub use matcher::matcher;
10 changes: 7 additions & 3 deletions postgresql_extensions/src/repository/portal_corp/repository.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crate::matcher::zip_matcher;
use crate::model::AvailableExtension;
use crate::repository::portal_corp::URL;
use crate::repository::{portal_corp, Repository};
use crate::repository::Repository;
use crate::Result;
use async_trait::async_trait;
use postgresql_archive::get_archive;
use postgresql_archive::repository::github::repository::GitHub;
use postgresql_archive::{get_archive, matcher};
use semver::{Version, VersionReq};
use std::fmt::Debug;
use std::io::Cursor;
Expand All @@ -31,7 +32,10 @@ impl PortalCorp {
/// # Errors
/// * If the repository cannot be initialized.
pub fn initialize() -> Result<()> {
matcher::registry::register(|url| Ok(url.starts_with(URL)), portal_corp::matcher)?;
postgresql_archive::matcher::registry::register(
|url| Ok(url.starts_with(URL)),
zip_matcher,
)?;
postgresql_archive::repository::registry::register(
|url| Ok(url.starts_with(URL)),
Box::new(GitHub::new),
Expand Down
Loading