Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No argument validation on globals #1546

Open
gabbifish opened this issue Sep 12, 2019 · 19 comments
Open

No argument validation on globals #1546

gabbifish opened this issue Sep 12, 2019 · 19 comments
Labels
A-parsing Area: Parser's logic and needs it changed somehow. C-enhancement Category: Raise on the bar on expectations S-waiting-on-design Status: Waiting on user-facing design to be resolved before implementing

Comments

@gabbifish
Copy link

gabbifish commented Sep 12, 2019

Rust Version

rustc 1.36.0 (a53f9df32 2019-07-03)

Affected Version of clap

2.33.0

Bug or Feature Request Summary

I was hoping that I could make an argument required, and also use the global() function to ensure that a user of my CLI could specify my required option wherever they please.

Expected Behavior Summary

An Arg with both .global() and .required_unless() would be usable.

Actual Behavior Summary

I see

thread 'main' panicked at 'Global arguments cannot be required.

	'binding' is marked as global and required', /home/gabbi/.cargo/registry/src/github.com-1ecc6299db9ec823/clap-2.33.0/src/app/parser.rs:204:9

Steps to Reproduce the issue

Create a clap App with the arg below, and attempt to run it.

Sample Code or Link to Sample Code

                .arg(
                    Arg::with_name("binding")
                    .help("The binding of the namespace this action applies to")
                    .short("b")
                    .long("binding")
                    .value_name("BINDING NAME")
                    .takes_value(true)
                    .global(true)
                    .conflicts_with("namespace-id")
                )
@jamestwebber
Copy link

I'll +1 this issue, I would like this to work as well. My tool has a bunch of universal required arguments (e.g. input directory) and then different tasks have some specific options. I was hoping to have usage look like

cmd --input [dir] --cmd-specific-option [option]
cmd subcmd --input [dir] --subcmd-specific-option [option]

Where --input is always required but can be specified after subcmd for readability (I find that dropping a subcommand in the middle of the line is hard to use/read).

Maybe there's a better way to set this up?

@CreepySkeleton CreepySkeleton added C: args E-help-wanted Call for participation: Help is requested to fix this issue. C-enhancement Category: Raise on the bar on expectations labels Feb 1, 2020
@pksunkara pksunkara added this to the 3.0 milestone Apr 23, 2020
@pksunkara pksunkara modified the milestones: 3.0, 3.1 Feb 22, 2021
@erayerdin
Copy link

erayerdin commented Jun 2, 2021

I don't know why this is the case, but a workaround now would be defining a .default_value("whatever") and do whatever when you get whatever as value.

This is one way, however one should keep in mind that this default value also limits what can be done with value. In my case, I will use an arg as Telegram token value, which is a long string, so setting default to "INVALID" makes sense in my case because I'm fairly sure there won't be any bot token with value "INVALID". On the other hand, if I used that arg as a file path, that would eliminate a directory or file named "INVALID".

Wish this will be implemented soon.

@pksunkara pksunkara removed the W: 3.x label Jun 3, 2021
@pksunkara
Copy link
Member

I would welcome people to design how this should work and implement it. Unfortunately, as it is now, we have no design to implement this because the global arg gets propagated to all subcommands and we haven't yet decided on how to resolve the required situation when that happens.

@pksunkara pksunkara removed this from the 3.1 milestone Jun 3, 2021
@pksunkara pksunkara added S-waiting-on-decision Status: Waiting on a go/no-go before implementing and removed E-help-wanted Call for participation: Help is requested to fix this issue. labels Jun 3, 2021
@jplatte
Copy link
Contributor

jplatte commented Nov 1, 2021

Until this is supported, maybe the derive macro could raise an error when #[clap(global = true)] is used on a required setting?

@epage epage added A-parsing Area: Parser's logic and needs it changed somehow. S-waiting-on-design Status: Waiting on user-facing design to be resolved before implementing and removed C: args S-waiting-on-decision Status: Waiting on a go/no-go before implementing labels Dec 8, 2021
@erayerdin
Copy link

I have come to say that I have run across this use-case several times now. It's been a long time (since Sep 2019) and this should be considered.

@epage
Copy link
Member

epage commented Feb 18, 2022

It's been a long time (since Sep 2019) and this should be considered.

