use std::process::Command;
use crate::platform::Platform;
use crate::release::PinnedRelease;
use crate::url::build_download_url;
use crate::Error;
const PLATFORMS: &[Platform] = &[
Platform::MacosArm64,
Platform::MacosX86_64,
Platform::LinuxX86_64,
Platform::WindowsX86_64,
];
#[derive(Debug, Clone)]
pub struct VerifyOutcome {
pub platform: Platform,
pub url: String,
pub status: Option<u16>,
pub error: Option<String>,
}
impl VerifyOutcome {
#[must_use]
pub fn ok(&self) -> bool {
matches!(self.status, Some(s) if (200..400).contains(&s))
}
}
pub fn verify_release(release: &PinnedRelease) -> Result<Vec<VerifyOutcome>, Error> {
let mut out = Vec::with_capacity(PLATFORMS.len());
for &platform in PLATFORMS {
let url = build_download_url(release, platform);
let outcome = curl_head(&url);
out.push(VerifyOutcome {
platform,
url,
status: outcome.0,
error: outcome.1,
});
}
Ok(out)
}
fn curl_head(url: &str) -> (Option<u16>, Option<String>) {
let result = Command::new("curl")
.args(["--head", "--silent", "--show-error", "--location"])
.arg(url)
.output();
match result {
Err(e) => (None, Some(format!("failed to spawn curl: {e}"))),
Ok(output) => {
if !output.status.success() && output.stdout.is_empty() {
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
return (None, Some(stderr));
}
let stdout = String::from_utf8_lossy(&output.stdout);
let status = stdout
.lines()
.filter_map(|line| {
let line = line.trim();
if line.starts_with("HTTP/") {
line.split_whitespace().nth(1)?.parse::<u16>().ok()
} else {
None
}
})
.next_back();
match status {
Some(s) => (Some(s), None),
None => (
None,
Some(format!(
"could not parse HTTP status from curl output: {}",
stdout.chars().take(200).collect::<String>()
)),
),
}
}
}
}