use vta_sdk::prelude::*;
use crate::display::shorten_did;
use crate::render::{BOLD, CYAN, DIM, GREEN, RESET, YELLOW, is_json_output, print_json};
fn qualified(did: &str, name: &str) -> String {
match domain_of(did) {
Some(domain) => format!("{domain}/@{name}"),
None => format!("@{name}"),
}
}
fn domain_of(did: &str) -> Option<String> {
let mut parts = did.split(':');
if parts.next()? != "did" {
return None;
}
let method = parts.next()?;
if method != "webvh" && method != "web" {
return None;
}
let _scid = parts.next()?;
let host = parts.next()?;
Some(host.replace("%3A", ":").replace("%3a", ":"))
}
pub async fn cmd_agent_name_set(
client: &VtaClient,
did: &str,
name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let result = client.set_agent_name(did, name).await?;
if is_json_output() {
print_json(&result)?;
return Ok(());
}
println!("{GREEN}\u{2713}{RESET} Agent name bound.");
println!(
" {CYAN}Name:{RESET} {BOLD}{}{RESET}",
qualified(did, name)
);
println!(" {CYAN}DID:{RESET} {did}");
println!(
" {DIM}The DID document now claims this name; the hosting server \
serves it as a redirect.{RESET}"
);
Ok(())
}
pub async fn cmd_agent_name_remove(
client: &VtaClient,
did: &str,
name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let result = client.remove_agent_name(did, name).await?;
if is_json_output() {
print_json(&result)?;
return Ok(());
}
println!(
"{GREEN}\u{2713}{RESET} Agent name released: {}",
qualified(did, name)
);
println!(
" {DIM}The claim is gone from the DID document and the name is free \
for anyone to claim. Use `disable` instead to keep it reserved.{RESET}"
);
Ok(())
}
pub async fn cmd_agent_name_disable(
client: &VtaClient,
did: &str,
name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let result = client.disable_agent_name(did, name).await?;
if is_json_output() {
print_json(&result)?;
return Ok(());
}
println!(
"{GREEN}\u{2713}{RESET} Agent name parked: {}",
qualified(did, name)
);
println!(
" {DIM}It no longer resolves, but stays reserved to this DID. \
Re-enable with `agent-names enable`.{RESET}"
);
Ok(())
}
pub async fn cmd_agent_name_enable(
client: &VtaClient,
did: &str,
name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let result = client.enable_agent_name(did, name).await?;
if is_json_output() {
print_json(&result)?;
return Ok(());
}
println!(
"{GREEN}\u{2713}{RESET} Agent name back in service: {}",
qualified(did, name)
);
Ok(())
}
pub async fn cmd_agent_name_list(
client: &VtaClient,
did: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let result = client.list_agent_names(did).await?;
if is_json_output() {
print_json(&result)?;
return Ok(());
}
if result.names.is_empty() {
println!("No agent names bound to {}.", shorten_did(did));
println!(" {DIM}Bind one with `agent-names set --did <did> --name <name>`.{RESET}");
return Ok(());
}
println!();
println!("{BOLD}Agent names for {}{RESET}", shorten_did(did));
println!();
for entry in &result.names {
let state = if entry.enabled {
format!("{GREEN}resolves{RESET}")
} else {
format!("{YELLOW}parked{RESET}")
};
println!(" {BOLD}{}{RESET} {state}", qualified(did, &entry.name));
}
println!();
Ok(())
}
pub async fn cmd_agent_name_check(
client: &VtaClient,
did: &str,
name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let result = client.check_agent_name(did, name).await?;
if is_json_output() {
print_json(&result)?;
return Ok(());
}
let full = format!("{}/@{}", result.domain, result.name);
if result.reserved {
println!("{YELLOW}\u{2717}{RESET} {full} is reserved.");
println!(
" {DIM}Reserved names (admin, api, support, …) are withheld to \
make impersonation harder.{RESET}"
);
} else if result.available {
println!("{GREEN}\u{2713}{RESET} {full} is available.");
println!(
" {DIM}Advisory only — it can be taken before you bind it, so \
`set` reports its own conflict.{RESET}"
);
} else {
println!("{YELLOW}\u{2717}{RESET} {full} is taken.");
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn domain_comes_from_the_did_not_the_operator() {
assert_eq!(
domain_of("did:webvh:QmScid:webvh.storm.ws:glenn-vta").as_deref(),
Some("webvh.storm.ws")
);
assert_eq!(
domain_of("did:web:QmScid:example.com").as_deref(),
Some("example.com")
);
}
#[test]
fn a_ported_host_is_percent_decoded() {
assert_eq!(
domain_of("did:webvh:QmScid:localhost%3A8534").as_deref(),
Some("localhost:8534")
);
}
#[test]
fn a_non_web_did_has_no_domain() {
assert!(domain_of("did:key:z6MkfrQjWz").is_none());
assert!(domain_of("not-a-did").is_none());
}
#[test]
fn qualified_falls_back_to_the_bare_local_part() {
assert_eq!(qualified("did:key:z6MkfrQjWz", "ops"), "@ops");
assert_eq!(
qualified("did:webvh:QmScid:webvh.storm.ws", "ops"),
"webvh.storm.ws/@ops"
);
}
}