I can understand the desire for this but it unfortunately isn't a priority. Before, the focus was on finishing the committed items for clap 3 by maintainers who had little time. We are now have some more liberal sponsorship (my employer) but I've completed the sponsored objective (clap3) and have to balance clap work against other sponsored objectives (cargo-edit atm). We have around 160 open issues which all exist because someone cares about them and we have to prioritize among them. Our current area of focus is in re-shaping clap to be more open ended with faster compile times and smaller binary sizes.

If you'd like to work on this, I would be willing to help mentor you on it.

@wildwestrom
Copy link

I'd like to start working on this. I'm just not sure where in the codebase I should be looking first.
I just got started with a debugger to see what's going on.
@pksunkara @epage

@epage
Copy link
Member

epage commented Jul 29, 2022

The relevant calls are roughly
_do_parse

Naively swapping the calls around would probably affect the way globals and defaults interact. Whether we are willing to change that is up to the details of the situation. Unsure what other concerns might lurk in this.

Note that master is now for v4. I would encourage doing development on a major feature there. If you want it earlier than v4's release (trying to keep it narrowly scoped to not extend more than a month or two) then once its merged, you can cherry pick it into the v3-master branch.

@wildwestrom
Copy link

wildwestrom commented Jul 30, 2022

OK, I've bitten off more than I can chew. I'm gonna need more help on this if anything. It's hard for me to track where things are going even stepping through with a debugger.

Here's a minimal example:

use clap::{Arg, Command};
fn main() {
    let subcmd1 = Command::new("sub1");
    let subcmd2 = Command::new("sub2")
        .arg(clap::Arg::new("optional_arg").help("Optional argument for subcommand"));

    let cli = Command::new("clap-fix")
        .arg(Arg::new("string_arg").required(true).global(true))
        .arg(Arg::new("num_arg").global(true).short('n').long("num-arg"))
        .subcommand(subcmd1)
        .subcommand(subcmd2);

    // This works but it's incorrect.
    let _ = cli.get_matches_from(["clap-fix", "string_arg_before", "sub1", "string_arg_after"]);
    // If this works then we're good.
    // let _ = cli.get_matches_from(["clap-fix", "sub1", "string_arg_after"]);
}

I also saw you guys were on opencollective. Can I open a bounty on this issue?

@epage
Copy link
Member

epage commented Aug 1, 2022

I also saw you guys were on opencollective. Can I open a bounty on this issue?

Probably? Previous maintainers were the ones mostly dealing with that; I have little experience. I've not seen people take up bounties too much for issues.

@dvc94ch
Copy link

dvc94ch commented Dec 19, 2022

@epage was this fixed recently, seems to work with 0.4.29?

@dvc94ch
Copy link

dvc94ch commented Dec 19, 2022

ah sorry, the issue persists, just the error message is wrong now. should I open a separate issue?

app-store-connect device list --api-key ~/.analog/developer.key.json
error: The following required arguments were not provided:
  --api-key <API_KEY>

Usage: app-store-connect --api-key <API_KEY> device --api-key <API_KEY> <COMMAND>

For more information try '--help'

@sondrelg
Copy link

sondrelg commented Feb 22, 2023

Is it possible to work around this by manually producing the right error when a global argument isn't specified?

I tried handling the None case of my optional global argument by calling:

Foo::command()
  .error(
    MissingRequiredArgument, 
    "the following required arguments were not provided:\n  \x1b[32m--application <APP>\x1b[0m",
  )
  .exit()

And that seems to produce the right error:

error: the following required arguments were not provided:
  --application <APP>

USAGE:
    ok secret --application <APP> <COMMAND>

For more information, try '--help'.

EDIT: The original error message had an error

@epage
Copy link
Member

epage commented Feb 23, 2023

If it isn't important to provide the custom usage, I'd just drop that and have Command::error generate it for you.

In theory, we've opened up all of the APIs so you can generate the error like clap:

  • Error::new with ErrorKind::MissingRequiredArgument
  • Error::with_cmd
  • Error::insert with ContextKind::InvalidArg, ContextValue::Strings(required)
  • Error::insert with ContextKind::Usage, ContextValue::StyledStr(usage)

See https://docs.rs/clap/latest/src/clap/error/mod.rs.html#487

The RichFormatter will then generate the appropriate message for you.

@hwittenborn
Copy link

