use serde::{Deserialize, Serialize};
pub const LIT_CONTEXT: &str = "https://lit.nervosys.com/api/v1/context.jsonld";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiLink {
pub rel: String,
pub href: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub method: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiEnvelope<T: Serialize> {
#[serde(rename = "@context")]
pub context: String,
#[serde(rename = "@type")]
pub resource_type: String,
#[serde(rename = "_links")]
pub links: Vec<ApiLink>,
pub data: T,
}
impl<T: Serialize> ApiEnvelope<T> {
pub fn wrap(resource_type: &str, data: T, links: Vec<ApiLink>) -> Self {
ApiEnvelope {
context: LIT_CONTEXT.to_string(),
resource_type: resource_type.to_string(),
links,
data,
}
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
}
pub fn repo_links(repo_name: &str) -> Vec<ApiLink> {
vec![
ApiLink {
rel: "self".to_string(),
href: format!("/repos/{}", repo_name),
method: Some("GET".to_string()),
title: Some("This repository".to_string()),
},
ApiLink {
rel: "commits".to_string(),
href: format!("/repos/{}/commits", repo_name),
method: Some("GET".to_string()),
title: Some("List commits".to_string()),
},
ApiLink {
rel: "branches".to_string(),
href: format!("/repos/{}/branches", repo_name),
method: Some("GET".to_string()),
title: Some("List branches".to_string()),
},
ApiLink {
rel: "issues".to_string(),
href: format!("/repos/{}/issues", repo_name),
method: Some("GET".to_string()),
title: Some("List issues".to_string()),
},
ApiLink {
rel: "prs".to_string(),
href: format!("/repos/{}/prs", repo_name),
method: Some("GET".to_string()),
title: Some("List pull requests".to_string()),
},
ApiLink {
rel: "peers".to_string(),
href: format!("/repos/{}/peers", repo_name),
method: Some("GET".to_string()),
title: Some("List federated peers".to_string()),
},
ApiLink {
rel: "events".to_string(),
href: format!("/repos/{}/events", repo_name),
method: Some("GET".to_string()),
title: Some("Event stream".to_string()),
},
]
}
pub fn commit_links(repo_name: &str, commit_hash: &str) -> Vec<ApiLink> {
vec![
ApiLink {
rel: "self".to_string(),
href: format!("/repos/{}/commits/{}", repo_name, commit_hash),
method: Some("GET".to_string()),
title: Some("This commit".to_string()),
},
ApiLink {
rel: "tree".to_string(),
href: format!("/repos/{}/tree/{}", repo_name, commit_hash),
method: Some("GET".to_string()),
title: Some("File tree at this commit".to_string()),
},
ApiLink {
rel: "parent".to_string(),
href: format!("/repos/{}/commits/{}~1", repo_name, commit_hash),
method: Some("GET".to_string()),
title: Some("Parent commit".to_string()),
},
ApiLink {
rel: "diff".to_string(),
href: format!("/repos/{}/diff/{}", repo_name, commit_hash),
method: Some("GET".to_string()),
title: Some("Diff for this commit".to_string()),
},
]
}
pub fn identity_links(did: &str) -> Vec<ApiLink> {
let safe_did = did.replace(':', "_");
vec![
ApiLink {
rel: "self".to_string(),
href: format!("/identities/{}", safe_did),
method: Some("GET".to_string()),
title: Some("This identity".to_string()),
},
ApiLink {
rel: "trust".to_string(),
href: format!("/identities/{}/trust", safe_did),
method: Some("GET".to_string()),
title: Some("Trust score".to_string()),
},
ApiLink {
rel: "tokens".to_string(),
href: format!("/identities/{}/tokens", safe_did),
method: Some("GET".to_string()),
title: Some("UCAN tokens".to_string()),
},
ApiLink {
rel: "delegations".to_string(),
href: format!("/identities/{}/delegations", safe_did),
method: Some("GET".to_string()),
title: Some("Task delegations".to_string()),
},
]
}
pub fn api_root_links() -> Vec<ApiLink> {
vec![
ApiLink {
rel: "self".to_string(),
href: "/".to_string(),
method: Some("GET".to_string()),
title: Some("API root".to_string()),
},
ApiLink {
rel: "repos".to_string(),
href: "/repos".to_string(),
method: Some("GET".to_string()),
title: Some("List repositories".to_string()),
},
ApiLink {
rel: "identities".to_string(),
href: "/identities".to_string(),
method: Some("GET".to_string()),
title: Some("List identities".to_string()),
},
ApiLink {
rel: "events".to_string(),
href: "/events".to_string(),
method: Some("GET".to_string()),
title: Some("Global event stream".to_string()),
},
ApiLink {
rel: "federation".to_string(),
href: "/federation".to_string(),
method: Some("GET".to_string()),
title: Some("Federation status".to_string()),
},
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_api_envelope() {
let data = serde_json::json!({"name": "test-repo"});
let envelope = ApiEnvelope::wrap("Repository", data, repo_links("test-repo"));
let json = envelope.to_json().unwrap();
assert!(json.contains("@context"));
assert!(json.contains("@type"));
assert!(json.contains("_links"));
assert!(json.contains("test-repo"));
}
#[test]
fn test_repo_links() {
let links = repo_links("myrepo");
assert!(links.iter().any(|l| l.rel == "self"));
assert!(links.iter().any(|l| l.rel == "commits"));
assert!(links.iter().any(|l| l.rel == "issues"));
}
#[test]
fn test_api_root() {
let links = api_root_links();
assert!(links.iter().any(|l| l.rel == "repos"));
assert!(links.iter().any(|l| l.rel == "identities"));
assert!(links.iter().any(|l| l.rel == "federation"));
}
}