Files
novem/src/api.rs

103 lines
2.5 KiB
Rust
Raw Normal View History

2026-04-24 19:22:25 +01:00
use std::fmt::format;
use reqwest::{Response, dns::Resolving};
use serde::Deserialize;
2026-04-20 22:54:31 +01:00
use crate::query;
2026-04-21 20:30:41 +01:00
pub(crate) mod auth;
2026-04-20 22:54:31 +01:00
pub(crate) mod repo;
2026-04-21 11:50:04 +01:00
pub(crate) mod user;
2026-04-20 22:54:31 +01:00
2026-04-21 11:50:04 +01:00
#[derive(Clone)]
2026-04-20 22:54:31 +01:00
pub struct QueryContext {
pub(crate) http: reqwest::Client,
2026-04-24 19:22:25 +01:00
pub(crate) auth: Option<AuthTokens>,
2026-04-21 20:30:41 +01:00
pub(crate) github: GithubCredentials,
2026-04-20 22:54:31 +01:00
}
2026-04-21 11:50:04 +01:00
#[derive(Clone)]
2026-04-24 19:22:25 +01:00
pub(crate) struct AuthTokens {
pub(crate) access_token: String,
2026-04-21 20:30:41 +01:00
}
#[derive(Clone)]
pub(crate) struct GithubCredentials {
2026-04-24 19:22:25 +01:00
pub(crate) base_url: &'static str,
2026-04-21 20:30:41 +01:00
pub(crate) client_id: &'static str,
2026-04-21 11:50:04 +01:00
}
2026-04-24 19:22:25 +01:00
#[derive(Debug)]
pub(crate) enum Error {
2026-04-21 11:50:04 +01:00
Unauthenticated,
2026-04-24 19:22:25 +01:00
Github(GithubError),
2026-04-21 20:30:41 +01:00
MalformedResponse(serde_json::Error),
HttpError(reqwest::Error),
2026-04-21 11:50:04 +01:00
}
2026-04-24 19:22:25 +01:00
#[derive(Debug, Deserialize)]
pub(crate) struct GithubError {
pub error: String,
pub error_description: Option<String>,
pub error_uri: Option<String>,
}
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<reqwest::RequestBuilder, Error> {
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")
2026-04-25 00:49:50 +01:00
.header("User-Agent", "kennethnym")
2026-04-24 19:22:25 +01:00
.bearer_auth(&auth.access_token))
}
}
2026-04-21 11:50:04 +01:00
impl query::Context for QueryContext {}
2026-04-21 20:30:41 +01:00
impl From<reqwest::Error> for Error {
fn from(value: reqwest::Error) -> Self {
Self::HttpError(value)
}
}
impl From<serde_json::Error> for Error {
fn from(value: serde_json::Error) -> Self {
Self::MalformedResponse(value)
}
}
2026-04-24 19:22:25 +01:00
async fn parse_response<T>(res: reqwest::Response) -> Result<T, Error>
where
T: serde::de::DeserializeOwned,
{
let status = res.status().clone();
let data = res.bytes().await?;
2026-04-25 00:49:50 +01:00
println!("[query] RES {:?} {:?}", status, str::from_utf8(&data));
2026-04-24 19:22:25 +01:00
if status.is_success() {
serde_json::from_slice::<T>(&data).map_err(|e| e.into())
} else {
serde_json::from_slice::<GithubError>(&data)
.map_err(|e| e.into())
2026-04-25 00:49:50 +01:00
.and_then(|e| {
println!(
"[api parse error] invalid json, received: {:?}",
str::from_utf8(&data),
);
Err(Error::Github(e))
})
2026-04-24 19:22:25 +01:00
}
}