Sorry if I'm missing something, but is there any technical reason this can't be done? Like if the check for when an Arg is both global and required was simply removed, would there be something that would start breaking?

@epage
Copy link
Member

epage commented Jun 1, 2023

Sorry if I'm missing something, but is there any technical reason this can't be done? Like if the check for when an Arg is both global and required was simply removed, would there be something that would start breaking?

Currently, globals are propagated after all validation has occurred which would cause missing-required errors where they don't apply.

For details, see my earlier comment

@kingzcheung
Copy link

kingzcheung commented Nov 29, 2023

This is very strange and does not conform to the normal usage.

cmd subcmd --global-arg # not work
cmd --global-arg subcmd # work

@Kibouo
Copy link

Kibouo commented Nov 29, 2023 via email

@Emilgardis
Copy link

Emilgardis commented Jan 25, 2024

While this isn't solved, I've managed to implement a rather (little) hacky way to still get a nice error message when using global and default_value

playground

use clap::{CommandFactory as _, FromArgMatches as _, Parser};

pub const DEFAULT_NOT_SET: &str = "\u{0}\u{beef}";

#[derive(Debug, Parser)]
pub struct Cli {
    #[arg(global = true, long, default_value = DEFAULT_NOT_SET)]
    pub required: String,
    #[command(subcommand)]
    pub subcommand: Option<Subcommand>,
}

#[derive(Debug, clap::Subcommand)]
pub enum Subcommand {
    Command {
        
    },
}

pub fn main() -> Result<(),Box<dyn std::error::Error>> {
    let _cli: Cli = {
        let mut cmd = Cli::command();
        let matches = Cli::command().get_matches_from(["playground", "command"]);
        let mut strings = vec![];

        let succ = std::iter::successors(Some((cmd.clone(), &matches)), |(cmd, matches)| {
            let (sub, matches) = matches.subcommand()?;
            Some((cmd.find_subcommand(sub)?.clone(), matches))
        });
        for (cmd, matches) in succ {
            let ids = cmd
                .get_arguments()
                .filter(|&arg| arg.is_global_set())
                .map(|arg| {
                    (
                        arg.clone()
                            // this is a hacky way to display, without the override here the stylized call will panic
                            .num_args(arg.get_num_args().unwrap_or_default())
                            .to_string(),
                        arg.get_id(),
                    )
                });

            for (display, id) in ids {
                if !matches!(
                    matches.value_source(id.as_str()),
                    Some(clap::parser::ValueSource::DefaultValue)
                ) {
                    continue;
                }
                let mut raw = matches.get_raw(id.as_str()).unwrap();
                if raw.len() != 1 {
                    continue;
                }
                let raw = raw.next().unwrap();
                if raw.to_string_lossy() != DEFAULT_NOT_SET {
                    continue;
                }
                strings.push(display);
            }
        }

        if strings.is_empty() {
            Cli::from_arg_matches(&matches)?
        } else {
            let mut err = clap::Error::new(clap::error::ErrorKind::MissingRequiredArgument);
            err.insert(
                clap::error::ContextKind::InvalidArg,
                clap::error::ContextValue::Strings(strings),
            );
            err.insert(
               clap::error::ContextKind::Usage,
               clap::error::ContextValue::StyledStr(cmd.render_usage()),
            );
            err.with_cmd(&cmd).exit();
        }
    };
    Ok(())
}

one thing missing here is usage, since there is no good way to render it the same way clap does internally.

@epage epage changed the title Why can't global args be required? No argument validation on globals Feb 1, 2024
Magicloud added a commit to Magicloud/kubeseal-helper that referenced this issue Mar 27, 2024
Now common args like `--secret-name` can be specified after the
sub commands.

Common args like `--secret-name` are needed by every sub commands.
To avoid declaring them in every sub command, they are specified
outside sub commands. I think `ksher cmd --secret-name XX` means
better than `ksher --secret-name XX cmd`, hence they are set as
`global`.

But per clap-rs/clap#1546, an arg cannot
be global and required at the same time. So `--secret-name` is set
as `Option` and checked manually to throw a clap style error message.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-parsing Area: Parser's logic and needs it changed somehow. C-enhancement Category: Raise on the bar on expectations S-waiting-on-design Status: Waiting on user-facing design to be resolved before implementing
Projects
None yet
Development

No branches or pull requests