use clap::Subcommand;
use serde::Serialize;
use crate::exit_code::ExitCode;
use crate::output::{Formatter, OutputConfig};
use rc_core::{Alias, AliasManager};
#[derive(Subcommand, Debug)]
pub enum AliasCommands {
Set(SetArgs),
List(ListArgs),
Remove(RemoveArgs),
}
#[derive(clap::Args, Debug)]
pub struct SetArgs {
pub name: String,
pub endpoint: String,
pub access_key: String,
pub secret_key: String,
#[arg(long, default_value = "us-east-1")]
pub region: String,
#[arg(long, default_value = "v4")]
pub signature: String,
#[arg(long, default_value = "auto")]
pub bucket_lookup: String,
#[arg(long, default_value = "false")]
pub insecure: bool,
}
#[derive(clap::Args, Debug)]
pub struct ListArgs {
#[arg(short, long)]
pub long: bool,
}
#[derive(clap::Args, Debug)]
pub struct RemoveArgs {
pub name: String,
}
#[derive(Serialize)]
struct AliasListOutput {
aliases: Vec<AliasInfo>,
}
#[derive(Serialize)]
struct AliasInfo {
name: String,
endpoint: String,
region: String,
bucket_lookup: String,
}
impl From<&Alias> for AliasInfo {
fn from(alias: &Alias) -> Self {
Self {
name: alias.name.clone(),
endpoint: alias.endpoint.clone(),
region: alias.region.clone(),
bucket_lookup: alias.bucket_lookup.clone(),
}
}
}
#[derive(Serialize)]
struct AliasOperationOutput {
success: bool,
alias: String,
message: String,
}
pub async fn execute(cmd: AliasCommands, output_config: OutputConfig) -> ExitCode {
let formatter = Formatter::new(output_config);
let alias_manager = match AliasManager::new() {
Ok(am) => am,
Err(e) => {
formatter.error(&format!("Failed to load aliases: {e}"));
return ExitCode::GeneralError;
}
};
match cmd {
AliasCommands::Set(args) => execute_set(args, &alias_manager, &formatter).await,
AliasCommands::List(args) => execute_list(args, &alias_manager, &formatter).await,
AliasCommands::Remove(args) => execute_remove(args, &alias_manager, &formatter).await,
}
}
async fn execute_set(args: SetArgs, manager: &AliasManager, formatter: &Formatter) -> ExitCode {
if args.name.is_empty() {
formatter.error("Alias name cannot be empty");
return ExitCode::UsageError;
}
if args.endpoint.is_empty() {
formatter.error("Endpoint URL cannot be empty");
return ExitCode::UsageError;
}
if args.signature != "v4" && args.signature != "v2" {
formatter.error("Signature must be 'v4' or 'v2'");
return ExitCode::UsageError;
}
if args.bucket_lookup != "auto" && args.bucket_lookup != "path" && args.bucket_lookup != "dns" {
formatter.error("Bucket lookup must be 'auto', 'path', or 'dns'");
return ExitCode::UsageError;
}
let mut alias = Alias::new(
&args.name,
&args.endpoint,
&args.access_key,
&args.secret_key,
);
alias.region = args.region;
alias.signature = args.signature;
alias.bucket_lookup = args.bucket_lookup;
alias.insecure = args.insecure;
match manager.set(alias) {
Ok(()) => {
if formatter.is_json() {
let output = AliasOperationOutput {
success: true,
alias: args.name.clone(),
message: format!("Alias '{}' configured successfully", args.name),
};
formatter.json(&output);
} else {
let styled_name = formatter.style_name(&args.name);
formatter.success(&format!("Alias '{styled_name}' configured successfully."));
}
ExitCode::Success
}
Err(e) => {
formatter.error(&e.to_string());
ExitCode::GeneralError
}
}
}
async fn execute_list(args: ListArgs, manager: &AliasManager, formatter: &Formatter) -> ExitCode {
match manager.list() {
Ok(aliases) => {
if formatter.is_json() {
let output = AliasListOutput {
aliases: aliases.iter().map(AliasInfo::from).collect(),
};
formatter.json(&output);
} else if aliases.is_empty() {
formatter.println("No aliases configured.");
} else if args.long {
for alias in &aliases {
let styled_name = formatter.style_name(&format!("{:<12}", alias.name));
let styled_url = formatter.style_url(&alias.endpoint);
let styled_region = formatter.style_date(&alias.region);
let styled_lookup = formatter.style_date(&alias.bucket_lookup);
formatter.println(&format!(
"{styled_name} {styled_url} (region: {styled_region}, lookup: {styled_lookup})"
));
}
} else {
for alias in &aliases {
let styled_name = formatter.style_name(&format!("{:<12}", alias.name));
let styled_url = formatter.style_url(&alias.endpoint);
formatter.println(&format!("{styled_name} {styled_url}"));
}
}
ExitCode::Success
}
Err(e) => {
formatter.error(&e.to_string());
ExitCode::GeneralError
}
}
}
async fn execute_remove(
args: RemoveArgs,
manager: &AliasManager,
formatter: &Formatter,
) -> ExitCode {
match manager.remove(&args.name) {
Ok(()) => {
if formatter.is_json() {
let output = AliasOperationOutput {
success: true,
alias: args.name.clone(),
message: format!("Alias '{}' removed successfully", args.name),
};
formatter.json(&output);
} else {
let styled_name = formatter.style_name(&args.name);
formatter.success(&format!("Alias '{styled_name}' removed successfully."));
}
ExitCode::Success
}
Err(rc_core::Error::AliasNotFound(_)) => {
formatter.error(&format!("Alias '{}' not found", args.name));
ExitCode::NotFound
}
Err(e) => {
formatter.error(&e.to_string());
ExitCode::GeneralError
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_set_args_defaults() {
let args = SetArgs {
name: "test".to_string(),
endpoint: "http://localhost:9000".to_string(),
access_key: "accesskey".to_string(),
secret_key: "secretkey".to_string(),
region: "us-east-1".to_string(),
signature: "v4".to_string(),
bucket_lookup: "auto".to_string(),
insecure: false,
};
assert_eq!(args.region, "us-east-1");
assert_eq!(args.signature, "v4");
assert_eq!(args.bucket_lookup, "auto");
assert!(!args.insecure);
}
#[test]
fn test_alias_info_from_alias() {
let alias = Alias::new("test", "http://localhost:9000", "key", "secret");
let info = AliasInfo::from(&alias);
assert_eq!(info.name, "test");
assert_eq!(info.endpoint, "http://localhost:9000");
assert_eq!(info.region, "us-east-1");
}
}