Skip to content

Commit a04a087

Browse files
committed
refactor!: refactor extension matchers
1 parent 3cdd9c5 commit a04a087

File tree

11 files changed

+206
-309
lines changed

11 files changed

+206
-309
lines changed

postgresql_extensions/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,13 @@
9191
pub mod blocking;
9292
mod error;
9393
pub mod extensions;
94+
mod matcher;
9495
mod model;
9596
pub mod repository;
9697

9798
pub use error::{Error, Result};
9899
pub use extensions::{get_available_extensions, get_installed_extensions, install, uninstall};
100+
pub use matcher::{matcher, tar_gz_matcher, zip_matcher};
99101
#[cfg(test)]
100102
pub use model::TestSettings;
101103
pub use model::{AvailableExtension, InstalledConfiguration, InstalledExtension};
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
use postgresql_archive::Result;
2+
use regex::Regex;
3+
use semver::Version;
4+
use std::collections::HashMap;
5+
use std::env::consts;
6+
use url::Url;
7+
8+
/// .zip asset matcher that matches the asset name to the postgresql major version, target triple or
9+
/// OS/CPU architecture.
10+
///
11+
/// # Errors
12+
/// * If the asset matcher fails.
13+
#[allow(clippy::case_sensitive_file_extension_comparisons)]
14+
pub fn zip_matcher(url: &str, name: &str, version: &Version) -> Result<bool> {
15+
if !matcher(url, name, version)? {
16+
return Ok(false);
17+
}
18+
19+
Ok(name.ends_with(".zip"))
20+
}
21+
22+
/// .tar.gz asset matcher that matches the asset name to the postgresql major version, target triple
23+
/// or OS/CPU architecture.
24+
///
25+
/// # Errors
26+
/// * If the asset matcher fails.
27+
#[allow(clippy::case_sensitive_file_extension_comparisons)]
28+
pub fn tar_gz_matcher(url: &str, name: &str, version: &Version) -> Result<bool> {
29+
if !matcher(url, name, version)? {
30+
return Ok(false);
31+
}
32+
33+
Ok(name.ends_with(".tar.gz"))
34+
}
35+
36+
/// Default asset matcher that matches the asset name to the postgresql major version, target triple
37+
/// or OS/CPU architecture.
38+
///
39+
/// # Errors
40+
/// * If the asset matcher fails.
41+
pub fn matcher(url: &str, name: &str, _version: &Version) -> Result<bool> {
42+
let Ok(url) = Url::parse(url) else {
43+
return Ok(false);
44+
};
45+
let query_parameters: HashMap<String, String> = url.query_pairs().into_owned().collect();
46+
let Some(postgresql_version) = query_parameters.get("postgresql_version") else {
47+
return Ok(false);
48+
};
49+
let postgresql_major_version = match postgresql_version.split_once('.') {
50+
None => return Ok(false),
51+
Some((major, _)) => major,
52+
};
53+
54+
let postgresql_version = format!("pg{postgresql_major_version}");
55+
let postgresql_version_re = regex(postgresql_version.as_str())?;
56+
if !postgresql_version_re.is_match(name) {
57+
return Ok(false);
58+
}
59+
60+
let target_re = regex(target_triple::TARGET)?;
61+
if target_re.is_match(name) {
62+
return Ok(true);
63+
}
64+
65+
let os = consts::OS;
66+
let os_re = regex(os)?;
67+
let matches_os = match os {
68+
"macos" => {
69+
let darwin_re = regex("darwin")?;
70+
os_re.is_match(name) || darwin_re.is_match(name)
71+
}
72+
_ => os_re.is_match(name),
73+
};
74+
75+
let arch = consts::ARCH;
76+
let arch_re = regex(arch)?;
77+
let matches_arch = match arch {
78+
"x86_64" => {
79+
let amd64_re = regex("amd64")?;
80+
arch_re.is_match(name) || amd64_re.is_match(name)
81+
}
82+
"aarch64" => {
83+
let arm64_re = regex("arm64")?;
84+
arch_re.is_match(name) || arm64_re.is_match(name)
85+
}
86+
_ => arch_re.is_match(name),
87+
};
88+
if matches_os && matches_arch {
89+
return Ok(true);
90+
}
91+
92+
Ok(false)
93+
}
94+
95+
/// Creates a new regex for the specified key.
96+
///
97+
/// # Arguments
98+
/// * `key` - The key to create the regex for.
99+
///
100+
/// # Returns
101+
/// * The regex.
102+
///
103+
/// # Errors
104+
/// * If the regex cannot be created.
105+
fn regex(key: &str) -> Result<Regex> {
106+
let regex = Regex::new(format!(r"[\W_]{key}[\W_]").as_str())?;
107+
Ok(regex)
108+
}
109+
110+
#[cfg(test)]
111+
mod tests {
112+
use super::*;
113+
use anyhow::Result;
114+
115+
#[test]
116+
fn test_invalid_url() -> Result<()> {
117+
let url = "^";
118+
assert!(!matcher(url, "", &Version::new(0, 0, 0))?);
119+
Ok(())
120+
}
121+
122+
#[test]
123+
fn test_no_version() -> Result<()> {
124+
assert!(!matcher("https://foo", "", &Version::new(0, 0, 0))?);
125+
Ok(())
126+
}
127+
128+
#[test]
129+
fn test_invalid_version() -> Result<()> {
130+
assert!(!matcher(
131+
"https://foo?postgresql_version=16",
132+
"",
133+
&Version::new(0, 0, 0)
134+
)?);
135+
Ok(())
136+
}
137+
138+
#[test]
139+
fn test_asset_match_success() -> Result<()> {
140+
let postgresql_major_version = 16;
141+
let url = format!("https://foo?postgresql_version={postgresql_major_version}.3");
142+
let version = Version::parse("1.2.3")?;
143+
let target = target_triple::TARGET;
144+
let os = consts::OS;
145+
let arch = consts::ARCH;
146+
let names = vec![
147+
format!("postgresql-pg16-{target}.zip"),
148+
format!("postgresql-pg16-{os}-{arch}.zip"),
149+
format!("postgresql-pg16-{target}.tar.gz"),
150+
format!("postgresql-pg16-{os}-{arch}.tar.gz"),
151+
format!("foo.{target}.pg16.tar.gz"),
152+
format!("foo.{os}.{arch}.pg16.tar.gz"),
153+
format!("foo-{arch}-{os}-pg16.tar.gz"),
154+
format!("foo_{arch}_{os}_pg16.tar.gz"),
155+
];
156+
157+
for name in names {
158+
assert!(matcher(url.as_str(), name.as_str(), &version)?, "{}", name);
159+
}
160+
Ok(())
161+
}
162+
163+
#[test]
164+
fn test_asset_match_errors() -> Result<()> {
165+
let postgresql_major_version = 16;
166+
let url = format!("https://foo?postgresql_version={postgresql_major_version}.3");
167+
let version = Version::parse("1.2.3")?;
168+
let target = target_triple::TARGET;
169+
let os = consts::OS;
170+
let arch = consts::ARCH;
171+
let names = vec![
172+
format!("foo{target}.tar.gz"),
173+
format!("foo{os}-{arch}.tar.gz"),
174+
format!("foo-{target}.tar"),
175+
format!("foo-{os}-{arch}.tar"),
176+
format!("foo-{os}{arch}.tar.gz"),
177+
];
178+
179+
for name in names {
180+
assert!(!matcher(url.as_str(), name.as_str(), &version)?, "{}", name);
181+
}
182+
Ok(())
183+
}
184+
}

