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}