cloud_terrastodon_azure_devops 0.35.0

Helpers for interacting with Azure DevOps for the Cloud Terrastodon project
use crate::get_azure_devops_cli_config;
use cloud_terrastodon_azure_devops_types::AzureDevOpsProjectName;
use cloud_terrastodon_command::CacheInvalidatable;
use cloud_terrastodon_command::async_trait;
use eyre::OptionExt;
use eyre::bail;
use std::pin::Pin;

#[must_use = "This is a future request, you must .await it"]
pub struct DefaultAzureDevOpsProjectNameRequest;

pub fn get_default_project_name() -> DefaultAzureDevOpsProjectNameRequest {
    DefaultAzureDevOpsProjectNameRequest
}

#[async_trait]
impl CacheInvalidatable for DefaultAzureDevOpsProjectNameRequest {
    async fn invalidate(&self) -> eyre::Result<()> {
        get_azure_devops_cli_config().invalidate().await?;
        Ok(())
    }
}

impl IntoFuture for DefaultAzureDevOpsProjectNameRequest {
    type Output = eyre::Result<AzureDevOpsProjectName>;
    type IntoFuture = Pin<Box<dyn std::future::Future<Output = Self::Output> + Send>>;

    fn into_future(self) -> Self::IntoFuture {
        Box::pin(async move {
            let config = get_azure_devops_cli_config().await?;
            let project = config
                .lines()
                .find(|line| line.contains("project"))
                .ok_or_eyre("Expected project to be configured using `az devops configure --defaults project=MyProject`")?;
            let Some((_, project)) = project.split_once('=') else {
                bail!("Expected project to have a slash before the name, found {project:?}");
            };

            let project = project.trim();
            AzureDevOpsProjectName::try_new(project.to_string())
        })
    }
}

#[cfg(test)]
mod test {
    use crate::get_default_project_name;

    #[tokio::test]
    pub async fn it_works() -> eyre::Result<()> {
        let x = get_default_project_name().await?;
        assert!(!format!("{x:?}").is_empty());
        Ok(())
    }
}