use std::path::Path;
use tokio::io::AsyncWriteExt;
use crate::pg_enums::{Architecture, OperationSystem};
use crate::pg_errors::Error;
use crate::pg_errors::Result;
#[derive(Debug, Copy, Clone)]
pub struct PostgresVersion(pub &'static str);
pub const PG_V18: PostgresVersion = PostgresVersion("18.2.0");
pub const PG_V17: PostgresVersion = PostgresVersion("17.8.0");
pub const PG_V16: PostgresVersion = PostgresVersion("16.12.0");
pub const PG_V15: PostgresVersion = PostgresVersion("15.16.0");
pub const PG_V14: PostgresVersion = PostgresVersion("14.21.0");
pub const PG_V13: PostgresVersion = PostgresVersion("13.23.0");
pub const PG_V12: PostgresVersion = PostgresVersion("12.22.0");
pub const PG_V11: PostgresVersion = PostgresVersion("11.22.1");
pub const PG_V10: PostgresVersion = PostgresVersion("10.23.0");
#[derive(Debug, Clone)]
pub struct PgFetchSettings {
pub host: String,
pub operating_system: OperationSystem,
pub architecture: Architecture,
pub version: PostgresVersion,
}
impl Default for PgFetchSettings {
fn default() -> Self {
PgFetchSettings {
host: "https://repo1.maven.org".to_string(),
operating_system: OperationSystem::default(),
architecture: Architecture::default(),
version: PG_V18,
}
}
}
impl PgFetchSettings {
pub fn platform(&self) -> String {
let os = self.operating_system.to_string();
let arch = if self.operating_system == OperationSystem::AlpineLinux {
format!("{}-alpine", self.architecture)
} else {
self.architecture.to_string()
};
format!("{}-{}", os, arch)
}
async fn start_download(&self) -> Result<reqwest::Response> {
let platform = self.platform();
let version = self.version.0;
let download_url = format!(
"{}/maven2/io/zonky/test/postgres/embedded-postgres-binaries-{}/{}/embedded-postgres-binaries-{}-{}.jar",
&self.host,
&platform,
version,
&platform,
version
);
let response = reqwest::get(download_url)
.await
.map_err(|e| Error::DownloadFailure(e.to_string()))?;
let status = response.status();
if !status.is_success() {
return Err(Error::DownloadFailure(format!(
"HTTP {status} fetching PostgreSQL {version} for platform '{platform}'. \
This version may not be available for the current OS/architecture. \
Note: darwin-arm64v8 (Apple Silicon) only has binaries for PG 14 and newer.",
)));
}
Ok(response)
}
pub async fn fetch_postgres(&self) -> Result<Vec<u8>> {
let response = self.start_download().await?;
let content = response
.bytes()
.await
.map_err(|e| Error::ConversionFailure(e.to_string()))?;
log::debug!("Downloaded {} bytes", content.len());
log::trace!(
"First 1024 bytes: {:?}",
&String::from_utf8_lossy(&content[..content.len().min(1024)])
);
Ok(content.to_vec())
}
pub(crate) async fn fetch_postgres_to_file(&self, zip_path: &Path) -> Result<()> {
let mut response = self.start_download().await?;
let mut file = tokio::fs::File::create(zip_path)
.await
.map_err(|e| Error::WriteFileError(e.to_string()))?;
let mut total = 0u64;
while let Some(chunk) = response
.chunk()
.await
.map_err(|e| Error::ConversionFailure(e.to_string()))?
{
file.write_all(&chunk)
.await
.map_err(|e| Error::WriteFileError(e.to_string()))?;
total += chunk.len() as u64;
}
file.sync_data()
.await
.map_err(|e| Error::WriteFileError(e.to_string()))?;
log::debug!("Downloaded and wrote {} bytes to disk", total);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn fetch_postgres() -> Result<()> {
let pg_settings = PgFetchSettings::default();
let content = pg_settings.fetch_postgres().await?;
assert!(!content.is_empty(), "downloaded content should not be empty");
Ok(())
}
#[tokio::test]
#[ignore]
async fn all_versions_downloadable() -> Result<()> {
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
let versions: &[(&str, PostgresVersion)] = &[
("PG_V10", PG_V10),
("PG_V11", PG_V11),
("PG_V12", PG_V12),
("PG_V13", PG_V13),
("PG_V14", PG_V14),
("PG_V15", PG_V15),
("PG_V16", PG_V16),
("PG_V17", PG_V17),
("PG_V18", PG_V18),
];
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
let versions: &[(&str, PostgresVersion)] = &[
("PG_V14", PG_V14),
("PG_V15", PG_V15),
("PG_V16", PG_V16),
("PG_V17", PG_V17),
("PG_V18", PG_V18),
];
for (name, version) in versions {
let settings = PgFetchSettings {
version: *version,
..Default::default()
};
let bytes = settings.fetch_postgres().await?;
println!("{name} ({}): {} bytes", version.0, bytes.len());
assert!(
bytes.len() > 1_000_000,
"{name} ({}) returned only {} bytes — likely missing for platform '{}'",
version.0,
bytes.len(),
settings.platform(),
);
}
Ok(())
}
}