postgresql_extensions/src/repository/portal_corp/matcher.rs

Lines changed: 0 additions & 89 deletions
This file was deleted.
Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
mod matcher;
21
pub mod repository;
32

43
pub const URL: &str = "https://github.com/portalcorp";
5-
6-
pub use matcher::matcher;

postgresql_extensions/src/repository/portal_corp/repository.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
use crate::matcher::zip_matcher;
12
use crate::model::AvailableExtension;
23
use crate::repository::portal_corp::URL;
3-
use crate::repository::{portal_corp, Repository};
4+
use crate::repository::Repository;
45
use crate::Result;
56
use async_trait::async_trait;
7+
use postgresql_archive::get_archive;
68
use postgresql_archive::repository::github::repository::GitHub;
7-
use postgresql_archive::{get_archive, matcher};
89
use semver::{Version, VersionReq};
910
use std::fmt::Debug;
1011
use std::io::Cursor;
@@ -31,7 +32,10 @@ impl PortalCorp {
3132
/// # Errors
3233
/// * If the repository cannot be initialized.
3334
pub fn initialize() -> Result<()> {
34-
matcher::registry::register(|url| Ok(url.starts_with(URL)), portal_corp::matcher)?;
35+
postgresql_archive::matcher::registry::register(
36+
|url| Ok(url.starts_with(URL)),
37+
zip_matcher,
38+
)?;
3539
postgresql_archive::repository::registry::register(
3640
|url| Ok(url.starts_with(URL)),
3741
Box::new(GitHub::new),

0 commit comments

Comments
 (0)