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