nils-api-testing-core 0.27.0

Library crate for nils-api-testing-core in the nils-cli workspace.
Documentation
use std::path::Path;

use crate::{Result, cli_util, env_file};

#[derive(Debug, Clone)]
pub struct EndpointSelection {
    pub url: String,
    pub endpoint_label_used: String,
    pub endpoint_value_used: String,
}

pub struct EndpointConfig<'a> {
    pub explicit_url: Option<&'a str>,
    pub env_name: Option<&'a str>,
    pub endpoints_env: &'a Path,
    pub endpoints_local: &'a Path,
    pub endpoints_files: &'a [&'a Path],
    pub url_env_var: &'a str,
    pub env_default_var: &'a str,
    pub url_prefix: &'a str,
    pub default_url: &'a str,
    pub setup_dir_label: &'a str,
}

pub fn resolve_cli_endpoint(cfg: EndpointConfig<'_>) -> Result<EndpointSelection> {
    let env_default = if !cfg.endpoints_files.is_empty() {
        env_file::read_var_last_wins(cfg.env_default_var, cfg.endpoints_files)?
    } else {
        None
    };

    let explicit_url = cfg.explicit_url.and_then(cli_util::trim_non_empty);
    let env_name = cfg.env_name.and_then(cli_util::trim_non_empty);

    let (url, endpoint_label_used, endpoint_value_used) = if let Some(url) = explicit_url {
        (url.clone(), "url".to_string(), url)
    } else if let Some(env_value) = env_name.as_deref() {
        if env_value.starts_with("http://") || env_value.starts_with("https://") {
            (
                env_value.to_string(),
                "url".to_string(),
                env_value.to_string(),
            )
        } else {
            if cfg.endpoints_files.is_empty() {
                anyhow::bail!(
                    "endpoints.env not found (expected under {setup_dir_label})",
                    setup_dir_label = cfg.setup_dir_label
                );
            }

            let env_key = cli_util::to_env_key(env_value);
            let key = format!("{url_prefix}{env_key}", url_prefix = cfg.url_prefix);
            let found = env_file::read_var_last_wins(&key, cfg.endpoints_files)?;
            let Some(found) = found else {
                let mut available =
                    cli_util::list_available_suffixes(cfg.endpoints_env, cfg.url_prefix);
                if cfg.endpoints_local.is_file() {
                    available.extend(cli_util::list_available_suffixes(
                        cfg.endpoints_local,
                        cfg.url_prefix,
                    ));
                    available.sort();
                    available.dedup();
                }
                let available = if available.is_empty() {
                    "none".to_string()
                } else {
                    available.join(" ")
                };
                anyhow::bail!("Unknown --env '{env_value}' (available: {available})");
            };

            (found, "env".to_string(), env_value.to_string())
        }
    } else if let Some(v) = std::env::var(cfg.url_env_var)
        .ok()
        .and_then(|s| cli_util::trim_non_empty(&s))
    {
        (v.clone(), "url".to_string(), v)
    } else if let Some(default_env) = env_default {
        if cfg.endpoints_files.is_empty() {
            anyhow::bail!(
                "{env_default_var} is set but endpoints.env not found (expected under {setup_dir_label})",
                env_default_var = cfg.env_default_var,
                setup_dir_label = cfg.setup_dir_label
            );
        }
        let env_key = cli_util::to_env_key(&default_env);
        let key = format!("{url_prefix}{env_key}", url_prefix = cfg.url_prefix);
        let found = env_file::read_var_last_wins(&key, cfg.endpoints_files)?;
        let Some(found) = found else {
            anyhow::bail!(
                "{env_default_var} is '{}' but no matching {url_prefix}* was found.",
                default_env,
                env_default_var = cfg.env_default_var,
                url_prefix = cfg.url_prefix
            );
        };
        (found, "env".to_string(), default_env)
    } else {
        let url = cfg.default_url.to_string();
        (url.clone(), "url".to_string(), url)
    };

    Ok(EndpointSelection {
        url,
        endpoint_label_used,
        endpoint_value_used,
    })
}

pub fn list_available_env_suffixes(
    endpoints_env: &Path,
    endpoints_local: &Path,
    url_prefix: &str,
    missing_message: &str,
) -> Result<Vec<String>> {
    if !endpoints_env.is_file() && !endpoints_local.is_file() {
        anyhow::bail!("{missing_message}");
    }

    let mut out = Vec::new();
    if endpoints_env.is_file() {
        out.extend(cli_util::list_available_suffixes(endpoints_env, url_prefix));
    }
    if endpoints_local.is_file() {
        out.extend(cli_util::list_available_suffixes(
            endpoints_local,
            url_prefix,
        ));
    }
    out.sort();
    out.dedup();

    Ok(out)
}