use anyhow::{Context, Result};
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct PublishStatus {
pub exists: bool,
#[allow(dead_code)]
pub version: String,
#[allow(dead_code)]
pub target: String,
#[serde(default)]
pub platforms: Vec<String>,
}
fn build_versions_url(control_plane_url: &str, name: &str, version: &str, target: &str) -> String {
format!(
"{}/api/catalog/binaries/{}/versions/{}?target={}",
control_plane_url.trim_end_matches('/'),
name,
version,
target
)
}
pub async fn check_published_version(
control_plane_url: &str,
name: &str,
version: &str,
target: &str,
api_key: Option<&str>,
user_id: Option<&str>,
) -> Result<PublishStatus> {
let (api_key, user_id) = match (api_key, user_id) {
(Some(key), Some(id)) if !key.is_empty() => (key, id),
_ => anyhow::bail!("No credentials configured"),
};
let url = build_versions_url(control_plane_url, name, version, target);
let client = reqwest::Client::new();
let response = client
.get(&url)
.header("Authorization", format!("Bearer {}", api_key))
.header("X-User-ID", user_id)
.send()
.await
.context("Failed to reach registry")?;
let status = response.status();
if !status.is_success() {
let body = response.text().await.unwrap_or_default();
anyhow::bail!("Publish status check failed with status {}: {}", status, body);
}
response
.json::<PublishStatus>()
.await
.context("Failed to parse publish status response")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_versions_url_includes_target_query_param() {
let url = build_versions_url("https://api.example.com", "my-robot", "1.2.3", "remote");
assert_eq!(
url,
"https://api.example.com/api/catalog/binaries/my-robot/versions/1.2.3?target=remote"
);
}
#[test]
fn build_versions_url_strips_trailing_slash_from_base() {
let url = build_versions_url("https://api.example.com/", "my-robot", "1.2.3", "robot");
assert!(url.starts_with("https://api.example.com/api/catalog/binaries/"));
assert!(!url.contains("com//api"));
}
#[tokio::test]
async fn check_published_version_errors_without_credentials() {
let err = check_published_version("https://api.example.com", "my-robot", "1.2.3", "robot", None, None)
.await
.unwrap_err();
assert!(err.to_string().contains("No credentials configured"));
}
#[tokio::test]
async fn check_published_version_errors_with_empty_api_key() {
let err = check_published_version(
"https://api.example.com",
"my-robot",
"1.2.3",
"robot",
Some(""),
Some("user-1"),
)
.await
.unwrap_err();
assert!(err.to_string().contains("No credentials configured"));
}
}