pub fn extract_show_headers<I, T>(args: I) -> (bool, Vec<OsString>)
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
let mut show_headers = false;
let mut filtered = Vec::new();
for arg in args.into_iter() {
let s = arg.clone().into();
let s_str = s.to_string_lossy();
if s_str == "--show-headers" {
show_headers = true;
continue;
}
filtered.push(s);
}
(show_headers, filtered)
}
use std::ffi::OsString;
const GLOBAL_FLAG_TABLE: &[(&str, Option<char>, bool)] = &[
("--timeout", None, true),
("--no-decompress", None, false),
("--fail", None, false),
("--verbose-errors", None, false),
("--show-headers", None, false),
("-h", Some('h'), false),
];
fn is_short_global_flag(c: char) -> bool {
GLOBAL_FLAG_TABLE.iter().any(|&(_long, short, _)| short == Some(c))
}
fn short_flag_takes_value(c: char) -> bool {
GLOBAL_FLAG_TABLE.iter().any(|&(_long, short, takes_val)| short == Some(c) && takes_val)
}
fn is_global_flag(arg: &str) -> bool {
for &(long, short, _takes_val) in GLOBAL_FLAG_TABLE {
if arg == long || (long.starts_with("--") && arg.starts_with(&format!("{}=", &long[2..]))) || (long.starts_with("--") && arg.starts_with(&format!("{}=", long))) {
return true;
}
if let Some(s) = short {
if arg == format!("-{}", s) {
return true;
}
}
}
if arg.starts_with('-') && !arg.starts_with("--") && arg.len() > 2 {
let chars: Vec<char> = arg[1..].chars().collect();
for &c in &chars {
if !is_short_global_flag(c) || short_flag_takes_value(c) {
return false;
}
}
return true;
}
false
}
pub fn reorder_global_options<I, T>(args: I) -> Vec<OsString>
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
let mut before_cmd = Vec::new();
let mut args_vec: Vec<OsString> = args.into_iter().map(|a| a.into()).collect();
if !args_vec.is_empty() {
before_cmd.push(args_vec[0].clone());
args_vec.remove(0);
}
let mut globals = Vec::new();
let mut subcmd_and_args = Vec::new();
let mut i = 0;
let mut found_subcmd = false;
while i < args_vec.len() {
let arg_str = args_vec[i].to_string_lossy();
if !found_subcmd && !arg_str.starts_with('-') {
let mut subcmd = arg_str.to_string();
if ["custom", "request", "raw", "http"].contains(&subcmd.as_str()) {
subcmd = "custom".to_string();
}
subcmd_and_args.push(OsString::from(subcmd));
found_subcmd = true;
} else if arg_str.starts_with('-') && !arg_str.starts_with("--") && arg_str.len() > 2 {
let chars: Vec<char> = arg_str[1..].chars().collect();
let mut all_global = true;
for &c in &chars {
if !is_short_global_flag(c) || short_flag_takes_value(c) {
all_global = false;
break;
}
}
if all_global {
for &c in &chars {
globals.push(OsString::from(format!("-{}", c)));
}
} else {
if found_subcmd {
subcmd_and_args.push(args_vec[i].clone());
} else {
subcmd_and_args.push(args_vec[i].clone());
}
}
} else if is_global_flag(&arg_str) {
globals.push(args_vec[i].clone());
if (arg_str == "--timeout" || arg_str.starts_with("--timeout=")) && i + 1 < args_vec.len() && !args_vec[i + 1].to_string_lossy().starts_with('-') {
globals.push(args_vec[i + 1].clone());
i += 1;
}
} else {
if found_subcmd {
subcmd_and_args.push(args_vec[i].clone());
} else {
subcmd_and_args.push(args_vec[i].clone());
}
}
i += 1;
}
before_cmd.extend(globals);
before_cmd.extend(subcmd_and_args);
before_cmd
}
pub fn validate_method(s: &str) -> Result<String, String> {
let allowed = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "TRACE", "CONNECT"];
let upper = s.to_uppercase();
if allowed.contains(&upper.as_str()) {
Ok(upper)
} else {
Err(format!(
"Invalid HTTP method: '{}'. Allowed methods: {}",
s,
allowed.join(", ")
))
}
}
pub fn validate_url(s: &str) -> Result<String, String> {
if s.starts_with("http://") || s.starts_with("https://") {
Ok(s.to_string())
} else {
Err("URL must start with http:// or https://".to_string())
}
}
pub fn parse_range(s: &str) -> Result<(u64, Option<u64>), String> {
let parts: Vec<&str> = s.split('-').collect();
if parts.len() != 2 {
return Err("Range must be in start-end format (end optional)".to_string());
}
let start = parts[0].trim().parse::<u64>().map_err(|_| "Invalid start of range".to_string())?;
let end = if parts[1].trim().is_empty() {
None
} else {
Some(parts[1].trim().parse::<u64>().map_err(|_| "Invalid end of range".to_string())?)
};
Ok((start, end))
}
pub fn parse_header(s: &str) -> Result<(String, String), String> {
let parts: Vec<&str> = s.splitn(2, ':').collect();
if parts.len() != 2 {
return Err("Header must be in key:value format".to_string());
}
Ok((parts[0].trim().to_string(), parts[1].trim().to_string()))
}
pub fn parse_query(s: &str) -> Result<(String, String), String> {
let parts: Vec<&str> = s.splitn(2, '=').collect();
if parts.len() != 2 {
return Err("Query must be in key=value format".to_string());
}
Ok((parts[0].trim().to_string(), parts[1].trim().to_string()))
}