main
rs 102 lines 3.01 KB
Raw
1 //! Repositories and their webhooks.
2
3 use crate::{
4 client::Client,
5 error::Result,
6 models::{GitCredential, Hook, Repository},
7 };
8
9 /// Fields accepted by `POST /api/v1/repos`.
10 #[derive(Debug, Clone, serde::Serialize)]
11 pub struct NewRepository {
12 pub name: String,
13 pub is_private: bool,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub description: Option<String>,
16 }
17
18 impl NewRepository {
19 pub fn new(name: impl Into<String>) -> Self {
20 Self {
21 name: name.into(),
22 is_private: false,
23 description: None,
24 }
25 }
26
27 pub fn private(mut self, private: bool) -> Self {
28 self.is_private = private;
29 self
30 }
31
32 pub fn description(mut self, description: Option<String>) -> Self {
33 self.description = description.filter(|d| !d.trim().is_empty());
34 self
35 }
36 }
37
38 impl Client {
39 /// `GET /api/v1/repos` — the signed-in user's repositories, newest first.
40 pub async fn repos(&self) -> Result<Vec<Repository>> {
41 self.get("repos").await
42 }
43
44 /// `POST /api/v1/repos` — creates an empty bare repo to push to.
45 pub async fn create_repo(&self, repo: &NewRepository) -> Result<Repository> {
46 self.post("repos", repo).await
47 }
48
49 /// `GET /api/v1/repos/:owner/:repo/hooks`
50 pub async fn hooks(&self, owner: &str, repo: &str) -> Result<Vec<Hook>> {
51 self.get(&format!("repos/{owner}/{repo}/hooks")).await
52 }
53
54 /// `POST /api/v1/repos/:owner/:repo/hooks`
55 ///
56 /// The response is the only place the signing secret ever appears.
57 pub async fn create_hook(
58 &self,
59 owner: &str,
60 repo: &str,
61 url: &str,
62 events: &[String],
63 ) -> Result<Hook> {
64 let body = serde_json::json!({ "url": url, "events": events });
65 self.post(&format!("repos/{owner}/{repo}/hooks"), &body)
66 .await
67 }
68
69 /// `DELETE /api/v1/repos/:owner/:repo/hooks/:id`
70 pub async fn delete_hook(&self, owner: &str, repo: &str, id: i64) -> Result<()> {
71 self.delete(&format!("repos/{owner}/{repo}/hooks/{id}"))
72 .await
73 }
74
75 /// `POST /api/v1/git_credentials` — mints a 1-hour, read-scoped git
76 /// password. This, not the account token, is what git clones with.
77 pub async fn git_credential(&self) -> Result<GitCredential> {
78 self.post("git_credentials", &serde_json::json!({})).await
79 }
80 }
81
82 #[cfg(test)]
83 mod tests {
84 use super::*;
85
86 #[test]
87 fn a_blank_description_is_omitted_rather_than_sent_as_empty() {
88 let repo = NewRepository::new("demo").description(Some(" ".into()));
89 let json = serde_json::to_value(&repo).unwrap();
90 assert!(json.get("description").is_none());
91 }
92
93 #[test]
94 fn a_real_description_is_sent() {
95 let repo = NewRepository::new("demo")
96 .private(true)
97 .description(Some("A demo repo".into()));
98 let json = serde_json::to_value(&repo).unwrap();
99 assert_eq!(json["description"], "A demo repo");
100 assert_eq!(json["is_private"], true);
101 }
102 }