use serde_json::{Value, json};
#[must_use]
pub fn self_link(path: &str) -> Value {
json!({"href": path})
}
#[must_use]
pub fn csaf_links(tracking_id: &str) -> Value {
json!({
"self": {"href": format!("/api/v1/csaf/{tracking_id}")},
"collection": {"href": "/api/v1/csaf"},
"validate": {"href": format!("/api/v1/csaf/{tracking_id}/validate")},
"audit_log": {"href": format!("/api/v1/audit-log?tracking_id={tracking_id}")}
})
}
#[must_use]
pub fn collection_links(base: &str, page: usize, per_page: usize, total: usize) -> Value {
let last_page = if total == 0 {
1
} else {
total.div_ceil(per_page)
};
let mut links = json!({
"self": {"href": format!("{base}?page={page}&per_page={per_page}")},
"first": {"href": format!("{base}?page=1&per_page={per_page}")},
"last": {"href": format!("{base}?page={last_page}&per_page={per_page}")}
});
if page < last_page {
links["next"] = json!({"href": format!("{base}?page={}&per_page={per_page}", page + 1)});
}
if page > 1 {
links["prev"] = json!({"href": format!("{base}?page={}&per_page={per_page}", page - 1)});
}
links
}
#[must_use]
pub fn api_root_links() -> Value {
json!({
"self": {"href": "/api/v1"},
"csaf": {"href": "/api/v1/csaf"},
"settings": {"href": "/api/v1/settings"},
"provider_metadata": {"href": "/api/v1/provider-metadata"},
"audit_log": {"href": "/api/v1/audit-log"},
"system_info": {"href": "/api/v1/system/info"},
"health": {"href": "/api/v1/system/health"}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_self_link() {
let link = self_link("/api/v1/csaf");
assert_eq!(link["href"], "/api/v1/csaf");
}
#[test]
fn test_csaf_links() {
let links = csaf_links("ndaal-sa-2026-003");
assert_eq!(links["self"]["href"], "/api/v1/csaf/ndaal-sa-2026-003");
assert_eq!(links["collection"]["href"], "/api/v1/csaf");
assert_eq!(
links["validate"]["href"],
"/api/v1/csaf/ndaal-sa-2026-003/validate"
);
}
#[test]
fn test_csaf_links_all_keys_present() {
let links = csaf_links("ndaal-sa-2026-001");
let obj = links.as_object().expect("should be object");
for key in &["self", "collection", "validate", "audit_log"] {
assert!(obj.contains_key(*key), "missing key: {key}");
}
}
#[test]
fn test_collection_links_first_page() {
let links = collection_links("/api/v1/csaf", 1, 20, 100);
assert!(links.get("prev").is_none());
assert!(links.get("next").is_some());
assert_eq!(links["first"]["href"], "/api/v1/csaf?page=1&per_page=20");
}
#[test]
fn test_collection_links_last_page() {
let links = collection_links("/api/v1/csaf", 5, 20, 100);
assert!(links.get("prev").is_some());
assert!(links.get("next").is_none());
assert_eq!(links["last"]["href"], "/api/v1/csaf?page=5&per_page=20");
}
#[test]
fn test_collection_links_middle_page() {
let links = collection_links("/api/v1/csaf", 3, 20, 100);
assert!(links.get("prev").is_some());
assert!(links.get("next").is_some());
}
#[test]
fn test_collection_links_empty() {
let links = collection_links("/api/v1/csaf", 1, 20, 0);
assert!(links.get("next").is_none());
}
#[test]
fn test_collection_links_single_page() {
let links = collection_links("/api/v1/csaf", 1, 20, 15);
assert!(links.get("next").is_none());
assert!(links.get("prev").is_none());
assert_eq!(links["last"]["href"], "/api/v1/csaf?page=1&per_page=20");
}
#[test]
fn test_api_root_links() {
let links = api_root_links();
let obj = links.as_object().expect("should be object");
for key in &[
"self",
"csaf",
"settings",
"provider_metadata",
"audit_log",
"system_info",
"health",
] {
assert!(obj.contains_key(*key), "missing key: {key}");
}
}
}