tatuin-providers 0.6.1

Providers lib of the Tatuin project
Documentation
// SPDX-License-Identifier: MIT

use tatuin_core::filter::FilterState;

use super::structs::Issue;
use itertools::Itertools;
use reqwest::{Method, RequestBuilder, header::HeaderMap};
use std::error::Error;

pub struct Client {
    base_url: String,
    default_header: HeaderMap,
    client: reqwest::Client,
}

impl Client {
    pub fn new(api_key: &str) -> Self {
        let mut headers = HeaderMap::new();
        headers.insert("Authorization", format!("Bearer {api_key}").parse().unwrap());
        headers.insert("X-GitHub-Api-Version", "2022-11-28".parse().unwrap());
        headers.insert("Accept", "application/vnd.github+json".parse().unwrap());
        headers.insert("User-Agent", "Tatuin".parse().unwrap());
        Self {
            base_url: "https://api.github.com".to_string(),
            default_header: headers,
            client: reqwest::Client::new(),
        }
    }

    fn request(&self, method: Method, url: &str) -> RequestBuilder {
        self.client.request(method, url).headers(self.default_header.clone())
    }

    pub async fn issues(&self, repo: &str, states: &[FilterState]) -> Result<Vec<Issue>, Box<dyn Error>> {
        let mut result = Vec::new();

        const PER_PAGE: i8 = 100;
        let mut page = 1;

        let state_query = if states.is_empty() {
            "state=all".to_string()
        } else {
            states
                .iter()
                .map(|s| match s {
                    FilterState::Completed => "state=closed".to_string(),
                    FilterState::Todo => "state=open".to_string(),
                    _ => String::new(),
                })
                .filter(|s| !s.is_empty())
                .join("&")
        };

        loop {
            let url = format!(
                "{}/repos/{repo}/issues?page={page}&per_page={PER_PAGE}&{state_query}",
                self.base_url
            );
            match self.request(Method::GET, &url).send().await?.json::<Vec<Issue>>().await {
                Ok(mut r) => {
                    if r.is_empty() {
                        break;
                    }

                    result.append(&mut r);
                    page += 1;
                }
                Err(e) => {
                    tracing::error!(target:"github_client", url=url, error=?e);
                    return Err(e.into());
                }
            }
        }

        Ok(result)
    }
}