git_cliff_core/remote/
gitea.rs

1use reqwest_middleware::ClientWithMiddleware;
2use serde::{Deserialize, Serialize};
3
4use super::*;
5use crate::config::Remote;
6use crate::error::*;
7
8/// Log message to show while fetching data from Gitea.
9pub const START_FETCHING_MSG: &str = "Retrieving data from Gitea...";
10
11/// Log message to show when done fetching from Gitea.
12pub const FINISHED_FETCHING_MSG: &str = "Done fetching Gitea data.";
13
14/// Template variables related to this remote.
15pub(crate) const TEMPLATE_VARIABLES: &[&str] = &["gitea", "commit.gitea", "commit.remote"];
16
17/// Representation of a single commit.
18#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
19pub struct GiteaCommit {
20    /// SHA.
21    pub sha: String,
22    /// Author of the commit.
23    pub author: Option<GiteaCommitAuthor>,
24    /// Timestamp of the commit.
25    pub created: String,
26}
27
28impl RemoteCommit for GiteaCommit {
29    fn id(&self) -> String {
30        self.sha.clone()
31    }
32
33    fn username(&self) -> Option<String> {
34        self.author.clone().and_then(|v| v.login)
35    }
36
37    fn timestamp(&self) -> Option<i64> {
38        Some(self.convert_to_unix_timestamp(self.created.clone().as_str()))
39    }
40}
41
42impl RemoteEntry for GiteaCommit {
43    fn url(_id: i64, api_url: &str, remote: &Remote, ref_name: Option<&str>, page: i32) -> String {
44        let mut url = format!(
45            "{}/api/v1/repos/{}/{}/commits?limit={MAX_PAGE_SIZE}&page={page}",
46            api_url, remote.owner, remote.repo
47        );
48
49        if let Some(ref_name) = ref_name {
50            url.push_str(&format!("&sha={}", ref_name));
51        }
52
53        url
54    }
55
56    fn buffer_size() -> usize {
57        10
58    }
59
60    fn early_exit(&self) -> bool {
61        false
62    }
63}
64
65/// Author of the commit.
66#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
67pub struct GiteaCommitAuthor {
68    /// Username.
69    pub login: Option<String>,
70}
71
72/// Label of the pull request.
73#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct PullRequestLabel {
76    /// Name of the label.
77    pub name: String,
78}
79
80/// Representation of a single pull request.
81#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
82pub struct GiteaPullRequest {
83    /// Pull request number.
84    pub number: i64,
85    /// Pull request title.
86    pub title: Option<String>,
87    /// SHA of the merge commit.
88    pub merge_commit_sha: Option<String>,
89    /// Labels of the pull request.
90    pub labels: Vec<PullRequestLabel>,
91}
92
93impl RemotePullRequest for GiteaPullRequest {
94    fn number(&self) -> i64 {
95        self.number
96    }
97
98    fn title(&self) -> Option<String> {
99        self.title.clone()
100    }
101
102    fn labels(&self) -> Vec<String> {
103        self.labels.iter().map(|v| v.name.clone()).collect()
104    }
105
106    fn merge_commit(&self) -> Option<String> {
107        self.merge_commit_sha.clone()
108    }
109}
110
111impl RemoteEntry for GiteaPullRequest {
112    fn url(_id: i64, api_url: &str, remote: &Remote, _ref_name: Option<&str>, page: i32) -> String {
113        format!(
114            "{}/api/v1/repos/{}/{}/pulls?limit={MAX_PAGE_SIZE}&page={page}&state=closed",
115            api_url, remote.owner, remote.repo
116        )
117    }
118
119    fn buffer_size() -> usize {
120        5
121    }
122
123    fn early_exit(&self) -> bool {
124        false
125    }
126}
127
128/// HTTP client for handling Gitea REST API requests.
129#[derive(Debug, Clone)]
130pub struct GiteaClient {
131    /// Remote.
132    remote: Remote,
133    /// HTTP client.
134    client: ClientWithMiddleware,
135}
136
137/// Constructs a Gitea client from the remote configuration.
138impl TryFrom<Remote> for GiteaClient {
139    type Error = Error;
140    fn try_from(remote: Remote) -> Result<Self> {
141        Ok(Self {
142            client: remote.create_client("application/json")?,
143            remote,
144        })
145    }
146}
147
148impl RemoteClient for GiteaClient {
149    const API_URL: &'static str = "https://codeberg.org";
150    const API_URL_ENV: &'static str = "GITEA_API_URL";
151
152    fn remote(&self) -> Remote {
153        self.remote.clone()
154    }
155
156    fn client(&self) -> ClientWithMiddleware {
157        self.client.clone()
158    }
159}
160
161impl GiteaClient {
162    /// Fetches the Gitea API and returns the commits.
163    pub async fn get_commits(&self, ref_name: Option<&str>) -> Result<Vec<Box<dyn RemoteCommit>>> {
164        Ok(self
165            .fetch::<GiteaCommit>(0, ref_name)
166            .await?
167            .into_iter()
168            .map(|v| Box::new(v) as Box<dyn RemoteCommit>)
169            .collect())
170    }
171
172    /// Fetches the Gitea API and returns the pull requests.
173    pub async fn get_pull_requests(
174        &self,
175        ref_name: Option<&str>,
176    ) -> Result<Vec<Box<dyn RemotePullRequest>>> {
177        Ok(self
178            .fetch::<GiteaPullRequest>(0, ref_name)
179            .await?
180            .into_iter()
181            .map(|v| Box::new(v) as Box<dyn RemotePullRequest>)
182            .collect())
183    }
184}
185
186#[cfg(test)]
187mod test {
188    use pretty_assertions::assert_eq;
189
190    use super::*;
191    use crate::remote::RemoteCommit;
192
193    #[test]
194    fn timestamp() {
195        let remote_commit = GiteaCommit {
196            sha: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
197            author: Some(GiteaCommitAuthor {
198                login: Some(String::from("orhun")),
199            }),
200            created: String::from("2021-07-18T15:14:39+03:00"),
201        };
202
203        assert_eq!(Some(1626610479), remote_commit.timestamp());
204    }
205}