use clap::{Parser, ValueEnum};
use colored::Colorize;
use rdap::bootstrap::rir_to_rdap_url;
use rdap::{QueryType, RdapClient, RdapRequest, display::RdapDisplay};
use std::process;
#[derive(Parser)]
#[command(name = "rdap")]
#[command(author, version, about = "Modern RDAP client", long_about = None)]
struct Cli {
query: Option<String>,
#[arg(short, long, conflicts_with = "rir")]
server: Option<String>,
#[arg(short = 'r', long, conflicts_with = "server")]
rir: Option<String>,
#[arg(short = 't', long)]
query_type: Option<QueryTypeArg>,
#[arg(short = 'f', long, default_value = "text")]
format: OutputFormat,
#[arg(short, long)]
verbose: bool,
#[arg(long, default_value = "30")]
timeout: u64,
#[arg(short = 'k', long)]
insecure: bool,
}
#[derive(Debug, Clone, ValueEnum)]
enum QueryTypeArg {
Domain,
Ip,
Autnum,
Entity,
Nameserver,
Help,
DomainSearch,
DomainSearchByNameserver,
DomainSearchByNameserverIp,
NameserverSearch,
NameserverSearchByIp,
EntitySearch,
EntitySearchByHandle,
}
impl From<QueryTypeArg> for QueryType {
fn from(arg: QueryTypeArg) -> Self {
match arg {
QueryTypeArg::Domain => QueryType::Domain,
QueryTypeArg::Ip => QueryType::Ip,
QueryTypeArg::Autnum => QueryType::Autnum,
QueryTypeArg::Entity => QueryType::Entity,
QueryTypeArg::Nameserver => QueryType::Nameserver,
QueryTypeArg::Help => QueryType::Help,
QueryTypeArg::DomainSearch => QueryType::DomainSearch,
QueryTypeArg::DomainSearchByNameserver => QueryType::DomainSearchByNameserver,
QueryTypeArg::DomainSearchByNameserverIp => QueryType::DomainSearchByNameserverIp,
QueryTypeArg::NameserverSearch => QueryType::NameserverSearch,
QueryTypeArg::NameserverSearchByIp => QueryType::NameserverSearchByIp,
QueryTypeArg::EntitySearch => QueryType::EntitySearch,
QueryTypeArg::EntitySearchByHandle => QueryType::EntitySearchByHandle,
}
}
}
#[derive(Debug, Clone, ValueEnum)]
enum OutputFormat {
Text,
Json,
JsonPretty,
Whois,
}
#[tokio::main]
async fn main() {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
let cli = Cli::parse();
if let Err(e) = run(cli).await {
eprintln!("{} {}", "Error:".bright_red().bold(), e);
process::exit(1);
}
}
async fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
use colored::Colorize;
let query = cli.query.ok_or("Query is required")?;
let query_type = if let Some(qt) = cli.query_type {
qt.into()
} else if cli.rir.is_some() {
QueryType::Entity
} else {
RdapRequest::detect_type(&query)?
};
if cli.verbose {
eprintln!("{} Query: {}", "→".bright_blue(), query.bright_white());
eprintln!(
"{} Type: {}",
"→".bright_blue(),
format!("{}", query_type).bright_yellow()
);
}
let mut request = RdapRequest::new(query_type, query);
if let Some(server_url) = cli.server {
let url = url::Url::parse(&server_url)?;
request = request.with_server(url);
if cli.verbose {
eprintln!(
"{} Server: {}",
"→".bright_blue(),
server_url.bright_green()
);
}
} else if let Some(rir_name) = &cli.rir {
let url = rir_to_rdap_url(rir_name).ok_or_else(|| {
format!(
"Unknown RIR '{}'. Supported: ripe, arin, apnic, lacnic, afrinic, frnic, glauca, norid",
rir_name
)
})?;
if cli.verbose {
eprintln!(
"{} RIR: {} ({})",
"→".bright_blue(),
rir_name.to_uppercase().bright_green(),
url.as_str().bright_cyan()
);
}
request = request.with_server(url);
}
let client = RdapClient::new()?.with_timeout(std::time::Duration::from_secs(cli.timeout));
if cli.verbose {
eprintln!("\n{} Querying RDAP server...\n", "⟳".bright_blue());
}
let result = client.query(&request).await?;
match cli.format {
OutputFormat::Text => {
result.display(cli.verbose);
}
OutputFormat::Json => {
let json = serde_json::to_string(&result)?;
println!("{}", json);
}
OutputFormat::JsonPretty => {
let json = serde_json::to_string_pretty(&result)?;
println!("{}", json);
}
OutputFormat::Whois => {
rdap::whois::display_whois(&result);
}
}
Ok(())
}