use std::io;
use clap::{Args, Parser};
use clap_cargo::style::CLAP_STYLING;
use clap_verbosity::{InfoLevel, Verbosity};
use color_eyre::{
eyre::{bail, Context},
Result,
};
use colored_json::ToColoredJson;
use http::Uri;
use tracing::{debug, warn};
use tracing_log::AsTrace;
use webfinger_rs::{Rel, WebFingerRequest};
#[derive(Debug, Parser)]
#[clap(styles = CLAP_STYLING)]
struct Cli {
#[command(flatten)]
fetch_command: FetchCommand,
#[command(flatten)]
verbosity: Verbosity<InfoLevel>,
}
#[derive(Debug, Args)]
#[command(next_line_help = false)]
struct FetchCommand {
resource: String,
host: Option<String>,
#[arg(short, long)]
rel: Vec<String>,
#[arg(long)]
insecure: bool,
}
#[tokio::main]
async fn main() -> Result<()> {
color_eyre::install()?;
let args = Cli::parse();
let log_level = args.verbosity.log_level_filter().as_trace();
tracing_subscriber::fmt()
.with_max_level(log_level)
.with_writer(io::stderr)
.init();
args.fetch_command.execute().await?;
Ok(())
}
impl FetchCommand {
async fn execute(&self) -> Result<()> {
let request = WebFingerRequest {
host: self.host()?,
resource: self.resource()?,
rels: self.link_relations(),
};
debug!("fetching webfinger resource: {:?}", request);
if self.insecure {
warn!("ignoring TLS certificate verification errors");
}
let client = reqwest::Client::builder()
.danger_accept_invalid_certs(self.insecure)
.build()?;
let response = request.execute_reqwest_with_client(&client).await?;
let json = response.to_string().to_colored_json_auto()?;
println!("{json}");
Ok(())
}
fn host(&self) -> Result<String> {
if let Some(host) = self.host.as_deref() {
Ok(host.to_string())
} else if let Some((_, host)) = self.resource.split_once('@') {
debug!("extracted host from resource: {}", host);
Ok(host.to_string())
} else {
bail!("no host provided")
}
}
fn resource(&self) -> Result<Uri> {
self.resource.parse().wrap_err("invalid resource")
}
fn link_relations(&self) -> Vec<Rel> {
self.rel.iter().map(|s| Rel::from(s.as_str())).collect()
}
}