grm/provider/
github.rs

1use serde::Deserialize;
2
3use super::auth;
4use super::escape;
5use super::ApiErrorResponse;
6use super::Filter;
7use super::JsonError;
8use super::Project;
9use super::Provider;
10
11const ACCEPT_HEADER_JSON: &str = "application/vnd.github.v3+json";
12const GITHUB_API_BASEURL: &str = match option_env!("GITHUB_API_BASEURL") {
13    Some(url) => url,
14    None => "https://api.github.com",
15};
16
17#[derive(Deserialize)]
18pub struct GithubProject {
19    pub name: String,
20    pub full_name: String,
21    pub clone_url: String,
22    pub ssh_url: String,
23    pub private: bool,
24}
25
26#[derive(Deserialize)]
27struct GithubUser {
28    #[serde(rename = "login")]
29    pub username: String,
30}
31
32impl Project for GithubProject {
33    fn name(&self) -> String {
34        self.name.clone()
35    }
36
37    fn namespace(&self) -> Option<String> {
38        if let Some((namespace, _name)) = self.full_name.rsplit_once('/') {
39            Some(namespace.to_string())
40        } else {
41            None
42        }
43    }
44
45    fn ssh_url(&self) -> String {
46        self.ssh_url.clone()
47    }
48
49    fn http_url(&self) -> String {
50        self.clone_url.clone()
51    }
52
53    fn private(&self) -> bool {
54        self.private
55    }
56}
57
58#[derive(Deserialize)]
59pub struct GithubApiErrorResponse {
60    pub message: String,
61}
62
63impl JsonError for GithubApiErrorResponse {
64    fn to_string(self) -> String {
65        self.message
66    }
67}
68
69pub struct Github {
70    filter: Filter,
71    secret_token: auth::AuthToken,
72}
73
74impl Provider for Github {
75    type Project = GithubProject;
76    type Error = GithubApiErrorResponse;
77
78    fn new(
79        filter: Filter,
80        secret_token: auth::AuthToken,
81        api_url_override: Option<String>,
82    ) -> Result<Self, String> {
83        if api_url_override.is_some() {
84            return Err("API URL overriding is not supported for Github".to_string());
85        }
86        Ok(Self {
87            filter,
88            secret_token,
89        })
90    }
91
92    fn filter(&self) -> &Filter {
93        &self.filter
94    }
95
96    fn secret_token(&self) -> &auth::AuthToken {
97        &self.secret_token
98    }
99
100    fn auth_header_key() -> &'static str {
101        "token"
102    }
103
104    fn get_user_projects(
105        &self,
106        user: &str,
107    ) -> Result<Vec<GithubProject>, ApiErrorResponse<GithubApiErrorResponse>> {
108        self.call_list(
109            &format!("{GITHUB_API_BASEURL}/users/{}/repos", escape(user)),
110            Some(ACCEPT_HEADER_JSON),
111        )
112    }
113
114    fn get_group_projects(
115        &self,
116        group: &str,
117    ) -> Result<Vec<GithubProject>, ApiErrorResponse<GithubApiErrorResponse>> {
118        self.call_list(
119            &format!("{GITHUB_API_BASEURL}/orgs/{}/repos?type=all", escape(group)),
120            Some(ACCEPT_HEADER_JSON),
121        )
122    }
123
124    fn get_accessible_projects(
125        &self,
126    ) -> Result<Vec<GithubProject>, ApiErrorResponse<GithubApiErrorResponse>> {
127        self.call_list(
128            &format!("{GITHUB_API_BASEURL}/user/repos"),
129            Some(ACCEPT_HEADER_JSON),
130        )
131    }
132
133    fn get_current_user(&self) -> Result<String, ApiErrorResponse<GithubApiErrorResponse>> {
134        Ok(super::call::<GithubUser, GithubApiErrorResponse>(
135            &format!("{GITHUB_API_BASEURL}/user"),
136            Self::auth_header_key(),
137            self.secret_token(),
138            Some(ACCEPT_HEADER_JSON),
139        )?
140        .username)
141    }
142}