use anyhow::Result;
use clap::Parser;
use colored::Colorize;
use dialoguer::{theme::ColorfulTheme, Input};
use mime::Mime;
use serde_json::Value;
use std::{io::Write, path::PathBuf};
use xreq_cli_utils::{get_config_file, get_default_config, parse_key_val, print_syntect};
use xreq_lib::{KeyVal, RequestConfig, RequestContext, Response};
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
#[clap(subcommand)]
action: Action,
}
#[derive(clap::Subcommand, Debug, Clone)]
enum Action {
Parse(ParseArgs),
Run(RunArgs),
}
#[derive(Parser, Debug, Clone)]
struct ParseArgs {
#[clap(short, long, value_parser, default_value = "default")]
profile: String,
#[clap(value_parser)]
url: Option<String>,
}
#[derive(Parser, Debug, Clone)]
struct RunArgs {
#[clap(short, long, value_parser)]
profile: String,
#[clap(short, value_parser = parse_key_val, number_of_values = 1)]
extra_params: Vec<KeyVal>,
#[clap(short, long, value_parser = get_config_file)]
config: Option<PathBuf>,
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
let mut output: Vec<String> = Vec::new();
match args.action {
Action::Parse(args) => parse(&mut output, args)?,
Action::Run(args) => run(&mut output, args).await?,
}
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
for line in output {
write!(stdout, "{}", line)?;
}
Ok(())
}
fn parse(output: &mut Vec<String>, ParseArgs { profile, url }: ParseArgs) -> Result<()> {
let (profile, url) = match url {
Some(url) => (profile, url),
None => {
let url = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Url to parse")
.interact()?;
let profile = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Give this url a profile name")
.default("default".into())
.interact()?;
(profile, url)
}
};
let ctx: RequestContext = url.parse()?;
let config = RequestConfig::new_with_profile(profile, ctx);
let result = serde_yaml::to_string(&config)?;
output.push("---\n".to_string());
print_syntect(output, result, "yaml")?;
Ok(())
}
async fn run(output: &mut Vec<String>, args: RunArgs) -> Result<()> {
let config_file = args.config.unwrap_or(get_default_config("xreq.yml")?);
let request_config = RequestConfig::try_load(&config_file).await?;
let mut config = request_config.get(&args.profile)?.clone();
config.update(&args.extra_params)?;
let resp = config.send().await?;
if atty::is(atty::Stream::Stdout) {
output.push(format!("Sending: {}\n\n", resp.url()));
print_status(output, &resp);
print_headers(output, &resp);
}
let mime = get_content_type(&resp);
let body = resp.text().await?;
print_body(output, mime, body)?;
Ok(())
}
fn print_status(output: &mut Vec<String>, resp: &Response) {
let status = format!("{:?} {}", resp.version(), resp.status()).blue();
output.push(format!("{}\n", status));
}
fn print_headers(output: &mut Vec<String>, resp: &Response) {
for (name, value) in resp.headers() {
output.push(format!("{}: {:?}\n", name.to_string().green(), value));
}
output.push("\n".into());
}
fn print_body(output: &mut Vec<String>, m: Option<Mime>, body: String) -> Result<()> {
match m {
Some(v) if v.essence_str() == mime::APPLICATION_JSON => {
let json: Value = serde_json::from_str(&body).unwrap();
let body = serde_json::to_string_pretty(&json).unwrap();
print_syntect(output, body, "json")
}
Some(v) if v == mime::TEXT_HTML => print_syntect(output, body, "html"),
_ => {
output.push(format!("{}\n", body));
Ok(())
}
}
}
fn get_content_type(resp: &Response) -> Option<Mime> {
resp.headers()
.get("content-type")
.map(|v| v.to_str().unwrap().parse().unwrap())
}