179 lines
4.7 KiB
Rust
179 lines
4.7 KiB
Rust
use base64::engine::general_purpose;
|
|
use base64::Engine as _;
|
|
use serde::Deserialize;
|
|
use tracing::debug;
|
|
|
|
use self::error::GiteaClientError;
|
|
pub(crate) mod error;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub(crate) struct GiteaClient {
|
|
api_root: String,
|
|
token: String,
|
|
http_client: reqwest::Client,
|
|
}
|
|
|
|
impl GiteaClient {
|
|
pub(crate) fn new<R: Into<String>, T: Into<String>>(api_root: R, token: T) -> GiteaClient {
|
|
GiteaClient {
|
|
api_root: api_root.into(),
|
|
token: token.into(),
|
|
http_client: reqwest::Client::new(),
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn get_tree<O: AsRef<str>, R: AsRef<str>, C: AsRef<str>>(
|
|
&self,
|
|
owner: O,
|
|
repo: R,
|
|
commit: C,
|
|
) -> Result<Tree, Box<dyn std::error::Error>> {
|
|
let mut files = Vec::new();
|
|
let mut page: Option<u64> = None;
|
|
let mut num_responses: u64 = 0;
|
|
loop {
|
|
let url = format!(
|
|
"{api_root}/v1/repos/{owner}/{repo}/git/trees/{commit}?recursive=true&per_page=100{page}",
|
|
api_root = self.api_root,
|
|
owner = owner.as_ref(),
|
|
repo = repo.as_ref(),
|
|
commit = commit.as_ref(),
|
|
page = page.map(|num| format!("&page={}", num)).unwrap_or_default()
|
|
);
|
|
let response = self
|
|
.http_client
|
|
.get(url)
|
|
.header("Authorization", format!("token {}", self.token))
|
|
.send()
|
|
.await?;
|
|
let response = response.error_for_status()?;
|
|
let total_count = response
|
|
.headers()
|
|
.get("x-total-count")
|
|
.ok_or(GiteaClientError::NoTotalCountHeaderInResponse)?
|
|
.to_str()?
|
|
.parse::<u64>()?;
|
|
if num_responses == 0 {
|
|
files.reserve(total_count as usize);
|
|
}
|
|
|
|
let body = response.text().await?;
|
|
let parsed_body: ResponseGetTree = serde_json::from_str(body.as_str())?;
|
|
num_responses += parsed_body.tree.len() as u64;
|
|
|
|
files.extend(parsed_body.tree.into_iter().map(|response_object| {
|
|
TreeFileReference::new(response_object.path, response_object.url)
|
|
}));
|
|
|
|
if num_responses >= total_count {
|
|
break;
|
|
}
|
|
|
|
page = Some(parsed_body.page + 1);
|
|
}
|
|
Ok(Tree::new(files))
|
|
}
|
|
|
|
pub(crate) async fn read_file(
|
|
&self,
|
|
file_reference: &TreeFileReference,
|
|
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
|
let response = self
|
|
.http_client
|
|
.get(&file_reference.url)
|
|
.header("Authorization", format!("token {}", self.token))
|
|
.send()
|
|
.await?;
|
|
let response = response.error_for_status()?;
|
|
let body = response.text().await?;
|
|
debug!("read_file response: {}", body);
|
|
let parsed_body: ResponseReadFile = serde_json::from_str(body.as_str())?;
|
|
assert!(
|
|
parsed_body.encoding == "base64",
|
|
"We currently only support base64 file encoding from gitea."
|
|
);
|
|
Ok(general_purpose::STANDARD.decode(parsed_body.content)?)
|
|
}
|
|
}
|
|
|
|
/// A single API response for GetTree containing only one page.
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
struct ResponseGetTree {
|
|
#[allow(dead_code)]
|
|
sha: String,
|
|
|
|
#[allow(dead_code)]
|
|
url: String,
|
|
tree: Vec<ResponseObjectReference>,
|
|
|
|
#[allow(dead_code)]
|
|
truncated: bool,
|
|
page: u64,
|
|
|
|
#[allow(dead_code)]
|
|
total_count: u64,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
struct ResponseObjectReference {
|
|
path: String,
|
|
|
|
#[allow(dead_code)]
|
|
mode: String,
|
|
|
|
#[allow(dead_code)]
|
|
#[serde(rename = "type")]
|
|
object_type: String,
|
|
|
|
#[allow(dead_code)]
|
|
size: u64,
|
|
|
|
#[allow(dead_code)]
|
|
sha: String,
|
|
url: String,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct Tree {
|
|
pub(crate) files: Vec<TreeFileReference>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct TreeFileReference {
|
|
pub(crate) path: String,
|
|
pub(crate) url: String,
|
|
}
|
|
|
|
impl Tree {
|
|
pub(crate) fn new(files: Vec<TreeFileReference>) -> Tree {
|
|
Tree { files }
|
|
}
|
|
}
|
|
|
|
impl TreeFileReference {
|
|
pub(crate) fn new<P: Into<String>, U: Into<String>>(path: P, url: U) -> TreeFileReference {
|
|
TreeFileReference {
|
|
path: path.into(),
|
|
url: url.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
struct ResponseReadFile {
|
|
content: String,
|
|
encoding: String,
|
|
|
|
#[allow(dead_code)]
|
|
url: String,
|
|
|
|
#[allow(dead_code)]
|
|
sha: String,
|
|
|
|
#[allow(dead_code)]
|
|
size: u64,
|
|
}
|