diff --git a/src/server/actors/repo/config.rs b/src/server/actors/repo/config.rs index dbf08be..604c254 100644 --- a/src/server/actors/repo/config.rs +++ b/src/server/actors/repo/config.rs @@ -11,7 +11,7 @@ use super::{LoadedConfig, RepoActor}; pub async fn load(details: RepoDetails, addr: Addr, net: Network) { let config = match details.forge.forge_type { - ForgeType::ForgeJo => forge::forgejo::config::load(details, &net).await, + ForgeType::ForgeJo => forge::forgejo::config::load(&details, &net).await, }; match config { Ok(config) => addr.do_send(LoadedConfig(config)), diff --git a/src/server/config.rs b/src/server/config.rs index 420aa73..a3de9aa 100644 --- a/src/server/config.rs +++ b/src/server/config.rs @@ -34,6 +34,10 @@ impl RepoConfig { pub(crate) fn load(toml: &str) -> Result> { toml::from_str(toml).map_err(OneOf::new) } + + pub(crate) const fn branches(&self) -> &RepoBranches { + &self.branches + } } impl Display for RepoConfig { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -46,6 +50,19 @@ pub struct RepoBranches { next: String, dev: String, } +impl RepoBranches { + pub fn main(&self) -> BranchName { + BranchName(self.main.clone()) + } + + pub fn next(&self) -> BranchName { + BranchName(self.next.clone()) + } + + pub fn dev(&self) -> BranchName { + BranchName(self.dev.clone()) + } +} impl Display for RepoBranches { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) diff --git a/src/server/forge/forgejo/config.rs b/src/server/forge/forgejo/config.rs index 3149fb5..f2c076f 100644 --- a/src/server/forge/forgejo/config.rs +++ b/src/server/forge/forgejo/config.rs @@ -3,26 +3,37 @@ use kxio::network::{self, Network}; use terrors::OneOf; use tracing::{error, info}; -use crate::server::config::{RepoConfig, RepoDetails}; +use crate::server::config::{BranchName, RepoConfig, RepoDetails}; + +#[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); + +type RepoConfigLoadErrors = ( + RepoConfigFileNotFound, + RepoConfigIsNotFile, + RepoConfigDecodeError, + RepoConfigParseError, + RepoConfigUnknownError, + RepoConfigBranchNotFound, +); pub async fn load( - details: RepoDetails, + details: &RepoDetails, net: &Network, -) -> Result< - RepoConfig, - OneOf<( - RepoConfigFileNotFound, - RepoConfigIsNotFile, - RepoConfigDecodeError, - RepoConfigParseError, - RepoConfigUnknownError, - )>, -> { - let hostname = details.forge.hostname; - let path = details.repo; +) -> Result> { + let hostname = &details.forge.hostname; + let path = &details.repo; let filepath = ".git-next.toml"; - let branch = details.branch; - let token = details.forge.token; + 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}" )); @@ -49,7 +60,7 @@ pub async fn load( )), })?; let status = response.status_code(); - match response.response_body() { + let config = match response.response_body() { Some(body) => { // we need to decode (see encoding field) the value of 'content' from the response match body.content_type { @@ -61,7 +72,11 @@ pub async fn load( error!(%status, "Failed to fetch repo config file"); Err(OneOf::new(RepoConfigUnknownError(status))) } - } + }?; + let config = validate(details, config, net) + .await + .map_err(OneOf::broaden)?; + Ok(config) } fn decode_config( @@ -81,17 +96,6 @@ fn decode_config( } } -#[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")] @@ -110,3 +114,77 @@ enum ForgeContentsType { #[serde(rename = "submodule")] Submodule, } + +#[derive(Debug)] +pub struct RepoConfigBranchNotFound(pub BranchName); + +type RepoConfigValidateErrors = (RepoConfigBranchNotFound, RepoConfigUnknownError); + +pub async fn validate( + details: &RepoDetails, + config: RepoConfig, + net: &Network, +) -> Result> { + let hostname = &details.forge.hostname; + let path = &details.repo; + let token = &details.forge.token; + let url = network::NetUrl::new(format!( + "https://{hostname}/api/v1/repos/{path}/branches?token={token}" + )); + + info!(%url, "Listing branches"); + 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| { + error!(?e, "Failed to list branches"); + OneOf::new(RepoConfigUnknownError( + network::StatusCode::INTERNAL_SERVER_ERROR, + )) + })?; + let branches = response.response_body().unwrap_or_default(); + if !branches + .iter() + .any(|branch| branch.name() == config.branches().main()) + { + return Err(OneOf::new(RepoConfigBranchNotFound( + config.branches().main(), + ))); + } + if !branches + .iter() + .any(|branch| branch.name() == config.branches().next()) + { + return Err(OneOf::new(RepoConfigBranchNotFound( + config.branches().next(), + ))); + } + if !branches + .iter() + .any(|branch| branch.name() == config.branches().dev()) + { + return Err(OneOf::new(RepoConfigBranchNotFound( + config.branches().dev(), + ))); + } + Ok(config) +} + +type BranchList = Vec; + +#[derive(Debug, serde::Deserialize)] +struct Branch { + name: String, +} +impl Branch { + fn name(&self) -> BranchName { + BranchName(self.name.clone()) + } +}