Skip to content

number_of_values with and without Option is weird/bad #364

@cecton

Description

@cecton

Before you read: if someone has a real practical use case where they need this to be consistent somehow, it's best to re-open this ticket and explain the use case (or open a new one but link this one).

There are 2 cases here that I find counter intuitive. The first one is when you use number_of_values with a Vec: it allows the user to not provide the parameter at all despite the field is not an Option. The second one is when you use number_of_values with an Option<Vec>: it allows the parameter to be passed with no value at all.

According to the doc of clap, number_of_values:

Specifies how many values are required to satisfy this argument. For example, if you had a -f argument where you wanted exactly 3 'files' you would set .number_of_values(3), and this argument wouldn't be satisfied unless the user provided 3 and only 3 values.

Therefore:

  1. accepting 0 value like in Option<Vec> makes no sense. It shouldn't behave that way.
  2. it doesn't tell if the argument is required or not but in structopt, fields are required unless they are Option (it's the opposite in clap)

Related to #349

Case 1

Code sample

#[derive(StructOpt, Debug)]
struct Cli {
    #[structopt(long, number_of_values = 2)]
    values: Vec<String>,
}

Expected behavior

values is required and is valid ONLY if 2 values are provided. Example:

command --values 1 2 

Or:

command --values 1 --values 2

Actual behavior

values is optional and is valid IF 2 values are provided OR the parameter is not used at all. Example:

command 

Or:

command --values 1 2

Or:

command --values 1 --values 2

Case 2

Code sample

#[derive(StructOpt, Debug)]
struct Cli {
    #[structopt(long, number_of_values = 2)]
    values: Option<Vec<String>>,
}

Expected behavior

values is optional and is valid IF 2 values are provided OR the parameter is not used at all. Example:

command

Or:

command --values 1 2

Or:

command --values 1 --values 2

Actual behavior

values is optional and is valid IF 2 values are provided OR 0 values are provided OR the parameter is not used at all. Example:

command 

Or:

command --values

Or:

command --values 1 2

Or:

command --values 1 --values 2

Analyze

I checked both cases with cargo-expand to see what code is generated. And this is what I found:

Case 1

            let app = app.arg(
                ::structopt::clap::Arg::with_name("values")
                    .takes_value(true)  // definitely correct, we want a value
                    .multiple(true) // we never asked for that but why not
                    .validator(|s| {
                        ::std::str::FromStr::from_str(s.as_str())
                            .map(|_: String| ())
                            .map_err(|e| e.to_string())
                    })
                    .long("values")
                    .number_of_values(2), // correct
                    // missing .required(true) despite the field is not an Option
            );

Case 2

            let app = app.arg(
                ::structopt::clap::Arg::with_name("values")
                    .takes_value(true) // correct
                    .multiple(true) // why not
                    .min_values(0) // incorrect! the field is not required but it needs to have 2 values, not 0
                    .validator(|s| {
                        ::std::str::FromStr::from_str(s.as_str())
                            .map(|_: String| ())
                            .map_err(|e| e.to_string())
                    })
                    .long("values")
                    .number_of_values(2), // correct
            );

Conclusion

I believe it would be best to change these behaviors to make it more intuitive but this will break everybody's code so I suggest to upgrade the MAJOR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions