use anyhow::{bail, Result};
use crate::api::client::ApiClient;
use crate::cli::args::ConfigureCommands;
use crate::config::manager::ConfigManager;
use crate::model::loader::ModelLoader;
const CREDENTIAL_KEYS: &[&str] = &["api_key"];
pub async fn run(config_mgr: &ConfigManager, subcmd: Option<ConfigureCommands>) -> Result<()> {
match subcmd {
None => run_interactive(config_mgr).await,
Some(ConfigureCommands::Set { key, value }) => run_set(config_mgr, &key, value).await,
Some(ConfigureCommands::Get { key }) => run_get(config_mgr, &key),
Some(ConfigureCommands::List) => run_list(config_mgr),
Some(ConfigureCommands::Clear) => run_clear(config_mgr),
}
}
async fn run_interactive(config_mgr: &ConfigManager) -> Result<()> {
let api_key =
rpassword::prompt_password("Zilliz Cloud API Key: ").map(|s| s.trim().to_string())?;
validate_ascii(&api_key, "API key")?;
if api_key.is_empty() {
bail!("API key cannot be empty.");
}
validate_api_key(config_mgr, &api_key).await?;
config_mgr.set_credential("api_key", &api_key)?;
println!("API Key configured successfully.");
Ok(())
}
async fn run_set(config_mgr: &ConfigManager, key: &str, value: Option<String>) -> Result<()> {
let is_credential = CREDENTIAL_KEYS.contains(&key);
let value = match value {
Some(v) => v,
None => {
if is_credential {
rpassword::prompt_password(format!("Enter {}: ", key))
.map(|s| s.trim().to_string())?
} else {
bail!("VALUE is required for non-credential keys.");
}
}
};
if is_credential {
validate_ascii(&value, key)?;
if value.is_empty() {
bail!("{} cannot be empty.", key);
}
if key == "api_key" {
validate_api_key(config_mgr, &value).await?;
}
config_mgr.set_credential(key, &value)?;
} else {
config_mgr.set_config("default", key, &value)?;
}
let display = if is_credential {
mask_value(&value)
} else {
value
};
println!("{} = {}", key, display);
Ok(())
}
fn run_get(config_mgr: &ConfigManager, key: &str) -> Result<()> {
let is_credential = CREDENTIAL_KEYS.contains(&key);
let value = if is_credential {
config_mgr.get_credential(key)
} else {
config_mgr.get_config("default", key)
};
match value {
Some(v) => {
let display = if is_credential { mask_value(&v) } else { v };
println!("{} = {}", key, display);
}
None => {
println!("{}: not set", key);
}
}
Ok(())
}
fn run_list(config_mgr: &ConfigManager) -> Result<()> {
let (credentials, config) = config_mgr.list_all();
for (key, value) in &credentials {
println!("{} = {}", key, mask_value(value));
}
for (key, value) in &config {
println!("{} = {}", key, value);
}
Ok(())
}
fn run_clear(config_mgr: &ConfigManager) -> Result<()> {
config_mgr.clear_credentials()?;
println!("Credentials cleared.");
Ok(())
}
async fn validate_api_key(config_mgr: &ConfigManager, api_key: &str) -> Result<()> {
println!("Validating API key...");
let models = ModelLoader::load_builtin()?;
let base_url =
super::endpoint::resolve_control_plane_url(config_mgr, &models.control_plane, None);
let list_path = models
.control_plane
.resources
.get("cluster")
.and_then(|r| r.operations.get("list"))
.map(|op| op.path().to_string())
.unwrap_or_else(|| "/v2/clusters".to_string());
let client = ApiClient::new(api_key.to_string(), base_url);
client.call("GET", &list_path, None, None).await?;
Ok(())
}
fn validate_ascii(value: &str, name: &str) -> Result<()> {
if !value.is_ascii() {
bail!(
"{} contains non-ASCII characters. \
Please check your input (on macOS, use Cmd+V to paste, not Option+V).",
name
);
}
Ok(())
}
fn mask_value(value: &str) -> String {
if value.len() >= 16 {
format!("{}****{}", &value[..4], &value[value.len() - 4..])
} else {
"****".to_string()
}
}