1use crate::config::Remote;
2use crate::error::*;
3use reqwest_middleware::ClientWithMiddleware;
4use serde::{
5 Deserialize,
6 Serialize,
7};
8
9use super::*;
10
11pub const START_FETCHING_MSG: &str = "Retrieving data from GitLab...";
13
14pub const FINISHED_FETCHING_MSG: &str = "Done fetching GitLab data.";
16
17pub(crate) const TEMPLATE_VARIABLES: &[&str] =
19 &["gitlab", "commit.gitlab", "commit.remote"];
20
21#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
25pub struct GitLabProject {
26 pub id: i64,
28 pub description: Option<String>,
30 pub name: String,
32 pub name_with_namespace: String,
34 pub path_with_namespace: String,
36 pub created_at: String,
38 pub default_branch: String,
40}
41
42impl RemoteEntry for GitLabProject {
43 fn url(_id: i64, api_url: &str, remote: &Remote, _page: i32) -> String {
44 format!(
45 "{}/projects/{}%2F{}",
46 api_url,
47 urlencoding::encode(remote.owner.as_str()),
48 remote.repo
49 )
50 }
51
52 fn buffer_size() -> usize {
53 1
54 }
55
56 fn early_exit(&self) -> bool {
57 false
58 }
59}
60
61#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
65pub struct GitLabCommit {
66 pub id: String,
68 pub short_id: String,
70 pub title: String,
72 pub author_name: String,
74 pub author_email: String,
76 pub authored_date: String,
78 pub committer_name: String,
80 pub committer_email: String,
82 pub committed_date: String,
84 pub created_at: String,
86 pub message: String,
88 pub parent_ids: Vec<String>,
90 pub web_url: String,
92}
93
94impl RemoteCommit for GitLabCommit {
95 fn id(&self) -> String {
96 self.id.clone()
97 }
98
99 fn username(&self) -> Option<String> {
100 Some(self.author_name.clone())
101 }
102
103 fn timestamp(&self) -> Option<i64> {
104 Some(self.convert_to_unix_timestamp(self.committed_date.clone().as_str()))
105 }
106}
107
108impl RemoteEntry for GitLabCommit {
109 fn url(id: i64, api_url: &str, _remote: &Remote, page: i32) -> String {
110 let commit_page = page + 1;
111 format!(
112 "{}/projects/{}/repository/commits?per_page={MAX_PAGE_SIZE}&\
113 page={commit_page}",
114 api_url, id
115 )
116 }
117 fn buffer_size() -> usize {
118 10
119 }
120
121 fn early_exit(&self) -> bool {
122 false
123 }
124}
125
126#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
130pub struct GitLabMergeRequest {
131 pub id: i64,
133 pub iid: i64,
135 pub project_id: i64,
137 pub title: String,
139 pub description: String,
141 pub state: String,
143 pub created_at: String,
145 pub author: GitLabUser,
147 pub sha: String,
149 pub merge_commit_sha: Option<String>,
151 pub squash_commit_sha: Option<String>,
153 pub web_url: String,
155 pub labels: Vec<String>,
157}
158
159impl RemotePullRequest for GitLabMergeRequest {
160 fn number(&self) -> i64 {
161 self.iid
162 }
163
164 fn title(&self) -> Option<String> {
165 Some(self.title.clone())
166 }
167
168 fn labels(&self) -> Vec<String> {
169 self.labels.clone()
170 }
171
172 fn merge_commit(&self) -> Option<String> {
173 self.merge_commit_sha.clone().or(Some(self.sha.clone()))
174 }
175}
176
177impl RemoteEntry for GitLabMergeRequest {
178 fn url(id: i64, api_url: &str, _remote: &Remote, page: i32) -> String {
179 format!(
180 "{}/projects/{}/merge_requests?per_page={MAX_PAGE_SIZE}&page={page}&\
181 state=merged",
182 api_url, id
183 )
184 }
185
186 fn buffer_size() -> usize {
187 5
188 }
189
190 fn early_exit(&self) -> bool {
191 false
192 }
193}
194
195#[derive(Debug, Default, Clone, Hash, Eq, PartialEq, Deserialize, Serialize)]
197pub struct GitLabUser {
198 pub id: i64,
200 pub name: String,
202 pub username: String,
204 pub state: String,
206 pub avatar_url: Option<String>,
208 pub web_url: String,
210}
211
212#[derive(Debug, Default, Clone, Hash, Eq, PartialEq, Deserialize, Serialize)]
214pub struct GitLabReference {
215 pub short: String,
217 pub relative: String,
219 pub full: String,
221}
222
223#[derive(Debug, Clone)]
225pub struct GitLabClient {
226 remote: Remote,
228 client: ClientWithMiddleware,
230}
231
232impl TryFrom<Remote> for GitLabClient {
234 type Error = Error;
235 fn try_from(remote: Remote) -> Result<Self> {
236 Ok(Self {
237 client: remote.create_client("application/json")?,
238 remote,
239 })
240 }
241}
242
243impl RemoteClient for GitLabClient {
244 const API_URL: &'static str = "https://gitlab.com/api/v4";
245 const API_URL_ENV: &'static str = "GITLAB_API_URL";
246
247 fn remote(&self) -> Remote {
248 self.remote.clone()
249 }
250
251 fn client(&self) -> ClientWithMiddleware {
252 self.client.clone()
253 }
254}
255
256impl GitLabClient {
257 pub async fn get_project(&self) -> Result<GitLabProject> {
259 self.get_entry::<GitLabProject>(0, 1).await
260 }
261
262 pub async fn get_commits(
264 &self,
265 project_id: i64,
266 ) -> Result<Vec<Box<dyn RemoteCommit>>> {
267 Ok(self
268 .fetch::<GitLabCommit>(project_id)
269 .await?
270 .into_iter()
271 .map(|v| Box::new(v) as Box<dyn RemoteCommit>)
272 .collect())
273 }
274
275 pub async fn get_merge_requests(
277 &self,
278 project_id: i64,
279 ) -> Result<Vec<Box<dyn RemotePullRequest>>> {
280 Ok(self
281 .fetch::<GitLabMergeRequest>(project_id)
282 .await?
283 .into_iter()
284 .map(|v| Box::new(v) as Box<dyn RemotePullRequest>)
285 .collect())
286 }
287}
288#[cfg(test)]
289mod test {
290 use super::*;
291 use pretty_assertions::assert_eq;
292
293 #[test]
294 fn gitlab_remote_encodes_owner() {
295 let remote = Remote::new("abc/def", "xyz1");
296 assert_eq!(
297 "https://gitlab.test.com/api/v4/projects/abc%2Fdef%2Fxyz1",
298 GitLabProject::url(1, "https://gitlab.test.com/api/v4", &remote, 0)
299 );
300 }
301
302 #[test]
303 fn timestamp() {
304 let remote_commit = GitLabCommit {
305 id: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
306 author_name: String::from("orhun"),
307 committed_date: String::from("2021-07-18T15:14:39+03:00"),
308 ..Default::default()
309 };
310
311 assert_eq!(Some(1626610479), remote_commit.timestamp());
312 }
313
314 #[test]
315 fn merge_request_no_merge_commit() {
316 let mr = GitLabMergeRequest {
317 sha: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
318 ..Default::default()
319 };
320 assert!(mr.merge_commit().is_some());
321 }
322}