113 lines
3.8 KiB
Rust
113 lines
3.8 KiB
Rust
|
|
//! A Forgejo (or Gitea) instance as a release source: its API root, and
|
||
|
|
//! how to find a repo's latest release there.
|
||
|
|
|
||
|
|
use crate::checker::ReleaseSource;
|
||
|
|
use anyhow::{Context, Result, bail};
|
||
|
|
use serde::Deserialize;
|
||
|
|
|
||
|
|
#[derive(Debug, Clone)]
|
||
|
|
pub struct ForgejoEndpoints {
|
||
|
|
/// The instance's API root, i.e. `<base_url>/api/v1`.
|
||
|
|
pub api: String,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl ForgejoEndpoints {
|
||
|
|
/// `base_url` is the instance's web root, e.g.
|
||
|
|
/// `https://code.austinschaefer.com`; a trailing slash is tolerated.
|
||
|
|
pub fn from_base_url(base_url: &str) -> Self {
|
||
|
|
Self {
|
||
|
|
api: format!("{}/api/v1", base_url.trim_end_matches('/')),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl ReleaseSource for ForgejoEndpoints {
|
||
|
|
/// One call, unlike GitHub's feed-then-confirm dance: Forgejo's
|
||
|
|
/// `releases/latest` already returns only the newest non-draft,
|
||
|
|
/// non-prerelease *release object*, so a stray tag with no release
|
||
|
|
/// behind it (GitHub's scaleway-cli `-dbg1` problem) can't be returned.
|
||
|
|
fn latest_release(&self, client: &reqwest::blocking::Client, repo: &str) -> Result<String> {
|
||
|
|
#[derive(Deserialize)]
|
||
|
|
struct Latest {
|
||
|
|
tag_name: String,
|
||
|
|
}
|
||
|
|
|
||
|
|
let url = format!("{}/repos/{repo}/releases/latest", self.api);
|
||
|
|
let response = client.get(&url).send()?;
|
||
|
|
// Forgejo answers 404 both for an unknown repo and for one with no
|
||
|
|
// releases yet — the common state for a project's very first
|
||
|
|
// release.
|
||
|
|
if response.status() == reqwest::StatusCode::NOT_FOUND {
|
||
|
|
bail!("no published release found at {url} (repo missing, or nothing released yet)");
|
||
|
|
}
|
||
|
|
let latest: Latest = response
|
||
|
|
.error_for_status()
|
||
|
|
.with_context(|| format!("fetching latest release from {url}"))?
|
||
|
|
.json()?;
|
||
|
|
Ok(latest.tag_name)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn api(&self) -> &str {
|
||
|
|
&self.api
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
use super::*;
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn from_base_url_appends_api_v1() {
|
||
|
|
let endpoints = ForgejoEndpoints::from_base_url("https://code.example.com");
|
||
|
|
assert_eq!(endpoints.api(), "https://code.example.com/api/v1");
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn from_base_url_tolerates_trailing_slash() {
|
||
|
|
let endpoints = ForgejoEndpoints::from_base_url("https://code.example.com/");
|
||
|
|
assert_eq!(endpoints.api(), "https://code.example.com/api/v1");
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn latest_release_returns_tag_name() {
|
||
|
|
let mut server = mockito::Server::new();
|
||
|
|
let _latest = server
|
||
|
|
.mock("GET", "/repos/o/r/releases/latest")
|
||
|
|
.with_status(200)
|
||
|
|
.with_body(r#"{"tag_name": "v0.1.0", "assets": []}"#)
|
||
|
|
.create();
|
||
|
|
|
||
|
|
let client = reqwest::blocking::Client::new();
|
||
|
|
let endpoints = ForgejoEndpoints { api: server.url() };
|
||
|
|
assert_eq!(endpoints.latest_release(&client, "o/r").unwrap(), "v0.1.0");
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn latest_release_names_the_no_releases_case() {
|
||
|
|
let mut server = mockito::Server::new();
|
||
|
|
let _latest = server
|
||
|
|
.mock("GET", "/repos/o/r/releases/latest")
|
||
|
|
.with_status(404)
|
||
|
|
.create();
|
||
|
|
|
||
|
|
let client = reqwest::blocking::Client::new();
|
||
|
|
let endpoints = ForgejoEndpoints { api: server.url() };
|
||
|
|
let err = endpoints.latest_release(&client, "o/r").unwrap_err();
|
||
|
|
assert!(err.to_string().contains("no published release"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn latest_release_surfaces_server_errors() {
|
||
|
|
let mut server = mockito::Server::new();
|
||
|
|
let _latest = server
|
||
|
|
.mock("GET", "/repos/o/r/releases/latest")
|
||
|
|
.with_status(500)
|
||
|
|
.create();
|
||
|
|
|
||
|
|
let client = reqwest::blocking::Client::new();
|
||
|
|
let endpoints = ForgejoEndpoints { api: server.url() };
|
||
|
|
let err = endpoints.latest_release(&client, "o/r").unwrap_err();
|
||
|
|
assert!(err.to_string().contains("fetching latest release"));
|
||
|
|
}
|
||
|
|
}
|