git_repos/provider/
gitlab.rs1use serde::Deserialize;
2
3use crate::{
4 provider::{escape, ApiErrorResponse, Filter, JsonError, Project, Provider},
5 token::AuthToken,
6};
7
8const ACCEPT_HEADER_JSON: &str = "application/json";
9const GITLAB_API_BASEURL: &str = match option_env!("GITLAB_API_BASEURL") {
10 Some(url) => url,
11 None => "https://gitlab.com",
12};
13
14#[derive(Deserialize)]
15#[serde(rename_all = "lowercase")]
16pub enum GitlabVisibility {
17 Private,
18 Internal,
19 Public,
20}
21
22#[derive(Deserialize)]
23pub struct GitlabProject {
24 #[serde(rename = "path")]
25 pub name: String,
26 pub path_with_namespace: String,
27 pub http_url_to_repo: String,
28 pub ssh_url_to_repo: String,
29 pub visibility: GitlabVisibility,
30}
31
32#[derive(Deserialize)]
33struct GitlabUser {
34 pub username: String,
35}
36
37impl Project for GitlabProject {
38 fn name(&self) -> String {
39 self.name.clone()
40 }
41
42 fn namespace(&self) -> Option<String> {
43 if let Some((namespace, _name)) = self.path_with_namespace.rsplit_once('/') {
44 Some(namespace.to_string())
45 } else {
46 None
47 }
48 }
49
50 fn ssh_url(&self) -> String {
51 self.ssh_url_to_repo.clone()
52 }
53
54 fn http_url(&self) -> String {
55 self.http_url_to_repo.clone()
56 }
57
58 fn private(&self) -> bool {
59 !matches!(self.visibility, GitlabVisibility::Public)
60 }
61}
62
63#[derive(Deserialize)]
64pub struct GitlabApiErrorResponse {
65 #[serde(alias = "error_description", alias = "error")]
66 pub message: String,
67}
68
69impl JsonError for GitlabApiErrorResponse {
70 fn to_string(self) -> String {
71 self.message
72 }
73}
74
75pub struct Gitlab {
76 filter: Filter,
77 secret_token: AuthToken,
78 api_url_override: Option<String>,
79}
80
81impl Gitlab {
82 fn api_url(&self) -> String {
83 self.api_url_override
84 .as_ref()
85 .unwrap_or(&GITLAB_API_BASEURL.to_string())
86 .trim_end_matches('/')
87 .to_string()
88 }
89}
90
91impl Provider for Gitlab {
92 type Error = GitlabApiErrorResponse;
93 type Project = GitlabProject;
94
95 fn new(
96 filter: Filter,
97 secret_token: AuthToken,
98 api_url_override: Option<String>,
99 ) -> Result<Self, String> {
100 Ok(Self { filter, secret_token, api_url_override })
101 }
102
103 fn filter(&self) -> &Filter {
104 &self.filter
105 }
106
107 fn secret_token(&self) -> &AuthToken {
108 &self.secret_token
109 }
110
111 fn auth_header_key() -> &'static str {
112 "bearer"
113 }
114
115 fn get_user_projects(
116 &self,
117 user: &str,
118 ) -> Result<Vec<GitlabProject>, ApiErrorResponse<GitlabApiErrorResponse>> {
119 self.call_list(
120 &format!("{}/api/v4/users/{}/projects", self.api_url(), escape(user)),
121 Some(ACCEPT_HEADER_JSON),
122 )
123 }
124
125 fn get_group_projects(
126 &self,
127 group: &str,
128 ) -> Result<Vec<GitlabProject>, ApiErrorResponse<GitlabApiErrorResponse>> {
129 self.call_list(
130 &format!(
131 "{}/api/v4/groups/{}/projects?include_subgroups=true&archived=false",
132 self.api_url(),
133 escape(group),
134 ),
135 Some(ACCEPT_HEADER_JSON),
136 )
137 }
138
139 fn get_accessible_projects(
140 &self,
141 ) -> Result<Vec<GitlabProject>, ApiErrorResponse<GitlabApiErrorResponse>> {
142 self.call_list(&format!("{}/api/v4/projects", self.api_url(),), Some(ACCEPT_HEADER_JSON))
143 }
144
145 fn get_current_user(&self) -> Result<String, ApiErrorResponse<GitlabApiErrorResponse>> {
146 Ok(super::call::<GitlabUser, GitlabApiErrorResponse>(
147 &format!("{}/api/v4/user", self.api_url()),
148 Self::auth_header_key(),
149 self.secret_token(),
150 Some(ACCEPT_HEADER_JSON),
151 )?
152 .username)
153 }
154}