git-next/src/server/forge/forgejo/config.rs

113 lines
3.5 KiB
Rust
Raw Normal View History

2024-04-09 10:44:01 +01:00
use base64::Engine;
use kxio::network::{self, Network};
use terrors::OneOf;
use tracing::{error, info};
use crate::server::config::{RepoConfig, RepoDetails};
pub async fn load(
details: RepoDetails,
net: &Network,
) -> Result<
RepoConfig,
OneOf<(
RepoConfigFileNotFound,
RepoConfigIsNotFile,
RepoConfigDecodeError,
RepoConfigParseError,
RepoConfigUnknownError,
)>,
> {
let hostname = details.forge.hostname;
let path = details.repo;
let filepath = ".git-next.toml";
let branch = details.branch;
let token = details.forge.token;
let url = network::NetUrl::new(format!(
"https://{hostname}/api/v1/repos/{path}/contents/{filepath}?ref={branch}&token={token}"
));
info!(%url, "Loading config");
let request = network::NetRequest::new(
network::RequestMethod::Get,
url,
network::NetRequestHeaders::new(),
network::RequestBody::None,
network::ResponseType::Json,
None,
network::NetRequestLogging::Both,
);
let result = net.get::<ForgeContentsResponse>(request).await;
let response = result.map_err(|e| match e {
network::NetworkError::RequestFailed(_, status_code, _)
| network::NetworkError::RequestError(_, status_code, _) => match status_code {
network::StatusCode::NOT_FOUND => OneOf::new(RepoConfigFileNotFound),
_ => OneOf::new(RepoConfigUnknownError(status_code)),
},
_ => OneOf::new(RepoConfigUnknownError(
network::StatusCode::INTERNAL_SERVER_ERROR,
)),
})?;
let status = response.status_code();
match response.response_body() {
Some(body) => {
// we need to decode (see encoding field) the value of 'content' from the response
match body.content_type {
ForgeContentsType::File => decode_config(body).map_err(OneOf::broaden),
_ => Err(OneOf::new(RepoConfigIsNotFile)),
}
}
None => {
error!(%status, "Failed to fetch repo config file");
Err(OneOf::new(RepoConfigUnknownError(status)))
}
}
}
fn decode_config(
body: ForgeContentsResponse,
) -> Result<RepoConfig, OneOf<(RepoConfigDecodeError, RepoConfigParseError)>> {
match body.encoding.as_str() {
"base64" => {
let decoded = base64::engine::general_purpose::STANDARD
.decode(body.content)
.map_err(|_| OneOf::new(RepoConfigDecodeError))?;
let decoded =
String::from_utf8(decoded).map_err(|_| OneOf::new(RepoConfigDecodeError))?;
let config = toml::from_str(&decoded).map_err(|_| OneOf::new(RepoConfigParseError))?;
Ok(config)
}
_ => Err(OneOf::new(RepoConfigDecodeError)),
}
}
#[derive(Debug)]
pub struct RepoConfigFileNotFound;
#[derive(Debug)]
pub struct RepoConfigIsNotFile;
#[derive(Debug)]
pub struct RepoConfigDecodeError;
#[derive(Debug)]
pub struct RepoConfigParseError;
#[derive(Debug)]
pub struct RepoConfigUnknownError(pub network::StatusCode);
#[derive(Clone, Debug, serde::Deserialize)]
struct ForgeContentsResponse {
#[serde(rename = "type")]
pub content_type: ForgeContentsType,
pub content: String,
pub encoding: String,
}
#[derive(Clone, Debug, serde::Deserialize)]
enum ForgeContentsType {
#[serde(rename = "file")]
File,
#[serde(rename = "dir")]
Dir,
#[serde(rename = "symlink")]
Symlink,
#[serde(rename = "submodule")]
Submodule,
}