use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct Release {
pub tag_name: String,
pub body: Option<String>,
pub assets: Vec<Asset>,
}
#[derive(Debug, Deserialize)]
pub struct Asset {
pub name: String,
pub browser_download_url: String,
}
pub async fn fetch_latest_release(owner: &str, repo: &str) -> anyhow::Result<Release> {
let url = format!(
"https://api.github.com/repos/{}/{}/releases/latest",
owner, repo
);
let client = reqwest::Client::builder()
.user_agent("a3s-updater/0.1")
.build()
.map_err(|e| anyhow::anyhow!("Failed to build HTTP client: {}", e))?;
let response = client
.get(&url)
.header("Accept", "application/vnd.github+json")
.send()
.await
.map_err(|e| anyhow::anyhow!("Failed to fetch latest release from {}: {}", url, e))?;
if !response.status().is_success() {
let status = response.status();
let body = response.text().await.unwrap_or_default();
return Err(anyhow::anyhow!(
"GitHub API returned {} for {}: {}",
status,
url,
body
));
}
let release: Release = response
.json()
.await
.map_err(|e| anyhow::anyhow!("Failed to parse GitHub release JSON: {}", e))?;
Ok(release)
}
pub fn parse_version(tag: &str) -> anyhow::Result<semver::Version> {
let version_str = tag.strip_prefix('v').unwrap_or(tag);
semver::Version::parse(version_str)
.map_err(|e| anyhow::anyhow!("Failed to parse version '{}': {}", tag, e))
}
pub fn asset_name(binary_name: &str, version: &str, os: &str, arch: &str) -> String {
format!("{}-{}-{}-{}.tar.gz", binary_name, version, os, arch)
}
pub fn find_matching_asset<'a>(
release: &'a Release,
binary_name: &str,
os: &str,
arch: &str,
) -> Option<&'a Asset> {
let version = parse_version(&release.tag_name).ok()?;
let expected = asset_name(binary_name, &version.to_string(), os, arch);
release.assets.iter().find(|a| a.name == expected)
}