Files
novem/src/api.rs

143 lines
3.5 KiB
Rust
Raw Normal View History

2026-05-11 00:32:12 +08:00
use reqwest::Method;
2026-04-26 16:06:49 +01:00
use serde::{Deserialize, Serialize};
2026-04-24 19:22:25 +01:00
2026-04-20 22:54:31 +01:00
use crate::query;
2026-04-21 20:30:41 +01:00
pub(crate) mod auth;
2026-05-06 01:42:38 +08:00
pub(crate) mod issues;
#[cfg(debug_assertions)]
mod mock;
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-05-06 01:42:38 +08:00
#[cfg(debug_assertions)]
pub(crate) should_use_fixtures: bool,
2026-04-20 22:54:31 +01:00
}
2026-04-26 16:06:49 +01:00
#[derive(Clone, Serialize, Deserialize)]
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-05-06 01:42:38 +08:00
#[cfg(debug_assertions)]
MissingMockFixture(String),
2026-04-24 19:22:25 +01:00
Github(GithubError),
2026-05-11 00:32:12 +08:00
MalformedResponse(String),
2026-04-21 20:30:41 +01:00
HttpError(reqwest::Error),
2026-05-11 00:32:12 +08:00
GraphQLError(Vec<graphql_client::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-05-11 00:32:12 +08:00
fn github_graphql_request<V>(
&self,
request: &graphql_client::QueryBody<V>,
) -> Result<reqwest::RequestBuilder, Error>
where
V: serde::Serialize,
{
Ok(self.github_request(Method::POST, "/graphql")?.json(request))
}
2026-04-24 19:22:25 +01:00
}
2026-05-06 01:42:38 +08:00
#[cfg(debug_assertions)]
pub(crate) fn use_github_fixtures() -> bool {
mock::is_enabled()
}
#[cfg(not(debug_assertions))]
pub(crate) fn use_github_fixtures() -> bool {
false
}
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 {
2026-05-11 00:32:12 +08:00
Self::MalformedResponse(value.to_string())
2026-04-21 20:30:41 +01:00
}
}
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
}
}
2026-05-08 02:23:28 +08:00
async fn parse_graphql_response<T>(
res: reqwest::Response,
2026-05-11 00:32:12 +08:00
) -> Result<(graphql_client::Response<T>, T), Error>
2026-05-08 02:23:28 +08:00
where
T: serde::de::DeserializeOwned,
{
2026-05-11 00:32:12 +08:00
let mut body: graphql_client::Response<T> = res.json().await?;
match body.data.take() {
| None => Err(Error::GraphQLError(body.errors.unwrap_or_default())),
| Some(data) => Ok((body, data)),
2026-05-11 00:32:12 +08:00
}
2026-05-08 02:23:28 +08:00
}