use std::fmt::format; use reqwest::{Response, dns::Resolving}; use serde::Deserialize; use crate::query; pub(crate) mod auth; pub(crate) mod repo; pub(crate) mod user; #[derive(Clone)] pub struct QueryContext { pub(crate) http: reqwest::Client, pub(crate) auth: Option, pub(crate) github: GithubCredentials, } #[derive(Clone)] pub(crate) struct AuthTokens { pub(crate) access_token: String, } #[derive(Clone)] pub(crate) struct GithubCredentials { pub(crate) base_url: &'static str, pub(crate) client_id: &'static str, } #[derive(Debug)] pub(crate) enum Error { Unauthenticated, Github(GithubError), MalformedResponse(serde_json::Error), HttpError(reqwest::Error), } #[derive(Debug, Deserialize)] pub(crate) struct GithubError { pub error: String, pub error_description: Option, pub error_uri: Option, } impl QueryContext { fn auth(&self) -> Result<&AuthTokens, Error> { self.auth.as_ref().ok_or(Error::Unauthenticated) } fn github_request( &self, method: reqwest::Method, url: &str, ) -> Result { let auth = self.auth()?; Ok(self .http .request(method, format!("{}{}", self.github.base_url, url)) .header("Accept", "application/vnd.github+json") .header("X-GitHub-Api-Version", "2026-03-10") .header("User-Agent", "kennethnym") .bearer_auth(&auth.access_token)) } } impl query::Context for QueryContext {} impl From for Error { fn from(value: reqwest::Error) -> Self { Self::HttpError(value) } } impl From for Error { fn from(value: serde_json::Error) -> Self { Self::MalformedResponse(value) } } async fn parse_response(res: reqwest::Response) -> Result where T: serde::de::DeserializeOwned, { let status = res.status().clone(); let data = res.bytes().await?; println!("[query] RES {:?} {:?}", status, str::from_utf8(&data)); if status.is_success() { serde_json::from_slice::(&data).map_err(|e| e.into()) } else { serde_json::from_slice::(&data) .map_err(|e| e.into()) .and_then(|e| { println!( "[api parse error] invalid json, received: {:?}", str::from_utf8(&data), ); Err(Error::Github(e)) }) } }