Skip to main content

tatuin_providers/github/
client.rs

1// SPDX-License-Identifier: MIT
2
3use tatuin_core::filter::FilterState;
4
5use super::structs::Issue;
6use itertools::Itertools;
7use reqwest::{Method, RequestBuilder, header::HeaderMap};
8use std::error::Error;
9
10pub struct Client {
11    base_url: String,
12    default_header: HeaderMap,
13    client: reqwest::Client,
14}
15
16impl Client {
17    pub fn new(api_key: &str) -> Self {
18        let mut headers = HeaderMap::new();
19        headers.insert("Authorization", format!("Bearer {api_key}").parse().unwrap());
20        headers.insert("X-GitHub-Api-Version", "2022-11-28".parse().unwrap());
21        headers.insert("Accept", "application/vnd.github+json".parse().unwrap());
22        headers.insert("User-Agent", "Tatuin".parse().unwrap());
23        Self {
24            base_url: "https://api.github.com".to_string(),
25            default_header: headers,
26            client: reqwest::Client::new(),
27        }
28    }
29
30    fn request(&self, method: Method, url: &str) -> RequestBuilder {
31        self.client.request(method, url).headers(self.default_header.clone())
32    }
33
34    pub async fn issues(&self, repo: &str, states: &[FilterState]) -> Result<Vec<Issue>, Box<dyn Error>> {
35        let mut result = Vec::new();
36
37        const PER_PAGE: i8 = 100;
38        let mut page = 1;
39
40        let state_query = if states.is_empty() {
41            "state=all".to_string()
42        } else {
43            states
44                .iter()
45                .map(|s| match s {
46                    FilterState::Completed => "state=closed".to_string(),
47                    FilterState::Todo => "state=open".to_string(),
48                    _ => String::new(),
49                })
50                .filter(|s| !s.is_empty())
51                .join("&")
52        };
53
54        loop {
55            let url = format!(
56                "{}/repos/{repo}/issues?page={page}&per_page={PER_PAGE}&{state_query}",
57                self.base_url
58            );
59            match self.request(Method::GET, &url).send().await?.json::<Vec<Issue>>().await {
60                Ok(mut r) => {
61                    if r.is_empty() {
62                        break;
63                    }
64
65                    result.append(&mut r);
66                    page += 1;
67                }
68                Err(e) => {
69                    tracing::error!(target:"github_client", url=url, error=?e);
70                    return Err(e.into());
71                }
72            }
73        }
74
75        Ok(result)
76    }
77}