use std::collections::HashMap;
use std::path::{Path, PathBuf};
use hurl::pretty::PrettyMode;
use super::CliOptions;
pub struct RunContext {
env_vars: HashMap<String, String>,
hurl_env_vars: HashMap<String, String>,
stdin_term: bool,
stdout_term: bool,
stderr_term: bool,
config_file: Option<PathBuf>,
}
const HURL_PREFIX: &str = "HURL_";
pub const HURL_COLOR: &str = "HURL_COLOR";
pub const HURL_COMPRESSED: &str = "HURL_COMPRESSED";
pub const HURL_CONNECT_TIMEOUT: &str = "HURL_CONNECT_TIMEOUT";
pub const HURL_CONTINUE_ON_ERROR: &str = "HURL_CONTINUE_ON_ERROR";
pub const HURL_DELAY: &str = "HURL_DELAY";
pub const HURL_ERROR_FORMAT: &str = "HURL_ERROR_FORMAT";
pub const HURL_FOLLOW_LOCATION: &str = "HURL_LOCATION";
pub const HURL_FOLLOW_LOCATION_TRUSTED: &str = "HURL_LOCATION_TRUSTED";
pub const HURL_INSECURE: &str = "HURL_INSECURE";
pub const HURL_IPV4: &str = "HURL_IPV4";
pub const HURL_IPV6: &str = "HURL_IPV6";
pub const HURL_JOBS: &str = "HURL_JOBS";
pub const HURL_HEADER: &str = "HURL_HEADER";
pub const HURL_HTTP10: &str = "HURL_HTTP10";
pub const HURL_HTTP11: &str = "HURL_HTTP11";
pub const HURL_HTTP2: &str = "HURL_HTTP2";
pub const HURL_HTTP3: &str = "HURL_HTTP3";
pub const HURL_LIMIT_RATE: &str = "HURL_LIMIT_RATE";
pub const HURL_MAX_FILESIZE: &str = "HURL_MAX_FILESIZE";
pub const HURL_MAX_REDIRS: &str = "HURL_MAX_REDIRS";
pub const HURL_MAX_TIME: &str = "HURL_MAX_TIME";
pub const HURL_NO_ASSERT: &str = "HURL_NO_ASSERT";
pub const HURL_NO_COLOR: &str = "HURL_NO_COLOR";
pub const HURL_NO_COOKIE_STORE: &str = "HURL_NO_COOKIE_STORE";
pub const HURL_NO_OUTPUT: &str = "HURL_NO_OUTPUT";
pub const HURL_NO_PRETTY: &str = "HURL_NO_PRETTY";
pub const HURL_PRETTY: &str = "HURL_PRETTY";
pub const HURL_RETRY: &str = "HURL_RETRY";
pub const HURL_RETRY_INTERVAL: &str = "HURL_RETRY_INTERVAL";
pub const HURL_SECRET_PREFIX: &str = "HURL_SECRET_";
pub const HURL_TEST: &str = "HURL_TEST";
pub const HURL_USER: &str = "HURL_USER";
pub const HURL_USER_AGENT: &str = "HURL_USER_AGENT";
pub const HURL_VARIABLE_PREFIX: &str = "HURL_VARIABLE_";
pub const HURL_VERBOSE: &str = "HURL_VERBOSE";
pub const HURL_VERBOSITY: &str = "HURL_VERBOSITY";
pub const HURL_VERY_VERBOSE: &str = "HURL_VERY_VERBOSE";
impl RunContext {
pub fn new(
env_vars: HashMap<String, String>,
stdin_term: bool,
stdout_term: bool,
stderr_term: bool,
) -> Self {
let config_file = get_config_file(&env_vars);
let hurl_env_vars = env_vars
.iter()
.filter(|(k, _v)| k.starts_with(HURL_PREFIX))
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<HashMap<_, _>>();
RunContext {
env_vars,
hurl_env_vars,
stdin_term,
stdout_term,
stderr_term,
config_file,
}
}
pub fn config_file_path(&self) -> Option<&Path> {
self.config_file.as_deref()
}
pub fn is_stdin_term(&self) -> bool {
self.stdin_term
}
pub fn is_stdout_term(&self) -> bool {
self.stdout_term
}
pub fn is_stderr_term(&self) -> bool {
self.stderr_term
}
pub fn compressed_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_COMPRESSED)
}
pub fn connect_timeout_env_var(&self) -> Option<&str> {
self.hurl_env_vars
.get(HURL_CONNECT_TIMEOUT)
.map(|v| v.as_str())
}
pub fn continue_on_error_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_CONTINUE_ON_ERROR)
}
pub fn delay_env_var(&self) -> Option<&str> {
self.hurl_env_vars.get(HURL_DELAY).map(|v| v.as_str())
}
pub fn error_format_env_var(&self) -> Option<&str> {
self.hurl_env_vars
.get(HURL_ERROR_FORMAT)
.map(|v| v.as_str())
}
pub fn header_env_var(&self) -> Option<&str> {
self.hurl_env_vars.get(HURL_HEADER).map(|v| v.as_str())
}
pub fn http10_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_HTTP10)
}
pub fn http11_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_HTTP11)
}
pub fn http2_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_HTTP2)
}
pub fn http3_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_HTTP3)
}
pub fn follow_location_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_FOLLOW_LOCATION)
}
pub fn follow_location_trusted_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_FOLLOW_LOCATION_TRUSTED)
}
pub fn insecure_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_INSECURE)
}
pub fn ipv4_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_IPV4)
}
pub fn ipv6_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_IPV6)
}
pub fn is_ci_env_var(&self) -> bool {
self.env_vars.contains_key("CI") || self.env_vars.contains_key("TF_BUILD")
}
pub fn jobs_env_var(&self) -> Option<&str> {
self.hurl_env_vars.get(HURL_JOBS).map(|v| v.as_str())
}
pub fn limit_rate_env_var(&self) -> Option<&str> {
self.hurl_env_vars.get(HURL_LIMIT_RATE).map(|v| v.as_str())
}
pub fn max_filesize_env_var(&self) -> Option<&str> {
self.hurl_env_vars
.get(HURL_MAX_FILESIZE)
.map(|v| v.as_str())
}
pub fn max_redirs_env_var(&self) -> Option<&str> {
self.hurl_env_vars.get(HURL_MAX_REDIRS).map(|v| v.as_str())
}
pub fn max_time_env_var(&self) -> Option<&str> {
self.hurl_env_vars.get(HURL_MAX_TIME).map(|v| v.as_str())
}
pub fn user_env_var(&self) -> Option<&str> {
self.hurl_env_vars.get(HURL_USER).map(|v| v.as_str())
}
pub fn user_agent_env_var(&self) -> Option<&str> {
self.hurl_env_vars.get(HURL_USER_AGENT).map(|v| v.as_str())
}
pub fn no_output_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_NO_OUTPUT)
}
pub fn no_assert_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_NO_ASSERT)
}
pub fn no_cookie_store_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_NO_COOKIE_STORE)
}
pub fn no_pretty_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_NO_PRETTY)
}
pub fn pretty_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_PRETTY)
}
pub fn retry_env_var(&self) -> Option<&str> {
self.hurl_env_vars.get(HURL_RETRY).map(|v| v.as_str())
}
pub fn retry_interval_env_var(&self) -> Option<&str> {
self.hurl_env_vars
.get(HURL_RETRY_INTERVAL)
.map(|v| v.as_str())
}
pub fn secret_env_vars(&self) -> HashMap<&str, &str> {
self.hurl_env_vars
.iter()
.filter_map(|(name, value)| {
name.strip_prefix(HURL_SECRET_PREFIX)
.filter(|n| !n.is_empty())
.map(|stripped| (stripped, value.as_str()))
})
.collect()
}
pub fn color_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_COLOR)
}
pub fn no_color_env_var(&self) -> Option<bool> {
if let Some(v) = self.env_vars.get("NO_COLOR") {
if !v.is_empty() { Some(true) } else { None }
} else {
self.get_env_var_bool(HURL_NO_COLOR)
}
}
pub fn test_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_TEST)
}
pub fn var_env_vars(&self) -> HashMap<&str, &str> {
self.hurl_env_vars
.iter()
.filter_map(|(name, value)| {
name.strip_prefix(HURL_VARIABLE_PREFIX)
.filter(|n| !n.is_empty())
.map(|stripped| (stripped, value.as_str()))
})
.collect()
}
pub fn verbose_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_VERBOSE)
}
pub fn verbosity_env_var(&self) -> Option<&str> {
self.hurl_env_vars.get(HURL_VERBOSITY).map(|v| v.as_str())
}
pub fn very_verbose_env_var(&self) -> Option<bool> {
self.get_env_var_bool(HURL_VERY_VERBOSE)
}
fn get_env_var_bool(&self, name: &'static str) -> Option<bool> {
self.hurl_env_vars
.get(name)
.map(|s| s.as_str())
.map(|v| v.to_ascii_lowercase())
.and_then(|v| match v.as_str() {
"1" | "true" => Some(true),
"0" | "false" => Some(false),
_ => None,
})
}
}
fn get_config_file(env_vars: &HashMap<String, String>) -> Option<PathBuf> {
get_config_dir(env_vars).map(|config_dir| config_dir.join("hurl").join("config"))
}
fn get_config_dir(env_vars: &HashMap<String, String>) -> Option<PathBuf> {
if let Some(config_dir) = env_vars.get("XDG_CONFIG_HOME") {
Some(Path::new(config_dir).to_path_buf())
} else {
env_vars
.get("HOME")
.map(|home_dir| Path::new(home_dir).join("config").to_path_buf())
}
}
pub fn init_options(context: &RunContext, default_options: CliOptions) -> CliOptions {
let mut options = default_options;
options.color_stdout = context.is_stdout_term();
options.color_stderr = context.is_stderr_term();
options.pretty = if context.is_stdout_term() {
PrettyMode::Automatic
} else {
PrettyMode::None
};
options
}
#[cfg(test)]
mod tests {
use crate::cli::options::context::RunContext;
use std::collections::HashMap;
#[test]
fn context_has_no_env_var_color() {
let stdin_term = true;
let stdout_term = true;
let stderr_term = true;
let env_vars = HashMap::from([("A".to_string(), "B".to_string())]);
let ctx = RunContext::new(env_vars, stdin_term, stdout_term, stderr_term);
assert!(ctx.color_env_var().is_none());
}
#[test]
fn context_has_color_env_var() {
let stdin_term = true;
let stdout_term = true;
let stderr_term = true;
let data = [
("HURL_COLOR", "0", Some(false)),
("HURL_COLOR", "1", Some(true)),
("HURL_COLOR", "true", Some(true)),
("HURL_COLOR", "TRUE", Some(true)),
("HURL_COLOR", "false", Some(false)),
("HURL_COLOR", "FALSE", Some(false)),
];
for (name, value, expected) in data {
let env_vars = HashMap::from([(name.to_string(), value.to_string())]);
let ctx = RunContext::new(env_vars, stdin_term, stdout_term, stderr_term);
assert_eq!(
ctx.color_env_var(),
expected,
"test env var {}={}",
name,
value
);
}
}
#[test]
fn context_has_no_color_env_var() {
let stdin_term = true;
let stdout_term = true;
let stderr_term = true;
let data = [
("NO_COLOR", "0", Some(true)),
("NO_COLOR", "1", Some(true)),
("NO_COLOR", "true", Some(true)),
("NO_COLOR", "TRUE", Some(true)),
("NO_COLOR", "false", Some(true)),
("NO_COLOR", "FALSE", Some(true)),
("HURL_NO_COLOR", "0", Some(false)),
("HURL_NO_COLOR", "1", Some(true)),
("HURL_NO_COLOR", "true", Some(true)),
("HURL_NO_COLOR", "TRUE", Some(true)),
("HURL_NO_COLOR", "false", Some(false)),
("HURL_NO_COLOR", "FALSE", Some(false)),
];
for (name, value, expected) in data {
let env_vars = HashMap::from([(name.to_string(), value.to_string())]);
let ctx = RunContext::new(env_vars, stdin_term, stdout_term, stderr_term);
assert_eq!(
ctx.no_color_env_var(),
expected,
"test env var {}={}",
name,
value
);
}
}
#[test]
fn empty_variables_secrets_from_env() {
let stdin_term = true;
let stdout_term = true;
let stderr_term = true;
let env_vars = HashMap::from([
("FOO".to_string(), "xxx".to_string()),
("BAR".to_string(), "yyy".to_string()),
("BAZ".to_string(), "yyy".to_string()),
]);
let ctx = RunContext::new(env_vars, stdin_term, stdout_term, stderr_term);
assert!(ctx.var_env_vars().is_empty());
assert!(ctx.secret_env_vars().is_empty());
}
#[test]
fn variables_from_env() {
let stdin_term = true;
let stdout_term = true;
let stderr_term = true;
let env_vars = HashMap::from([
("FOO".to_string(), "xxx".to_string()),
("BAR".to_string(), "yyy".to_string()),
("BAZ".to_string(), "yyy".to_string()),
("HURL_VARIABLE_foo".to_string(), "true".to_string()),
("HURL_VARIABLE_id".to_string(), "1234".to_string()),
("BAZ".to_string(), "yyy".to_string()),
("HURL_VARIABLE".to_string(), "1234".to_string()),
("HURL_VARIABLE_".to_string(), "abcd".to_string()),
("HURL_VARIABLE_FOO".to_string(), "def".to_string()),
("HURL_COLOR".to_string(), "1".to_string()),
("HURL_NO_COLOR".to_string(), "1".to_string()),
]);
let ctx = RunContext::new(env_vars, stdin_term, stdout_term, stderr_term);
assert_eq!(ctx.var_env_vars().len(), 3);
assert_eq!(ctx.var_env_vars()["foo"], "true");
assert_eq!(ctx.var_env_vars()["id"], "1234");
assert_eq!(ctx.var_env_vars()["FOO"], "def");
assert!(ctx.secret_env_vars().is_empty());
}
}