Skip to main content

idt/downloaders/http/
github.rs

1use rootcause::prelude::ResultExt as _;
2
3/// Fetches the latest release tag name for a GitHub repository.
4///
5/// Calls `GET https://api.github.com/repos/{repo}/releases/latest` and
6/// extracts the `tag_name` field from the JSON response.
7///
8/// If the `GITHUB_TOKEN` environment variable is set, it is sent as a
9/// Bearer token to raise the rate limit from 60 to 5 000 requests/hour.
10///
11/// # Errors
12/// - The HTTP request fails or returns a non-success status.
13/// - The response body cannot be parsed as JSON.
14/// - The `tag_name` field is missing from the response.
15pub fn get_latest_release_tag(repo: &str) -> rootcause::Result<String> {
16    let url = format!("https://api.github.com/repos/{repo}/releases/latest");
17
18    let mut req = ureq::get(&url)
19        .header("Accept", "application/vnd.github+json")
20        .header("User-Agent", "Mozilla/5.0");
21
22    if let Ok(token) = std::env::var("GITHUB_TOKEN") {
23        req = req.header("Authorization", &format!("Bearer {token}"));
24    }
25
26    let resp = req
27        .call()
28        .context("error fetching latest release")
29        .attach_with(|| format!("repo={repo:?}"))?;
30
31    let body_str = resp
32        .into_body()
33        .read_to_string()
34        .context("error reading release response body")
35        .attach_with(|| format!("repo={repo:?}"))?;
36
37    let body: serde_json::Value = serde_json::from_str(&body_str)
38        .context("error parsing release JSON")
39        .attach_with(|| format!("repo={repo:?}"))?;
40
41    body.get("tag_name")
42        .and_then(serde_json::Value::as_str)
43        .map(String::from)
44        .ok_or_else(|| rootcause::report!("missing tag_name in release response"))
45        .attach_with(|| format!("repo={repo:?}"))
46}