use std::io::Read;
use super::http::{headers::Headers, Method};
use super::logs;
use crate::error;
use crate::error::Error;
use clap::{crate_authors, crate_name, crate_version, Arg, ArgMatches};
pub mod output;
use output::Output;
pub struct Input {
pub url: String,
pub method: Method,
pub headers: Headers,
pub body: Option<String>,
pub allow_proxy: bool,
pub timeout: u64,
}
pub fn parse_args(args: Vec<String>) -> Result<(Input, Output), Error> {
let matches = use_clap(&args);
let input = parse_input(&matches)?;
let output = parse_output(&matches);
if matches.is_present("info") {
enable_logging()?;
}
Ok((input, output))
}
fn parse_input(matches: &ArgMatches) -> Result<Input, Error> {
let mut headers = headers(matches)?;
let body = collect_body(
matches.value_of("body"),
matches.value_of("json"),
&mut headers,
)?;
let timeout = match matches.value_of("timeout") {
Some(timeout) => timeout.parse::<u64>().unwrap(), None => 10,
};
Ok(Input {
url: matches.value_of("url").unwrap().to_string(),
method: get_method(matches.value_of("method").unwrap()),
headers,
body,
allow_proxy: !(matches.is_present("no-proxy")),
timeout,
})
}
fn parse_output(matches: &ArgMatches) -> Output {
Output {
verbose: matches.is_present("verbose"),
no_body: matches.is_present("no-body"),
}
}
fn headers(matches: &ArgMatches) -> Result<Headers, Error> {
let mut headers = single_headers(matches.values_of("header"));
if let Some(h) = json_headers(matches.value_of("headers"))? {
headers.append(h)
};
Ok(headers)
}
fn single_headers(headers_option: Option<clap::Values>) -> Headers {
match headers_option {
Some(values) => {
let mut headers = Headers::new();
for val in values {
let splits: Vec<&str> = val.splitn(2, ':').collect();
headers.add(splits[0], splits[1]);
}
headers
}
None => Headers::new(),
}
}
fn json_headers(headers_json: Option<&str>) -> Result<Option<Headers>, Error> {
match headers_json {
Some(value) => {
let json_string = match value.ends_with(".json") {
true => read_file(value)?,
false => value.to_string(),
};
let map: std::collections::HashMap<String, String> =
serde_json::from_str(&json_string)?;
Ok(Some(Headers::from(map)))
}
None => Ok(None),
}
}
fn read_file(path: &str) -> Result<String, Error> {
let mut file = std::fs::File::open(path)?;
let mut buf = String::new();
file.read_to_string(&mut buf)?;
Ok(buf)
}
fn collect_body(
body_option: Option<&str>,
json_option: Option<&str>,
headers: &mut Headers,
) -> Result<Option<String>, Error> {
let mut body: Option<String> = None;
if let Some(body_str) = body_option {
body = Some(body_str.to_string());
}
if let Some(body_str) = json_option {
match serde_json::from_str::<serde_json::Value>(body_str) {
Ok(_) => {
body = Some(body_str.to_string());
headers.add("Content-Type", "application/json");
}
Err(why) => error!(&why.to_string()),
};
}
Ok(body)
}
fn enable_logging() -> Result<(), log::SetLoggerError> {
static LOGGER: logs::Logger = logs::Logger;
log::set_logger(&LOGGER).map(|()| log::set_max_level(log::LevelFilter::Info))
}
fn get_method(method: &str) -> Method {
match method.to_lowercase().as_str() {
"get" => Method::Get,
"post" => Method::Post,
"put" => Method::Put,
"delete" => Method::Delete,
"patch" => Method::Patch,
"connect" => Method::Connect,
"options" => Method::Options,
"trace" => Method::Trace,
"head" => Method::Head,
_ => Method::Get,
}
}
fn use_clap(args: &[String]) -> ArgMatches {
return clap::app_from_crate!(crate_name!())
.version(crate_version!())
.author(crate_authors!("\n"))
.arg(
Arg::new("url")
.help("The URL for the request")
.required(true)
.takes_value(true),
)
.arg(
Arg::new("verbose")
.long("verbose")
.short('v')
.help("Full request and response output in JSON"),
)
.arg(Arg::new("info").long("info").help("Show info logging"))
.arg(
Arg::new("method")
.takes_value(true)
.short('m')
.long("method")
.default_value("get")
.possible_values(&[
"get", "GET", "post", "POST", "put", "PUT", "trace", "TRACE", "patch", "PATCH",
"delete", "DELETE", "head", "HEAD", "options", "OPTIONS", "connect", "CONNECT",
]),
)
.arg(
Arg::new("header")
.help("Header for request")
.takes_value(true)
.short('h')
.long("header")
.multiple_occurrences(true),
)
.arg(
Arg::new("headers")
.help("Headers as a JSON string or JSON file")
.takes_value(true)
.long("headers"),
)
.arg(
Arg::new("body")
.help("Body for request")
.long("body")
.conflicts_with("json")
.takes_value(true),
)
.arg(
Arg::new("json")
.help("Send body with Content-Type:application/json")
.long("json")
.conflicts_with("body")
.takes_value(true),
)
.arg(
Arg::new("no-body")
.help("Don't print response body")
.long("no-body"),
)
.arg(
Arg::new("no-proxy")
.help("Do not proxy request")
.long("no-proxy"),
)
.arg(
Arg::new("timeout")
.help("The read timeout in seconds for the request")
.long("timeout")
.takes_value(true),
)
.get_matches_from(args);
}
#[test]
fn test_collect_body() {
let mut headers = Headers::new();
let body_opt = Some("form1:value2");
let json_opt = None;
let body = collect_body(body_opt, json_opt, &mut headers).unwrap();
assert!(body.is_some());
assert_eq!(body.unwrap(), "form1:value2");
}
#[test]
fn test_collect_json_body() {
let mut headers = Headers::new();
let body_opt = None;
let json_opt = Some(r#"{"key":"value"}"#);
let body = collect_body(body_opt, json_opt, &mut headers).unwrap();
assert!(body.is_some());
assert_eq!(body.unwrap(), r#"{"key":"value"}"#);
assert!(headers.get("Content-Type").is_some());
}