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::(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> { 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, }