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}