1use serde::{Deserialize, Serialize};
7
8pub const LIT_CONTEXT: &str = "https://lit.nervosys.com/api/v1/context.jsonld";
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ApiLink {
14 pub rel: String,
16 pub href: String,
18 #[serde(skip_serializing_if = "Option::is_none")]
20 pub method: Option<String>,
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub title: Option<String>,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct ApiEnvelope<T: Serialize> {
29 #[serde(rename = "@context")]
31 pub context: String,
32 #[serde(rename = "@type")]
34 pub resource_type: String,
35 #[serde(rename = "_links")]
37 pub links: Vec<ApiLink>,
38 pub data: T,
40}
41
42impl<T: Serialize> ApiEnvelope<T> {
43 pub fn wrap(resource_type: &str, data: T, links: Vec<ApiLink>) -> Self {
45 ApiEnvelope {
46 context: LIT_CONTEXT.to_string(),
47 resource_type: resource_type.to_string(),
48 links,
49 data,
50 }
51 }
52
53 pub fn to_json(&self) -> Result<String, serde_json::Error> {
55 serde_json::to_string_pretty(self)
56 }
57}
58
59pub fn repo_links(repo_name: &str) -> Vec<ApiLink> {
61 vec![
62 ApiLink {
63 rel: "self".to_string(),
64 href: format!("/repos/{}", repo_name),
65 method: Some("GET".to_string()),
66 title: Some("This repository".to_string()),
67 },
68 ApiLink {
69 rel: "commits".to_string(),
70 href: format!("/repos/{}/commits", repo_name),
71 method: Some("GET".to_string()),
72 title: Some("List commits".to_string()),
73 },
74 ApiLink {
75 rel: "branches".to_string(),
76 href: format!("/repos/{}/branches", repo_name),
77 method: Some("GET".to_string()),
78 title: Some("List branches".to_string()),
79 },
80 ApiLink {
81 rel: "issues".to_string(),
82 href: format!("/repos/{}/issues", repo_name),
83 method: Some("GET".to_string()),
84 title: Some("List issues".to_string()),
85 },
86 ApiLink {
87 rel: "prs".to_string(),
88 href: format!("/repos/{}/prs", repo_name),
89 method: Some("GET".to_string()),
90 title: Some("List pull requests".to_string()),
91 },
92 ApiLink {
93 rel: "peers".to_string(),
94 href: format!("/repos/{}/peers", repo_name),
95 method: Some("GET".to_string()),
96 title: Some("List federated peers".to_string()),
97 },
98 ApiLink {
99 rel: "events".to_string(),
100 href: format!("/repos/{}/events", repo_name),
101 method: Some("GET".to_string()),
102 title: Some("Event stream".to_string()),
103 },
104 ]
105}
106
107pub fn commit_links(repo_name: &str, commit_hash: &str) -> Vec<ApiLink> {
109 vec![
110 ApiLink {
111 rel: "self".to_string(),
112 href: format!("/repos/{}/commits/{}", repo_name, commit_hash),
113 method: Some("GET".to_string()),
114 title: Some("This commit".to_string()),
115 },
116 ApiLink {
117 rel: "tree".to_string(),
118 href: format!("/repos/{}/tree/{}", repo_name, commit_hash),
119 method: Some("GET".to_string()),
120 title: Some("File tree at this commit".to_string()),
121 },
122 ApiLink {
123 rel: "parent".to_string(),
124 href: format!("/repos/{}/commits/{}~1", repo_name, commit_hash),
125 method: Some("GET".to_string()),
126 title: Some("Parent commit".to_string()),
127 },
128 ApiLink {
129 rel: "diff".to_string(),
130 href: format!("/repos/{}/diff/{}", repo_name, commit_hash),
131 method: Some("GET".to_string()),
132 title: Some("Diff for this commit".to_string()),
133 },
134 ]
135}
136
137pub fn identity_links(did: &str) -> Vec<ApiLink> {
139 let safe_did = did.replace(':', "_");
140 vec![
141 ApiLink {
142 rel: "self".to_string(),
143 href: format!("/identities/{}", safe_did),
144 method: Some("GET".to_string()),
145 title: Some("This identity".to_string()),
146 },
147 ApiLink {
148 rel: "trust".to_string(),
149 href: format!("/identities/{}/trust", safe_did),
150 method: Some("GET".to_string()),
151 title: Some("Trust score".to_string()),
152 },
153 ApiLink {
154 rel: "tokens".to_string(),
155 href: format!("/identities/{}/tokens", safe_did),
156 method: Some("GET".to_string()),
157 title: Some("UCAN tokens".to_string()),
158 },
159 ApiLink {
160 rel: "delegations".to_string(),
161 href: format!("/identities/{}/delegations", safe_did),
162 method: Some("GET".to_string()),
163 title: Some("Task delegations".to_string()),
164 },
165 ]
166}
167
168pub fn api_root_links() -> Vec<ApiLink> {
170 vec![
171 ApiLink {
172 rel: "self".to_string(),
173 href: "/".to_string(),
174 method: Some("GET".to_string()),
175 title: Some("API root".to_string()),
176 },
177 ApiLink {
178 rel: "repos".to_string(),
179 href: "/repos".to_string(),
180 method: Some("GET".to_string()),
181 title: Some("List repositories".to_string()),
182 },
183 ApiLink {
184 rel: "identities".to_string(),
185 href: "/identities".to_string(),
186 method: Some("GET".to_string()),
187 title: Some("List identities".to_string()),
188 },
189 ApiLink {
190 rel: "events".to_string(),
191 href: "/events".to_string(),
192 method: Some("GET".to_string()),
193 title: Some("Global event stream".to_string()),
194 },
195 ApiLink {
196 rel: "federation".to_string(),
197 href: "/federation".to_string(),
198 method: Some("GET".to_string()),
199 title: Some("Federation status".to_string()),
200 },
201 ]
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn test_api_envelope() {
210 let data = serde_json::json!({"name": "test-repo"});
211 let envelope = ApiEnvelope::wrap("Repository", data, repo_links("test-repo"));
212 let json = envelope.to_json().unwrap();
213 assert!(json.contains("@context"));
214 assert!(json.contains("@type"));
215 assert!(json.contains("_links"));
216 assert!(json.contains("test-repo"));
217 }
218
219 #[test]
220 fn test_repo_links() {
221 let links = repo_links("myrepo");
222 assert!(links.iter().any(|l| l.rel == "self"));
223 assert!(links.iter().any(|l| l.rel == "commits"));
224 assert!(links.iter().any(|l| l.rel == "issues"));
225 }
226
227 #[test]
228 fn test_api_root() {
229 let links = api_root_links();
230 assert!(links.iter().any(|l| l.rel == "repos"));
231 assert!(links.iter().any(|l| l.rel == "identities"));
232 assert!(links.iter().any(|l| l.rel == "federation"));
233 }
234}