Repository: Orange-OpenSource/hurl
File: packages/hurlfmt/src/curl/commands.rs (Rust)

Code before:
            }

            let mut cookies = Vec::new();
            let mut errors = Vec::new();

            for (i, result) in Cookie::split_parse(value).enumerate() {
                match result {
                    Ok(c) => cookies.push(c),
                    Err(e) => errors.push((i, e)),
                }
            }

            if errors.is_empty() {
                let cookies_str = cookies
                    .into_iter()
                    .map(|c| format!("{}={}", c.name(), c.value()))
                    .collect::<Vec<_>>()
                    .join("; ");
                Ok(cookies_str)
            } else {
                match errors.as_slice() {
                    [(_, e)] => Err(e.to_string()),
                    _ => {
                        let errors_str = errors
                            .into_iter()
                            .map(|(i, e)| format!("index {}: {}", i + 1, e))
                            .collect::<Vec<_>>()
                            .join(", ");
                        Err(format!(
                            "multiple invalid cookies provided: [{}]",
                            errors_str
                        ))
                    }
                }
            }
        })
        .action(ArgAction::Append)
        .num_args(1)
}

pub fn data() -> clap::Arg {
    clap::Arg::new("data")
        .long("data")
        .short('d')
        .value_name("data")
        .num_args(1)
}

pub fn headers() -> clap::Arg {
    clap::Arg::new("headers")
        .long("header")

Review comment (suggestion):
Let me know if you'd prefer to show custom error strings (owned by `hurl`) instead of showing the error strings from the underlying crate.

Code after:

            let (valid_cookies, invalid_cookies): (Vec<_>, Vec<_>) = value
                .split(';')
                .map(str::trim)
                .filter(|c| !c.is_empty())
                .partition(|c| c.contains('='));

            if invalid_cookies.is_empty() {
                Ok(valid_cookies.join("; "))
            } else {
                match invalid_cookies.as_slice() {
                    [_] => Err("invalid cookie pair provided".to_string()),
                    _ => Err(format!(
                        "invalid cookie pairs provided: [{}]",
                        invalid_cookies.join(", ")
                    )),
                }
            }
        })
        .action(ArgAction::Append)
        .num_args(1)
}

pub fn data() -> clap::Arg {
    clap::Arg::new("data")
        .long("data")
        .short('d')
        .value_name("data")
        .num_args(1)
}

pub fn headers() -> clap::Arg {
    clap::Arg::new("headers")
        .long("header")
        .short('H')
        .value_name("NAME:VALUE")
        .value_parser(|value: &str| {
            // We add a basic format check on headers, accepting either "NAME: VALUE" or "NAME;" for an empty header.
            // See curl manual <https://curl.se/docs/manpage.html#-H>
            // > If you send the custom header with no-value then its header must be terminated with a semicolon,
            // > such as -H "X-Custom-Header;" to send "X-Custom-Header:".
            if value.contains(":") || value.ends_with(";") {
                Ok(String::from(value))
            } else {
                Err("headers must be formatted as '<NAME:VALUE>' or '<NAME>;'")
            }
        })
        .action(ArgAction::Append)
        .num_args(1)
}