use std::process::ExitCode;
use clap::{Args, Subcommand};
use crate::config;
const API_KEY_ENV: &str = "AASM_API_KEY";
fn resolve_set_api_key(flag: Option<String>) -> Option<String> {
if let Some(key) = flag {
eprintln!(
"warning: passing --api-key exposes the key in process listings \
(ps, /proc/<pid>/cmdline) and shell history; prefer the \
{API_KEY_ENV} environment variable instead"
);
return Some(key);
}
std::env::var(API_KEY_ENV).ok().filter(|key| !key.is_empty())
}
#[derive(Args)]
pub struct ContextArgs {
#[command(subcommand)]
pub command: ContextCommands,
}
#[derive(Subcommand)]
pub enum ContextCommands {
List,
Set(SetArgs),
Use(UseArgs),
}
#[derive(Args)]
pub struct SetArgs {
pub name: String,
#[arg(long)]
pub api_url: String,
#[arg(long)]
pub api_key: Option<String>,
}
#[derive(Args)]
pub struct UseArgs {
pub name: String,
}
pub fn dispatch(args: ContextArgs) -> ExitCode {
match args.command {
ContextCommands::List => run_list(),
ContextCommands::Set(set_args) => run_set(set_args),
ContextCommands::Use(use_args) => run_use(use_args),
}
}
fn run_list() -> ExitCode {
let cfg = match config::load() {
Ok(c) => c,
Err(e) => {
eprintln!("error: {e}");
return ExitCode::FAILURE;
}
};
if cfg.contexts.is_empty() {
println!("No contexts configured. Use `aasm context set` to add one.");
return ExitCode::SUCCESS;
}
let default_name = cfg.default_context.as_deref().unwrap_or("");
for (name, ctx) in &cfg.contexts {
let marker = if name == default_name { " *" } else { "" };
let key_status = if ctx.api_key.is_some() { " (key set)" } else { "" };
println!("{name}{marker} {}{key_status}", ctx.api_url);
}
ExitCode::SUCCESS
}
fn run_set(args: SetArgs) -> ExitCode {
let mut cfg = match config::load() {
Ok(c) => c,
Err(e) => {
eprintln!("error: {e}");
return ExitCode::FAILURE;
}
};
cfg.contexts.insert(
args.name.clone(),
config::ContextConfig {
api_url: args.api_url,
api_key: resolve_set_api_key(args.api_key),
},
);
if cfg.contexts.len() == 1 {
cfg.default_context = Some(args.name.clone());
}
if let Err(e) = config::save(&cfg) {
eprintln!("error: {e}");
return ExitCode::FAILURE;
}
println!("Context '{}' saved.", args.name);
ExitCode::SUCCESS
}
fn run_use(args: UseArgs) -> ExitCode {
let mut cfg = match config::load() {
Ok(c) => c,
Err(e) => {
eprintln!("error: {e}");
return ExitCode::FAILURE;
}
};
if !cfg.contexts.contains_key(&args.name) {
eprintln!("error: context '{}' not found", args.name);
eprintln!("Available contexts:");
for name in cfg.contexts.keys() {
eprintln!(" {name}");
}
return ExitCode::FAILURE;
}
cfg.default_context = Some(args.name.clone());
if let Err(e) = config::save(&cfg) {
eprintln!("error: {e}");
return ExitCode::FAILURE;
}
println!("Switched to context '{}'.", args.name);
ExitCode::SUCCESS
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn api_key_flag_takes_precedence_over_env() {
let _guard = crate::test_support::env_guard();
std::env::set_var(API_KEY_ENV, "env-key");
let resolved = resolve_set_api_key(Some("flag-key".to_string()));
std::env::remove_var(API_KEY_ENV);
assert_eq!(resolved.as_deref(), Some("flag-key"));
}
#[test]
fn api_key_read_from_env_when_flag_absent() {
let _guard = crate::test_support::env_guard();
std::env::set_var(API_KEY_ENV, "env-key");
let resolved = resolve_set_api_key(None);
std::env::remove_var(API_KEY_ENV);
assert_eq!(resolved.as_deref(), Some("env-key"));
}
#[test]
fn api_key_none_when_neither_flag_nor_env_set() {
let _guard = crate::test_support::env_guard();
std::env::remove_var(API_KEY_ENV);
assert!(resolve_set_api_key(None).is_none());
}
#[test]
fn api_key_empty_env_treated_as_unset() {
let _guard = crate::test_support::env_guard();
std::env::set_var(API_KEY_ENV, "");
let resolved = resolve_set_api_key(None);
std::env::remove_var(API_KEY_ENV);
assert!(resolved.is_none());
}
}