1use crate::error::GorError;
8use crate::host::Host;
9use crate::keyring_store;
10
11pub struct Client {
16 http: reqwest::blocking::Client,
18 host: Host,
20 token: Option<String>,
22}
23
24impl Client {
25 pub fn new(hostname: &str) -> Result<Self, GorError> {
42 let host = Host::new(hostname);
43 let token = keyring_store::get_token(hostname).unwrap_or(None);
44 let http = reqwest::blocking::Client::builder()
45 .user_agent(concat!("gor/", env!("CARGO_PKG_VERSION")))
46 .build()
47 .map_err(GorError::Http)?;
48 Ok(Self { http, host, token })
49 }
50
51 pub fn with_token(hostname: &str, token: &str) -> Result<Self, GorError> {
65 let host = Host::new(hostname);
66 let http = reqwest::blocking::Client::builder()
67 .user_agent(concat!("gor/", env!("CARGO_PKG_VERSION")))
68 .build()
69 .map_err(GorError::Http)?;
70 Ok(Self {
71 http,
72 host,
73 token: Some(token.to_string()),
74 })
75 }
76
77 #[must_use]
79 pub const fn host(&self) -> &Host {
80 &self.host
81 }
82
83 #[must_use]
85 pub const fn is_authenticated(&self) -> bool {
86 self.token.is_some()
87 }
88
89 #[must_use]
91 pub fn token(&self) -> Option<&str> {
92 self.token.as_deref()
93 }
94
95 pub fn get(&self, path: &str) -> Result<reqwest::blocking::Response, GorError> {
103 let url = self.host.api_url(path);
104 let mut req = self
105 .http
106 .get(&url)
107 .header("Accept", "application/vnd.github+json");
108 if let Some(token) = &self.token {
109 req = req.header("Authorization", format!("Bearer {token}"));
110 }
111 tracing::debug!("GET {url}");
112 req.send().map_err(GorError::Http)
113 }
114
115 pub fn get_absolute(&self, url: &str) -> Result<reqwest::blocking::Response, GorError> {
123 let mut req = self
124 .http
125 .get(url)
126 .header("Accept", "application/octet-stream");
127 if let Some(token) = &self.token {
128 req = req.header("Authorization", format!("Bearer {token}"));
129 }
130 tracing::debug!("GET {url}");
131 req.send().map_err(GorError::Http)
132 }
133
134 pub fn request(
144 &self,
145 method: &str,
146 path: &str,
147 headers: &[String],
148 body: Option<Vec<u8>>,
149 ) -> Result<reqwest::blocking::Response, GorError> {
150 let url = self.host.api_url(path);
151 let mut req = self
152 .http
153 .request(method.parse().unwrap_or(reqwest::Method::GET), &url)
154 .header("Accept", "application/vnd.github+json");
155
156 if let Some(token) = &self.token {
157 req = req.header("Authorization", format!("Bearer {token}"));
158 }
159
160 for header in headers {
161 if let Some((key, value)) = header.split_once(':') {
162 req = req.header(key.trim(), value.trim());
163 }
164 }
165
166 if let Some(body_bytes) = body {
167 req = req.body(body_bytes);
168 }
169
170 tracing::debug!("{} {url}", method.to_uppercase());
171 req.send().map_err(GorError::Http)
172 }
173
174 pub fn post<T: serde::Serialize>(
180 &self,
181 path: &str,
182 body: &T,
183 ) -> Result<reqwest::blocking::Response, GorError> {
184 let url = self.host.api_url(path);
185 let mut req = self
186 .http
187 .post(&url)
188 .header("Accept", "application/vnd.github+json")
189 .json(body);
190 if let Some(token) = &self.token {
191 req = req.header("Authorization", format!("Bearer {token}"));
192 }
193 tracing::debug!("POST {url}");
194 req.send().map_err(GorError::Http)
195 }
196
197 pub fn post_form_url(
206 &self,
207 url: &str,
208 form: &std::collections::HashMap<&str, &str>,
209 ) -> Result<reqwest::blocking::Response, GorError> {
210 tracing::debug!("POST {url}");
211 self.http
212 .post(url)
213 .header("Accept", "application/json")
214 .form(form)
215 .send()
216 .map_err(GorError::Http)
217 }
218
219 pub fn graphql(
228 &self,
229 query: &str,
230 variables: Option<serde_json::Value>,
231 ) -> Result<serde_json::Value, GorError> {
232 let url = self.host.api_url("/graphql");
233 let mut body = serde_json::json!({"query": query});
234 if let Some(vars) = variables {
235 body["variables"] = vars;
236 }
237 let mut req = self
238 .http
239 .post(&url)
240 .header("Accept", "application/vnd.github+json")
241 .json(&body);
242 if let Some(token) = &self.token {
243 req = req.header("Authorization", format!("Bearer {token}"));
244 }
245 tracing::debug!("POST {url} (GraphQL)");
246 let response = req.send().map_err(GorError::Http)?;
247 let result: serde_json::Value = response.json().map_err(GorError::Http)?;
248 if let Some(errors) = result.get("errors") {
249 let msg = errors[0]["message"].as_str().unwrap_or("GraphQL error");
250 return Err(GorError::Auth(msg.to_string()));
251 }
252 Ok(result)
253 }
254
255 pub fn upload_asset(
264 &self,
265 upload_url: &str,
266 data: &[u8],
267 content_type: &str,
268 ) -> Result<reqwest::blocking::Response, GorError> {
269 let mut req = self
270 .http
271 .post(upload_url)
272 .header("Accept", "application/vnd.github+json")
273 .header("Content-Type", content_type)
274 .body(data.to_vec());
275 if let Some(token) = &self.token {
276 req = req.header("Authorization", format!("Bearer {token}"));
277 }
278 tracing::debug!("POST {upload_url}");
279 req.send().map_err(GorError::Http)
280 }
281
282 pub fn save_token(&self) -> Result<(), GorError> {
288 self.token.as_ref().map_or_else(
289 || Ok(()),
290 |token| keyring_store::set_token(self.host.hostname(), token),
291 )
292 }
293
294 pub fn set_token(&mut self, token: String) -> Result<(), GorError> {
300 keyring_store::set_token(self.host.hostname(), &token)?;
301 self.token = Some(token);
302 Ok(())
303 }
304
305 pub fn save_user(&self, user: &str) -> Result<(), GorError> {
311 keyring_store::set_user(self.host.hostname(), user)
312 }
313
314 pub fn stored_user(&self) -> Result<Option<String>, GorError> {
320 keyring_store::get_user(self.host.hostname())
321 }
322}