Repository: Orange-OpenSource/hurl
File: packages/hurl/src/cli/options/matches.rs (Rust)

Code before:
pub fn repeat(arg_matches: &ArgMatches) -> Option<Count> {
    match get::<i32>(arg_matches, "repeat") {
        Some(-1) => Some(Count::Infinite),
        Some(n) => Some(Count::Finite(n as usize)),
        None => None,
    }
}

pub fn resolves(arg_matches: &ArgMatches) -> Vec<String> {
    get_strings(arg_matches, "resolve").unwrap_or_default()
}

pub fn retry(arg_matches: &ArgMatches) -> Option<Count> {
    match get::<i32>(arg_matches, "retry") {
        Some(-1) => Some(Count::Infinite),
        Some(r) => Some(Count::Finite(r as usize)),
        None => None,
    }
}

pub fn retry_interval(arg_matches: &ArgMatches) -> Result<Duration, CliOptionsError> {
    let s = get::<String>(arg_matches, "retry_interval").unwrap_or("1000".to_string());
    get_duration(&s, DurationUnit::MilliSecond)
}

/// Helper function to read and parse a file containing key-value pairs
fn read_key_value_file<F>(
    filenames: &[String],
    file_type: &str,
    mut process_line: F,
) -> Result<(), CliOptionsError>
where
    F: FnMut(&str) -> Result<(), CliOptionsError>,
{
    for filename in filenames {
        let path = Path::new(filename);
        if !path.exists() {
            return Err(CliOptionsError::Error(format!(
                "{} file {} does not exist",
                file_type,
                path.display()
            )));
        }

        let file = File::open(path).unwrap();
        let reader = BufReader::new(file);
        for (index, line) in reader.lines().enumerate() {
            let line = match line {
                Ok(s) => s,
                Err(_) => {
                    return Err(CliOptionsError::Error(format!(

Review comment (security):
I like that we try to reuse some sole between `--variables-file` and `--secrets-file` but we should move this logic in its own module. I've update `master` branch with a `VariablesFile` structure that is used is `variables()`:

https://github.com/Orange-OpenSource/hurl/blob/4d0e594fe97bbd6f01a7b8aceeb22e3891740d5b/packages/hurl/src/cli/options/matches.rs#L497-L507

You can rebase your branch and use this struct to implement `secrets()` function.

Note: contraty to "normal" variable, secrets shoudl use `TypeKind::String` instead of `TypeKind::Inferred`



Code after:
pub fn repeat(arg_matches: &ArgMatches) -> Option<Count> {
    match get::<i32>(arg_matches, "repeat") {
        Some(-1) => Some(Count::Infinite),
        Some(n) => Some(Count::Finite(n as usize)),
        None => None,
    }
}

pub fn resolves(arg_matches: &ArgMatches) -> Vec<String> {
    get_strings(arg_matches, "resolve").unwrap_or_default()
}

pub fn retry(arg_matches: &ArgMatches) -> Option<Count> {
    match get::<i32>(arg_matches, "retry") {
        Some(-1) => Some(Count::Infinite),
        Some(r) => Some(Count::Finite(r as usize)),
        None => None,
    }
}

pub fn retry_interval(arg_matches: &ArgMatches) -> Result<Duration, CliOptionsError> {
    let s = get::<String>(arg_matches, "retry_interval").unwrap_or("1000".to_string());
    get_duration(&s, DurationUnit::MilliSecond)
}

pub fn secret(matches: &ArgMatches) -> Result<HashMap<String, String>, CliOptionsError> {
    let mut secrets = HashMap::new();

    if let Some(filenames) = get_strings(matches, "secrets_file") {
        for f in &filenames {
            let filename = Path::new(f);
            let vars = VariablesFile::open(filename, TypeKind::String)?;
            for var in vars {
                let (name, value) = var?;
                // We check that there is no existing secrets
                if secrets.contains_key(&name) {
                    return Err(CliOptionsError::Error(format!(
                        "secret '{}' can't be reassigned",
                        &name
                    )));
                }
                // Secrets can only be string.
                if let Value::String(value) = value {
                    secrets.insert(name.to_string(), value);
                }
            }
        }
    }

    if let Some(secret) = get_strings(matches, "secret") {
        for s in secret {