From 7995282f65183b0a615c224a3ea13eeb10a1e828 Mon Sep 17 00:00:00 2001 From: Filip Wandzio Date: Sun, 28 Dec 2025 00:45:12 +0100 Subject: Kind of Initial Comment Implement basic way of testing various http methods via simple cli interface. Also write basic config file kind of parser. --- main/Cargo.toml | 15 +++++ main/src/main.rs | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 main/Cargo.toml create mode 100644 main/src/main.rs (limited to 'main') diff --git a/main/Cargo.toml b/main/Cargo.toml new file mode 100644 index 0000000..e91ddbf --- /dev/null +++ b/main/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "pdat" +version = "0.1.0" +edition = "2024" + +[dependencies] +clap = {version = "4.5.48", features = ["derive"] } +colored = "3.0.0" +reqwest = {version = "0.12.23", features = ["json"] } +serde_json = {version = "1.0.145"} +tokio = {version = "1.47.1", features = ["full"]} +toml = "0.9.7" + + +config = { path = "../config" } diff --git a/main/src/main.rs b/main/src/main.rs new file mode 100644 index 0000000..0604673 --- /dev/null +++ b/main/src/main.rs @@ -0,0 +1,185 @@ +use clap::Parser; +use colored::*; +use config::Config; +use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; +use serde_json::Value; +use std::process; + +#[derive(Parser)] +#[command(author, version, about)] +struct Args { + method: String, + url: String, + #[arg(short = 'H', long)] + header: Vec, + #[arg(short = 'd', long)] + data: Option, + #[arg(short = 'c', long)] + config: Option, + #[arg(long)] + indent: Option, +} + +fn print_colored_json(value: &Value, indent_level: usize, indent_spaces: usize) { + let indent = " ".repeat(indent_spaces * indent_level); + + match value { + Value::Object(map) => { + println!("{}{}", indent, "{".bright_blue()); + for (key, val) in map { + print!( + "{}{}: ", + " ".repeat(indent_spaces * (indent_level + 1)), + key.bright_cyan() + ); + print_colored_json(val, indent_level + 1, indent_spaces); + } + println!("{}{}", indent, "}".bright_blue()); + } + Value::Array(arr) => { + println!("{}{}", indent, "[".bright_blue()); + for val in arr { + print_colored_json(val, indent_level + 1, indent_spaces); + } + println!("{}{}", indent, "]".bright_blue()); + } + Value::String(s) => println!("{}{}", indent, format!("\"{}\"", s).bright_yellow()), + Value::Number(n) => println!("{}{}", indent, n.to_string().bright_magenta()), + Value::Bool(b) => println!("{}{}", indent, b.to_string().bright_green()), + Value::Null => println!("{}{}", indent, "null".bright_black()), + } +} + +fn colorize_status(status_code: u16) -> ColoredString { + match status_code { + 100..=199 => status_code.to_string().bright_blue(), + 200..=299 => status_code.to_string().green(), + 300..=399 => status_code.to_string().cyan(), + 400..=499 => status_code.to_string().yellow(), + 500..=599 => status_code.to_string().red(), + _ => status_code.to_string().normal(), + } +} + +fn build_request( + client: &reqwest::Client, + method: &str, + url: &str, + headers: HeaderMap, +) -> Result { + let method = method.to_uppercase(); + let builder = match method.as_str() { + "GET" => Ok(client.get(url)), + "POST" => Ok(client.post(url)), + "PUT" => Ok(client.put(url)), + "DELETE" => Ok(client.delete(url)), + "PATCH" => Ok(client.patch(url)), + "OPTIONS" => Ok(client.request(reqwest::Method::OPTIONS, url)), + "HEAD" => Ok(client.head(url)), + other => Err(format!("Unsupported HTTP method: {}", other)), + }?; + Ok(builder.headers(headers)) +} + +#[tokio::main] +async fn main() { + let args = Args::parse(); + let client = reqwest::Client::new(); + + let config = match args.config.as_ref().map(|p| Config::from_file(p)) { + Some(Ok(cfg)) => Some(cfg), + Some(Err(e)) => { + eprintln!("{}: Failed to load config: {}", "Error".red(), e); + process::exit(1); + } + None => None, + }; + + let mut headers = HeaderMap::new(); + + if let Some(cfg) = &config { + for header_str in cfg.headers() { + match header_str.split_once(':') { + Some((key, value)) => { + if let (Ok(k), Ok(v)) = ( + HeaderName::from_bytes(key.trim().as_bytes()), + HeaderValue::from_str(value.trim()), + ) { + headers.insert(k, v); + } + } + None => eprintln!( + "{}: Invalid header format in config: {}", + "Warning".yellow(), + header_str + ), + } + } + } + + for header_str in &args.header { + match header_str.split_once(':') { + Some((key, value)) => { + if let (Ok(k), Ok(v)) = ( + HeaderName::from_bytes(key.trim().as_bytes()), + HeaderValue::from_str(value.trim()), + ) { + headers.insert(k, v); + } + } + None => eprintln!( + "{}: Invalid header format in CLI: {}", + "Warning".yellow(), + header_str + ), + } + } + + let indent_spaces = args + .indent + .or_else(|| config.as_ref().map(|c| c.indent())) + .unwrap_or(2); + + let mut request_builder = match build_request(&client, &args.method, &args.url, headers) { + Ok(r) => r, + Err(e) => { + eprintln!("{}: {}", "Error".red(), e); + process::exit(1); + } + }; + + if let Some(json_data) = args.data { + match serde_json::from_str::(&json_data) { + Ok(json_value) => request_builder = request_builder.json(&json_value), + Err(e) => { + eprintln!("{}: Invalid JSON body: {}", "Error".red(), e); + process::exit(1); + } + } + } + + let response = match request_builder.send().await { + Ok(resp) => resp, + Err(e) => { + eprintln!("{}: Request failed: {}", "Error".red(), e); + process::exit(1); + } + }; + + let status_code = response.status().as_u16(); + println!("Status: {}", colorize_status(status_code)); + + let response_text = match response.text().await { + Ok(t) => t, + Err(e) => { + eprintln!("{}: Failed to read response body: {}", "Error".red(), e); + process::exit(1); + } + }; + + if let Ok(json_value) = serde_json::from_str::(&response_text) { + print_colored_json(&json_value, 0, indent_spaces); + } else { + println!("{}", response_text); + } +} -- cgit v1.2.3