api_testing_core/
cli_endpoint.rs1use std::path::Path;
2
3use crate::{Result, cli_util, env_file};
4
5#[derive(Debug, Clone)]
6pub struct EndpointSelection {
7 pub url: String,
8 pub endpoint_label_used: String,
9 pub endpoint_value_used: String,
10}
11
12pub struct EndpointConfig<'a> {
13 pub explicit_url: Option<&'a str>,
14 pub env_name: Option<&'a str>,
15 pub endpoints_env: &'a Path,
16 pub endpoints_local: &'a Path,
17 pub endpoints_files: &'a [&'a Path],
18 pub url_env_var: &'a str,
19 pub env_default_var: &'a str,
20 pub url_prefix: &'a str,
21 pub default_url: &'a str,
22 pub setup_dir_label: &'a str,
23}
24
25pub fn resolve_cli_endpoint(cfg: EndpointConfig<'_>) -> Result<EndpointSelection> {
26 let env_default = if !cfg.endpoints_files.is_empty() {
27 env_file::read_var_last_wins(cfg.env_default_var, cfg.endpoints_files)?
28 } else {
29 None
30 };
31
32 let explicit_url = cfg.explicit_url.and_then(cli_util::trim_non_empty);
33 let env_name = cfg.env_name.and_then(cli_util::trim_non_empty);
34
35 let (url, endpoint_label_used, endpoint_value_used) = if let Some(url) = explicit_url {
36 (url.clone(), "url".to_string(), url)
37 } else if let Some(env_value) = env_name.as_deref() {
38 if env_value.starts_with("http://") || env_value.starts_with("https://") {
39 (
40 env_value.to_string(),
41 "url".to_string(),
42 env_value.to_string(),
43 )
44 } else {
45 if cfg.endpoints_files.is_empty() {
46 anyhow::bail!(
47 "endpoints.env not found (expected under {setup_dir_label})",
48 setup_dir_label = cfg.setup_dir_label
49 );
50 }
51
52 let env_key = cli_util::to_env_key(env_value);
53 let key = format!("{url_prefix}{env_key}", url_prefix = cfg.url_prefix);
54 let found = env_file::read_var_last_wins(&key, cfg.endpoints_files)?;
55 let Some(found) = found else {
56 let mut available =
57 cli_util::list_available_suffixes(cfg.endpoints_env, cfg.url_prefix);
58 if cfg.endpoints_local.is_file() {
59 available.extend(cli_util::list_available_suffixes(
60 cfg.endpoints_local,
61 cfg.url_prefix,
62 ));
63 available.sort();
64 available.dedup();
65 }
66 let available = if available.is_empty() {
67 "none".to_string()
68 } else {
69 available.join(" ")
70 };
71 anyhow::bail!("Unknown --env '{env_value}' (available: {available})");
72 };
73
74 (found, "env".to_string(), env_value.to_string())
75 }
76 } else if let Some(v) = std::env::var(cfg.url_env_var)
77 .ok()
78 .and_then(|s| cli_util::trim_non_empty(&s))
79 {
80 (v.clone(), "url".to_string(), v)
81 } else if let Some(default_env) = env_default {
82 if cfg.endpoints_files.is_empty() {
83 anyhow::bail!(
84 "{env_default_var} is set but endpoints.env not found (expected under {setup_dir_label})",
85 env_default_var = cfg.env_default_var,
86 setup_dir_label = cfg.setup_dir_label
87 );
88 }
89 let env_key = cli_util::to_env_key(&default_env);
90 let key = format!("{url_prefix}{env_key}", url_prefix = cfg.url_prefix);
91 let found = env_file::read_var_last_wins(&key, cfg.endpoints_files)?;
92 let Some(found) = found else {
93 anyhow::bail!(
94 "{env_default_var} is '{}' but no matching {url_prefix}* was found.",
95 default_env,
96 env_default_var = cfg.env_default_var,
97 url_prefix = cfg.url_prefix
98 );
99 };
100 (found, "env".to_string(), default_env)
101 } else {
102 let url = cfg.default_url.to_string();
103 (url.clone(), "url".to_string(), url)
104 };
105
106 Ok(EndpointSelection {
107 url,
108 endpoint_label_used,
109 endpoint_value_used,
110 })
111}
112
113pub fn list_available_env_suffixes(
114 endpoints_env: &Path,
115 endpoints_local: &Path,
116 url_prefix: &str,
117 missing_message: &str,
118) -> Result<Vec<String>> {
119 if !endpoints_env.is_file() && !endpoints_local.is_file() {
120 anyhow::bail!("{missing_message}");
121 }
122
123 let mut out = Vec::new();
124 if endpoints_env.is_file() {
125 out.extend(cli_util::list_available_suffixes(endpoints_env, url_prefix));
126 }
127 if endpoints_local.is_file() {
128 out.extend(cli_util::list_available_suffixes(
129 endpoints_local,
130 url_prefix,
131 ));
132 }
133 out.sort();
134 out.dedup();
135
136 Ok(out)
137}