diff --git a/Cargo.lock b/Cargo.lock index 11b448a..77d6e79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,6 +162,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "config-file" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df51e72c150781d2c7d4cbcb0b803277caaa80476786994a62961a8f1010dafb" +dependencies = [ + "serde", + "thiserror", + "toml", +] + [[package]] name = "cookie" version = "0.18.1" @@ -302,8 +313,10 @@ version = "0.1.3" dependencies = [ "clap", "colored", + "config-file", "cookie", "reqwest", + "serde", "serde_json", "serde_json5", "tokio", @@ -1367,6 +1380,26 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.3.44" @@ -1469,6 +1502,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "tower" version = "0.5.2" diff --git a/Cargo.toml b/Cargo.toml index f7a8c04..a73849f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,5 +13,7 @@ reqwest = { version = "0.12", features = ["cookies"] } cookie = "0.18" tokio = { version = "1.48", features = ["full"] } colored = "3.0" +serde = "1.0" serde_json = "1.0" serde_json5 = "0.2" +config-file = "0.2" diff --git a/src/main.rs b/src/main.rs index 90be416..bffa2e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,17 @@ -use std::sync::Arc; +use std::{path::PathBuf, sync::Arc}; use clap::{Parser, ValueEnum}; use colored::*; +use config_file::FromConfigFile; use reqwest::{ ClientBuilder, Request, header::{HeaderName, HeaderValue}, }; +use serde::Deserialize; -#[derive(Debug, Copy, Clone, ValueEnum)] +#[derive(Debug, Default, Copy, Clone, ValueEnum, Deserialize)] enum Method { + #[default] GET, POST, PUT, @@ -52,21 +55,23 @@ impl From for reqwest::Method { } } -#[derive(Parser, Debug)] +#[derive(Parser, Debug, Deserialize)] #[command(version, about)] struct Cli { /// The URL to request - url: String, + url: Option, - #[arg(short, long, default_value_t = Method::GET)] - method: Method, + #[arg(short, long)] + method: Option, /// Add a header to the request #[arg(short = 'H', long = "header", value_name = "NAME=VALUE")] + #[serde(default)] headers: Vec, /// Add a cookie to the request #[arg(short = 'c', long = "cookie", value_name = "NAME=VALUE")] + #[serde(default)] cookies: Vec, /// Short hand notation for the `Authorization` header @@ -79,7 +84,13 @@ struct Cli { /// Forces the body to be valid JSON #[arg(short = 'j', long = "json-body")] - body_is_json: bool, + json_body: Option, + + /// Path to a TOML configuration file. + /// Configuration from the command-line takes precedence. + #[arg(long = "config")] + #[serde(skip)] + config_file: Option, } #[tokio::main] @@ -108,9 +119,23 @@ impl std::fmt::Display for Error { impl std::error::Error for Error {} async fn run() -> std::result::Result<(), Box> { - let args = Cli::parse(); + let mut args = Cli::parse(); + + if let Some(config_file) = args.config_file { + let mut config = Cli::from_config_file(config_file)?; + + args.url = args.url.or(config.url); + args.method = args.method.or(config.method); + args.headers.append(&mut config.headers); + args.cookies.append(&mut config.cookies); + args.auth = args.auth.or(config.auth); + args.body = args.body.or(config.body); + args.json_body = args.json_body.or(config.json_body); + } + + let Some(url) = args.url else { return Ok(()) }; - let url = reqwest::Url::parse(&args.url)?; + let url = reqwest::Url::parse(&url)?; let jar = reqwest::cookie::Jar::default(); for cookie in &args.cookies { @@ -121,7 +146,7 @@ async fn run() -> std::result::Result<(), Box> { .cookie_provider(Arc::new(jar)) .build()?; - let method = args.method.into(); + let method = args.method.unwrap_or_default().into(); let mut request = Request::new(method, url); @@ -143,7 +168,7 @@ async fn run() -> std::result::Result<(), Box> { } if let Some(body) = args.body { - if args.body_is_json { + if args.json_body.unwrap_or_default() { if let Err(err) = serde_json5::from_str::(&body) { return Err(Box::new(Error::InvalidJson(err))); }