Files
novem/src/api/repo.rs

169 lines
4.3 KiB
Rust
Raw Normal View History

2026-05-23 18:45:44 +01:00
use std::sync::Arc;
2026-05-18 22:30:46 +08:00
use reqwest::Method;
2026-05-06 01:42:38 +08:00
use serde::Deserialize;
2026-05-18 22:30:46 +08:00
use crate::{
2026-05-23 18:45:44 +01:00
api, query,
2026-05-23 12:28:45 +01:00
util::{self, file},
2026-05-18 22:30:46 +08:00
};
2026-05-06 01:42:38 +08:00
#[derive(Debug, Deserialize)]
pub struct Repository {
pub id: u64,
pub name: String,
pub full_name: String,
pub private: bool,
pub html_url: String,
pub description: Option<String>,
pub default_branch: String,
pub owner: Owner,
}
#[derive(Debug, Deserialize)]
pub struct Owner {
pub login: String,
pub id: api::user::Id,
pub avatar_url: String,
pub html_url: String,
}
2026-04-20 15:13:26 +01:00
2026-05-18 22:30:46 +08:00
#[derive(Debug, Clone)]
pub struct FileRef {
2026-05-23 18:45:44 +01:00
pub repo_slug: Arc<str>,
pub path: Arc<str>,
pub reff: Option<Arc<str>>,
2026-05-18 22:30:46 +08:00
}
2026-04-20 15:13:26 +01:00
#[derive(Clone)]
pub struct List;
2026-04-21 20:30:41 +01:00
impl query::QueryFn for List {
2026-05-06 01:42:38 +08:00
type Data = Vec<Repository>;
type Error = api::Error;
type Context = api::QueryContext;
2026-04-20 15:13:26 +01:00
2026-05-06 01:42:38 +08:00
fn key(&self) -> query::Key {
"repo/list".into()
2026-04-20 15:13:26 +01:00
}
2026-05-06 01:42:38 +08:00
async fn run(&self, c: &Self::Context) -> Result<Self::Data, Self::Error> {
#[cfg(debug_assertions)]
if c.should_use_fixtures {
return super::mock::list_repos();
}
let params = [("sort", "updated"), ("per_page", "100")];
let res = c
.github_request(reqwest::Method::GET, "/user/repos")?
.query(&params)
.send()
.await?;
api::parse_response(res).await
2026-04-20 15:13:26 +01:00
}
}
2026-05-18 22:30:46 +08:00
#[derive(Clone)]
pub struct FetchFileContent {
2026-05-23 18:45:44 +01:00
pub repo_slug: Arc<str>,
pub path: Arc<str>,
pub reff: Option<Arc<str>>,
2026-05-18 22:30:46 +08:00
}
impl query::QueryFn for FetchFileContent {
type Data = bytes::Bytes;
type Error = api::Error;
type Context = api::QueryContext;
fn key(&self) -> query::Key {
match &self.reff {
2026-05-23 18:45:44 +01:00
| Some(reff) => format!("repo/fetch/{}/{}/{}", self.repo_slug, self.path, reff).into(),
| None => format!("repo/fetch/{}/{}", self.repo_slug, self.path).into(),
2026-05-18 22:30:46 +08:00
}
}
async fn run(&self, c: &Self::Context) -> Result<Self::Data, Self::Error> {
#[cfg(debug_assertions)]
if c.should_use_fixtures {
return super::mock::fetch_file_content(
&self.repo_slug,
&self.path,
self.reff.as_deref(),
);
}
let path = match &self.reff {
2026-05-23 18:45:44 +01:00
| Some(reff) => format!(
"/repos/{}/contents/{}?ref={}",
self.repo_slug, self.path, reff
),
| None => format!("/repos/{}/contents/{}", self.repo_slug, self.path),
2026-05-18 22:30:46 +08:00
};
let res = c
.github_request(Method::GET, &path)?
.header("Accept", "application/vnd.github.raw+json")
.send()
.await?;
api::raw_content(res).await
}
}
#[derive(Debug, Clone)]
2026-05-23 12:28:45 +01:00
pub struct FetchFileDiff {
2026-05-18 22:30:46 +08:00
pub base: FileRef,
pub head: FileRef,
}
2026-05-23 12:28:45 +01:00
impl query::QueryFn for FetchFileDiff {
type Data = util::diff::ContentDiff;
2026-05-18 22:30:46 +08:00
type Error = api::Error;
type Context = api::QueryContext;
fn key(&self) -> query::Key {
format!(
"repo/diff/{}/{}/{}",
self.base.repo_slug, self.base.path, self.head.path
)
.into()
}
async fn run(&self, c: &Self::Context) -> Result<Self::Data, Self::Error> {
async fn fetch_content(
r: &FileRef,
2026-05-23 12:28:45 +01:00
c: &<FetchFileDiff as query::QueryFn>::Context,
2026-05-18 22:30:46 +08:00
) -> Result<Option<bytes::Bytes>, api::Error> {
let path = match &r.reff {
2026-05-23 18:45:44 +01:00
| Some(reff) => format!("/repos/{}/contents/{}?ref={}", r.repo_slug, r.path, reff),
| None => format!("/repos/{}/contents/{}", r.repo_slug, r.path),
2026-05-18 22:30:46 +08:00
};
let res = c
.github_request(Method::GET, &path)?
.header("Accept", "application/vnd.github.raw+json")
.send()
.await?;
res.headers().get("Content-Type");
let bytes = api::raw_content(res).await?;
let file::ContentType::Text = file::classify_content(&bytes) else {
return Ok(None);
};
Ok(Some(bytes))
}
let (old, new) = tokio::join!(fetch_content(&self.base, c), fetch_content(&self.head, c),);
match (old, new) {
2026-05-23 18:45:44 +01:00
| (Ok(Some(old)), Ok(Some(new))) => Ok(util::diff::diff_content(old, new)),
| _ => Err(api::Error::MalformedResponse(
"failed to fetch content".to_string(),
)),
2026-05-18 22:30:46 +08:00
}
}
}