git_repos/provider/
github.rs

1use serde::Deserialize;
2
3use crate::{
4    provider::{escape, ApiErrorResponse, Filter, JsonError, Project, Provider},
5    token::AuthToken,
6};
7
8const ACCEPT_HEADER_JSON: &str = "application/vnd.github.v3+json";
9const GITHUB_API_BASEURL: &str = 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) -> String {
31        self.name.clone()
32    }
33
34    fn namespace(&self) -> Option<String> {
35        if let Some((namespace, _name)) = self.full_name.rsplit_once('/') {
36            Some(namespace.to_string())
37        } else {
38            None
39        }
40    }
41
42    fn ssh_url(&self) -> String {
43        self.ssh_url.clone()
44    }
45
46    fn http_url(&self) -> String {
47        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: AuthToken,
69}
70
71impl Provider for Github {
72    type Error = GithubApiErrorResponse;
73    type Project = GithubProject;
74
75    fn new(
76        filter: Filter,
77        secret_token: AuthToken,
78        api_url_override: Option<String>,
79    ) -> Result<Self, String> {
80        if api_url_override.is_some() {
81            return Err("API URL overriding is not supported for Github".to_string());
82        }
83        Ok(Self { filter, secret_token })
84    }
85
86    fn filter(&self) -> &Filter {
87        &self.filter
88    }
89
90    fn secret_token(&self) -> &AuthToken {
91        &self.secret_token
92    }
93
94    fn auth_header_key() -> &'static str {
95        "token"
96    }
97
98    fn get_user_projects(
99        &self,
100        user: &str,
101    ) -> Result<Vec<GithubProject>, ApiErrorResponse<GithubApiErrorResponse>> {
102        self.call_list(
103            &format!("{GITHUB_API_BASEURL}/users/{}/repos", escape(user)),
104            Some(ACCEPT_HEADER_JSON),
105        )
106    }
107
108    fn get_group_projects(
109        &self,
110        group: &str,
111    ) -> Result<Vec<GithubProject>, ApiErrorResponse<GithubApiErrorResponse>> {
112        self.call_list(
113            &format!("{GITHUB_API_BASEURL}/orgs/{}/repos?type=all", escape(group)),
114            Some(ACCEPT_HEADER_JSON),
115        )
116    }
117
118    fn get_accessible_projects(
119        &self,
120    ) -> Result<Vec<GithubProject>, ApiErrorResponse<GithubApiErrorResponse>> {
121        self.call_list(&format!("{GITHUB_API_BASEURL}/user/repos"), Some(ACCEPT_HEADER_JSON))
122    }
123
124    fn get_current_user(&self) -> Result<String, ApiErrorResponse<GithubApiErrorResponse>> {
125        Ok(super::call::<GithubUser, GithubApiErrorResponse>(
126            &format!("{GITHUB_API_BASEURL}/user"),
127            Self::auth_header_key(),
128            self.secret_token(),
129            Some(ACCEPT_HEADER_JSON),
130        )?
131        .username)
132    }
133}