use std::time::Duration;
const REGISTRY_URL: &str = "https://crates.io/api/v1/crates/fundaia";
#[derive(Debug, serde::Deserialize)]
struct RegistryAnswer {
#[serde(rename = "crate")]
published: PublishedCrate,
}
#[derive(Debug, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
struct PublishedCrate {
newest_version: String,
#[serde(default)]
max_stable_version: Option<String>,
}
pub async fn latest_version(budget: Duration) -> Option<String> {
let http = reqwest::Client::builder()
.timeout(budget)
.user_agent(concat!("fundaia/", env!("CARGO_PKG_VERSION")))
.build()
.ok()?;
let response = http.get(REGISTRY_URL).send().await.ok()?;
if !response.status().is_success() {
return None;
}
let answer: RegistryAnswer = response.json().await.ok()?;
Some(
answer
.published
.max_stable_version
.unwrap_or(answer.published.newest_version),
)
}
#[cfg(test)]
mod tests {
use super::*;
const REGISTRY_PAYLOAD: &str = r#"{
"crate": {
"id": "fundaia",
"name": "fundaia",
"newest_version": "0.4.0-rc1",
"max_stable_version": "0.3.0",
"description": "Command line for the Fundaia deployment platform"
},
"versions": []
}"#;
#[test]
fn it_should_read_the_newest_stable_release_from_the_registry() {
let answer: RegistryAnswer =
serde_json::from_str(REGISTRY_PAYLOAD).expect("the payload is the registry's own");
assert_eq!(answer.published.max_stable_version.unwrap(), "0.3.0");
}
#[test]
fn it_should_ignore_the_fields_the_registry_sends_and_this_never_reads() {
assert!(serde_json::from_str::<RegistryAnswer>(REGISTRY_PAYLOAD).is_ok());
}
#[test]
fn it_should_fall_back_to_the_newest_version_when_nothing_stable_exists() {
let answer: RegistryAnswer = serde_json::from_str(
r#"{"crate":{"newest_version":"0.1.0-alpha1","max_stable_version":null}}"#,
)
.expect("a crate whose every release was a pre-release");
assert_eq!(
answer
.published
.max_stable_version
.unwrap_or(answer.published.newest_version),
"0.1.0-alpha1",
);
}
}