bytes_radar/net/providers/
gitlab.rs1use crate::net::traits::{GitProvider, ParsedRepository, ProviderConfig};
2use async_trait::async_trait;
3use reqwest::Client;
4
5pub struct GitLabProvider {
6 token: Option<String>,
7}
8
9impl GitLabProvider {
10 pub fn new() -> Self {
11 Self { token: None }
12 }
13}
14
15#[async_trait]
16impl GitProvider for GitLabProvider {
17 fn name(&self) -> &'static str {
18 "gitlab"
19 }
20
21 fn can_handle(&self, url: &str) -> bool {
22 url.contains("gitlab.com") || url.contains("gitlab.")
23 }
24
25 fn parse_url(&self, url: &str) -> Option<ParsedRepository> {
26 if !self.can_handle(url) {
27 return None;
28 }
29
30 let url = url.trim_end_matches('/');
31
32 if url.contains("/-/tree/") {
33 return self.parse_tree_url(url);
34 }
35
36 if url.contains("/-/commit/") {
37 return self.parse_commit_url(url);
38 }
39
40 self.parse_basic_url(url)
41 }
42
43 fn build_download_urls(&self, parsed: &ParsedRepository) -> Vec<String> {
44 let mut urls = Vec::new();
45
46 if let Some(ref branch_or_commit) = parsed.branch_or_commit {
47 let host = parsed.host.as_deref().unwrap_or("gitlab.com");
48
49 if parsed.is_commit {
50 urls.push(format!(
51 "https://{}/{}/-/archive/{}/{}-{}.tar.gz",
52 host,
53 self.build_project_path(&parsed.owner, &parsed.repo),
54 branch_or_commit,
55 parsed.repo,
56 branch_or_commit
57 ));
58 } else {
59 urls.push(format!(
60 "https://{}/{}/-/archive/{}/{}-{}.tar.gz",
61 host,
62 self.build_project_path(&parsed.owner, &parsed.repo),
63 branch_or_commit,
64 parsed.repo,
65 branch_or_commit
66 ));
67 }
68 }
69
70 urls
71 }
72
73 async fn get_default_branch(
74 &self,
75 _client: &Client,
76 _parsed: &ParsedRepository,
77 ) -> Option<String> {
78 None
79 }
80
81 fn apply_config(&mut self, config: &ProviderConfig) {
82 self.token = config
83 .credentials
84 .get("token")
85 .cloned()
86 .or_else(|| config.credentials.get("private_token").cloned());
87 }
88
89 fn get_project_name(&self, url: &str) -> String {
90 if let Some(parsed) = self.parse_url(url) {
91 return parsed.project_name;
92 }
93
94 if let Some(filename) = url.split('/').next_back() {
95 if filename.ends_with(".tar.gz") {
96 return filename.trim_end_matches(".tar.gz").to_string();
97 }
98 if filename.ends_with(".tgz") {
99 return filename.trim_end_matches(".tgz").to_string();
100 }
101 return filename.to_string();
102 }
103
104 "gitlab-project".to_string()
105 }
106}
107
108impl GitLabProvider {
109 fn parse_tree_url(&self, url: &str) -> Option<ParsedRepository> {
110 let parts: Vec<&str> = url.split('/').collect();
111 if let Some(tree_pos) = parts.iter().position(|&x| x == "tree") {
112 if tree_pos + 1 < parts.len() && tree_pos >= 3 {
113 let gitlab_pos = parts.iter().position(|&x| x.contains("gitlab"))?;
114 let host = parts[gitlab_pos].to_string();
115 let owner = parts[gitlab_pos + 1].to_string();
116 let repo = parts[gitlab_pos + 2].to_string();
117 let branch = parts[tree_pos + 1].to_string();
118
119 return Some(
120 ParsedRepository::new(owner, repo)
121 .with_branch(branch)
122 .with_host(host),
123 );
124 }
125 }
126 None
127 }
128
129 fn parse_commit_url(&self, url: &str) -> Option<ParsedRepository> {
130 let parts: Vec<&str> = url.split('/').collect();
131 if let Some(commit_pos) = parts.iter().position(|&x| x == "commit") {
132 if commit_pos + 1 < parts.len() && commit_pos >= 3 {
133 let gitlab_pos = parts.iter().position(|&x| x.contains("gitlab"))?;
134 let host = parts[gitlab_pos].to_string();
135 let owner = parts[gitlab_pos + 1].to_string();
136 let repo = parts[gitlab_pos + 2].to_string();
137 let commit = parts[commit_pos + 1].to_string();
138
139 return Some(
140 ParsedRepository::new(owner, repo)
141 .with_commit(commit)
142 .with_host(host),
143 );
144 }
145 }
146 None
147 }
148
149 fn parse_basic_url(&self, url: &str) -> Option<ParsedRepository> {
150 let parts: Vec<&str> = url.split('/').collect();
151 if let Some(gitlab_pos) = parts.iter().position(|&x| x.contains("gitlab")) {
152 if gitlab_pos + 2 < parts.len() {
153 let host = parts[gitlab_pos].to_string();
154 let owner = parts[gitlab_pos + 1].to_string();
155 let repo = parts[gitlab_pos + 2].to_string();
156
157 return Some(ParsedRepository::new(owner, repo).with_host(host));
158 }
159 }
160
161 None
162 }
163
164 fn build_project_path(&self, owner: &str, repo: &str) -> String {
165 format!("{}/{}", owner, repo)
166 }
167}
168
169impl Default for GitLabProvider {
170 fn default() -> Self {
171 Self::new()
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn test_can_handle() {
181 let provider = GitLabProvider::new();
182 assert!(provider.can_handle("https://gitlab.com/user/repo"));
183 assert!(provider.can_handle("https://gitlab.example.com/user/repo"));
184 assert!(!provider.can_handle("https://github.com/user/repo"));
185 }
186
187 #[test]
188 fn test_parse_basic_url() {
189 let provider = GitLabProvider::new();
190
191 let parsed = provider.parse_url("https://gitlab.com/user/repo").unwrap();
192 assert_eq!(parsed.owner, "user");
193 assert_eq!(parsed.repo, "repo");
194 assert_eq!(parsed.project_name, "repo@main");
195 assert_eq!(parsed.branch_or_commit, None);
196 assert!(!parsed.is_commit);
197 assert_eq!(parsed.host.as_ref().unwrap(), "gitlab.com");
198 }
199
200 #[test]
201 fn test_parse_tree_url() {
202 let provider = GitLabProvider::new();
203
204 let parsed = provider
205 .parse_url("https://gitlab.com/user/repo/-/tree/develop")
206 .unwrap();
207 assert_eq!(parsed.owner, "user");
208 assert_eq!(parsed.repo, "repo");
209 assert_eq!(parsed.project_name, "repo@develop");
210 assert_eq!(parsed.branch_or_commit, Some("develop".to_string()));
211 assert!(!parsed.is_commit);
212 }
213
214 #[test]
215 fn test_build_download_urls() {
216 let provider = GitLabProvider::new();
217
218 let parsed = ParsedRepository::new("user".to_string(), "repo".to_string())
219 .with_branch("main".to_string())
220 .with_host("gitlab.com".to_string());
221
222 let urls = provider.build_download_urls(&parsed);
223 assert!(urls
224 .contains(&"https://gitlab.com/user/repo/-/archive/main/repo-main.tar.gz".to_string()));
225 }
226
227 #[test]
228 fn test_self_hosted_gitlab() {
229 let provider = GitLabProvider::new();
230
231 let parsed = provider
232 .parse_url("https://gitlab.company.com/team/project")
233 .unwrap();
234 assert_eq!(parsed.owner, "team");
235 assert_eq!(parsed.repo, "project");
236 assert_eq!(parsed.host.as_ref().unwrap(), "gitlab.company.com");
237 }
238}