-
Notifications
You must be signed in to change notification settings - Fork 152
Description
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:
- accepting 0 value like in
Option<Vec>
makes no sense. It shouldn't behave that way. - 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.