xbp 10.30.3

XBP is a zero-config build pack that can also interact with proxies, kafka, sockets, synthetic monitors.
Documentation
use crate::commands::cli_session::fetch_cloudflare_credentials_from_dashboard;
use crate::commands::cloudflare_config::{
    ensure_cloudflare_credentials_interactive, is_interactive_terminal,
};
use crate::config::{resolve_cloudflare_account_id, resolve_cloudflare_api_token};
use crate::provider_support::cloudflare::CloudflareClient;

#[derive(Debug, Clone, Default)]
pub struct CloudflareCredentialOverrides {
    pub token: Option<String>,
    pub account_id: Option<String>,
}

pub async fn resolve_cloudflare_credentials(
    overrides: CloudflareCredentialOverrides,
    require_account_id: bool,
) -> Result<(String, String), String> {
    match try_resolve_cloudflare_credentials(&overrides, require_account_id).await {
        Ok(credentials) => Ok(credentials),
        Err(error) if is_interactive_terminal() && overrides_are_empty(&overrides) => {
            ensure_cloudflare_credentials_interactive().await?;
            try_resolve_cloudflare_credentials(&overrides, require_account_id).await
                .map_err(|_| error)
        }
        Err(error) => Err(error),
    }
}

pub async fn build_cloudflare_client(
    overrides: CloudflareCredentialOverrides,
    require_account_id: bool,
) -> Result<CloudflareClient, String> {
    let (token, account_id) =
        resolve_cloudflare_credentials(overrides, require_account_id).await?;
    CloudflareClient::new(token, account_id)
}

async fn try_resolve_cloudflare_credentials(
    overrides: &CloudflareCredentialOverrides,
    require_account_id: bool,
) -> Result<(String, String), String> {
    let token_override = normalize_override(overrides.token.clone());
    let account_id_override = normalize_override(overrides.account_id.clone());

    if let Some(token) = token_override.clone() {
        let account_id = resolve_account_id(account_id_override, require_account_id)?;
        return Ok((token, account_id));
    }

    if let Some(token) = resolve_cloudflare_api_token() {
        let account_id = resolve_account_id(account_id_override, require_account_id)?;
        return Ok((token, account_id));
    }

    if let Some(credentials) = fetch_cloudflare_credentials_from_dashboard().await? {
        let account_id = account_id_override.unwrap_or(credentials.account_id);
        return Ok((credentials.access_token, account_id));
    }

    Err(missing_cloudflare_credentials_message())
}

fn resolve_account_id(
    account_id_override: Option<String>,
    require_account_id: bool,
) -> Result<String, String> {
    if let Some(account_id) = account_id_override.or_else(resolve_cloudflare_account_id) {
        return Ok(account_id);
    }

    if require_account_id {
        return Err(missing_cloudflare_account_id_message());
    }

    Ok(String::new())
}

fn overrides_are_empty(overrides: &CloudflareCredentialOverrides) -> bool {
    overrides.token.is_none() && overrides.account_id.is_none()
}

fn normalize_override(value: Option<String>) -> Option<String> {
    value
        .map(|value| value.trim().to_string())
        .filter(|value| !value.is_empty())
}

fn missing_cloudflare_credentials_message() -> String {
    "Cloudflare credentials are not configured.".to_string()
}

fn missing_cloudflare_account_id_message() -> String {
    "Cloudflare account ID is not configured.".to_string()
}

#[cfg(test)]
mod tests {
    use super::{normalize_override, overrides_are_empty, CloudflareCredentialOverrides};

    #[test]
    fn normalize_override_trims_and_rejects_empty_values() {
        assert_eq!(
            normalize_override(Some("  token  ".to_string())),
            Some("token".to_string())
        );
        assert_eq!(normalize_override(Some("   ".to_string())), None);
        assert_eq!(normalize_override(None), None);
    }

    #[test]
    fn detects_empty_overrides() {
        assert!(overrides_are_empty(&CloudflareCredentialOverrides::default()));
        assert!(!overrides_are_empty(&CloudflareCredentialOverrides {
            token: Some("token".to_string()),
            account_id: None,
        }));
    }
}