Skip to content

Commit 090e0bd

Browse files
Merge pull request #113 from theseus-rs/refactor-extension-matchers
refactor!: refactor extension matchers
2 parents 3cdd9c5 + 62edcd5 commit 090e0bd

File tree

11 files changed

+250
-309
lines changed

11 files changed

+250
-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: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
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+
/// .tar.gz asset matcher that matches the asset name to the postgresql major version, target triple
9+
/// or OS/CPU architecture.
10+
///
11+
/// # Errors
12+
/// * If the asset matcher fails.
13+
#[allow(clippy::case_sensitive_file_extension_comparisons)]
14+
pub fn tar_gz_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(".tar.gz"))
20+
}
21+
22+
/// .zip asset matcher that matches the asset name to the postgresql major version, target triple or
23+
/// OS/CPU architecture.
24+
///
25+
/// # Errors
26+
/// * If the asset matcher fails.
27+
#[allow(clippy::case_sensitive_file_extension_comparisons)]
28+
pub fn zip_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(".zip"))
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_tar_gz_matcher() -> 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+
145+
let valid_name = format!("postgresql-pg{postgresql_major_version}-{target}.tar.gz");
146+
let invalid_name = format!("postgresql-pg{postgresql_major_version}-{target}.zip");
147+
assert!(
148+
tar_gz_matcher(url.as_str(), valid_name.as_str(), &version)?,
149+
"{}",
150+
valid_name
151+
);
152+
assert!(
153+
!tar_gz_matcher(url.as_str(), invalid_name.as_str(), &version)?,
154+
"{}",
155+
invalid_name
156+
);
157+
Ok(())
158+
}
159+
160+
#[test]
161+
fn test_zip_matcher() -> Result<()> {
162+
let postgresql_major_version = 16;
163+
let url = format!("https://foo?postgresql_version={postgresql_major_version}.3");
164+
let version = Version::parse("1.2.3")?;
165+
let target = target_triple::TARGET;
166+
167+
let valid_name = format!("postgresql-pg{postgresql_major_version}-{target}.zip");
168+
let invalid_name = format!("postgresql-pg{postgresql_major_version}-{target}.tar.gz");
169+
assert!(
170+
zip_matcher(url.as_str(), valid_name.as_str(), &version)?,
171+
"{}",
172+
valid_name
173+
);
174+
assert!(
175+
!zip_matcher(url.as_str(), invalid_name.as_str(), &version)?,
176+
"{}",
177+
invalid_name
178+
);
179+
Ok(())
180+
}
181+
182+
#[test]
183+
fn test_matcher_success() -> Result<()> {
184+
let postgresql_major_version = 16;
185+
let url = format!("https://foo?postgresql_version={postgresql_major_version}.3");
186+
let version = Version::parse("1.2.3")?;
187+
let target = target_triple::TARGET;
188+
let os = consts::OS;
189+
let arch = consts::ARCH;
190+
let names = vec![
191+
format!("postgresql-pg{postgresql_major_version}-{target}.zip"),
192+
format!("postgresql-pg{postgresql_major_version}-{os}-{arch}.zip"),
193+
format!("postgresql-pg{postgresql_major_version}-{target}.tar.gz"),
194+
format!("postgresql-pg{postgresql_major_version}-{os}-{arch}.tar.gz"),
195+
format!("foo.{target}.pg{postgresql_major_version}.tar.gz"),
196+
format!("foo.{os}.{arch}.pg{postgresql_major_version}.tar.gz"),
197+
format!("foo-{arch}-{os}-pg{postgresql_major_version}.tar.gz"),
198+
format!("foo_{arch}_{os}_pg{postgresql_major_version}.tar.gz"),
199+
];
200+
201+
for name in names {
202+
assert!(matcher(url.as_str(), name.as_str(), &version)?, "{}", name);
203+
}
204+
Ok(())
205+
}
206+
207+
#[test]
208+
fn test_matcher_errors() -> Result<()> {
209+
let postgresql_major_version = 16;
210+
let url = format!("https://foo?postgresql_version={postgresql_major_version}.3");
211+
let version = Version::parse("1.2.3")?;
212+
let target = target_triple::TARGET;
213+
let os = consts::OS;
214+
let arch = consts::ARCH;
215+
let names = vec![
216+
format!("foo{target}.tar.gz"),
217+
format!("foo{os}-{arch}.tar.gz"),
218+
format!("foo-{target}.tar"),
219+
format!("foo-{os}-{arch}.tar"),
220+
format!("foo-{os}{arch}.tar.gz"),
221+
];
222+
223+
for name in names {
224+
assert!(!matcher(url.as_str(), name.as_str(), &version)?, "{}", name);
225+
}
226+
Ok(())
227+
}
228+
}

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)