Skip to main content

grm/provider/
github.rs

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