use anyhow::{Context, Result};
use colored::Colorize;
use tsafe_core::{
audit::AuditEntry,
errors::SafeError,
namespace_bulk::{apply_namespace_copy, apply_namespace_move, plan_namespace_bulk},
};
use tsafe_cli::cli::NsAction;
use crate::helpers::{audit, open_vault};
pub(crate) fn cmd_ns(profile: &str, action: NsAction) -> Result<()> {
match action {
NsAction::List => {
let vault = open_vault(profile)?;
let mut namespaces: std::collections::BTreeSet<String> =
std::collections::BTreeSet::new();
for key in vault.list() {
if let Some((ns, _)) = key.split_once('/') {
namespaces.insert(ns.to_string());
}
}
if namespaces.is_empty() {
println!(
"{} No namespaces in profile '{profile}' — all keys are in the root namespace",
"i".blue()
);
} else {
for ns in &namespaces {
let count = vault
.list()
.iter()
.filter(|k| k.starts_with(&format!("{ns}/")))
.count();
println!("{ns} ({count} key{})", if count == 1 { "" } else { "s" });
}
}
Ok(())
}
NsAction::Copy { from, to, force } => ns_copy_or_move(profile, &from, &to, force, false),
NsAction::Move { from, to, force } => ns_copy_or_move(profile, &from, &to, force, true),
}
}
fn ns_copy_or_move(profile: &str, from: &str, to: &str, force: bool, is_move: bool) -> Result<()> {
let mut vault = open_vault(profile)?;
let pairs = plan_namespace_bulk(&vault, from, to, force).map_err(|e| anyhow::anyhow!("{e}"))?;
if pairs.is_empty() {
println!(
"{} No keys under namespace '{}' in profile '{}'",
"i".blue(),
from,
profile
);
return Ok(());
}
if is_move {
apply_namespace_move(&mut vault, &pairs, force).map_err(|e| match &e {
SafeError::SecretNotFound { .. } => {
anyhow::anyhow!("secret not found (vault changed?)")
}
SafeError::SecretAlreadyExists { .. } => {
anyhow::anyhow!("destination already exists — use --force to overwrite")
}
_ => anyhow::anyhow!("{e}"),
})?;
let op = "ns-move";
let detail = format!("{from}->{to} ({} keys)", pairs.len());
let _ = audit(profile).append(&AuditEntry::success(profile, op, Some(&detail)));
println!(
"{} Moved {} key(s) from '{}/' → '{}/'",
"✓".green(),
pairs.len(),
from,
to
);
} else {
apply_namespace_copy(&mut vault, &pairs).with_context(|| "namespace copy")?;
let op = "ns-copy";
let detail = format!("{from}->{to} ({} keys)", pairs.len());
let _ = audit(profile).append(&AuditEntry::success(profile, op, Some(&detail)));
println!(
"{} Copied {} key(s) from '{}/' → '{}/' (sources unchanged)",
"✓".green(),
pairs.len(),
from,
to
);
}
Ok(())
}