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

Keeping post parsing validation in clap #4987

Closed
2 tasks done
Easyoakland opened this issue Jun 24, 2023 · 3 comments
Closed
2 tasks done

Keeping post parsing validation in clap #4987

Easyoakland opened this issue Jun 24, 2023 · 3 comments
Labels
A-validators Area: ArgMatches validation logi C-enhancement Category: Raise on the bar on expectations

Comments

@Easyoakland
Copy link

Easyoakland commented Jun 24, 2023

Please complete the following tasks

Clap Version

clap = "4.3.8"

Describe your use case

TLDR: Post process validation function that runs with clap's own parsing to support arbitrary invariant validation.

I have a struct with complex multi-field properties and invariants that should be upheld when finished parsing. An example might looks like:

#[derive(Debug, Parser)]
struct Cli {
    a: usize,
    b: usize,
    c: usize,
}

I would like to be able to validate arbitrary invariants (ex a+b<100) on this struct after it has been fully validated and before returning from parse.

I see in the docs here that you can already perform arbitrary validation after the Parse() call. However, it seems ugly that you have to include this extra parsing logic in the actual program code instead of defining it on the struct itself.
Its also problematic when integrating the cli with klask. The program runs through to here and should return an error but currently will continue until the user's main and then finally report the invalid args.

Describe the solution you'd like

Some way of specifying a value parser fn(self)->Self that runs on the entire parsed struct before leaving clap's own validation logic. So from_arg_matches_mut, try_get_matches_from_mut and the like use this to validate after parsing the whole struct.
Whatever syntax is used for this it should also work for #[derive(Args)] so subcommands can also handle their own validated invariants.
For example the following syntax could be created so the following program never panics in the actual user's main. Instead clap should use the value_parser=Self::post_validate:

use clap::Parser;

const MAX_VALUE: usize = 100;

#[derive(Clone, Debug, Parser(value_parser=Self::post_validate))]
struct Cli {
    a: usize,
    b: usize,
    c: usize,
}

impl Cli {
    fn post_validate(self) -> Result<Self, String> {
        if self.a + self.b > MAX_VALUE {
            Err(format!(
                "a and b should not add up to more than {MAX_VALUE}"
            ))
        } else {
            Ok(self)
        }
    }
}

fn main() {
    let opt = Cli::parse();
    assert!(
        opt.a + opt.b <= MAX_VALUE,
        "Clap didn't validate all invariants"
    );
    dbg!(opt);
}

Alternatives, if applicable

  1. Its not preferred but acceptable if the function must be free and not an associated to the struct.
  2. Currently I can only think of accomplishing post parse validation by:
use clap::{CommandFactory, Parser};

const MAX_VALUE: usize = 100;

#[derive(Clone, Debug, Parser /* (value_parser=Self::post_validate) */)]
struct Cli {
    a: usize,
    b: usize,
    c: usize,
}

impl Cli {
    fn post_validate(self) -> Result<Self, String> {
        if self.a + self.b > MAX_VALUE {
            Err(format!(
                "a and b should not add up to more than {MAX_VALUE}"
            ))
        } else {
            Ok(self)
        }
    }
}

fn handler(error: String) -> ! {
    let mut cmd = Cli::command();
    cmd.error(clap::error::ErrorKind::ArgumentConflict, error)
        .exit()
}

fn main() {
    let opt = Cli::parse().post_validate().map_err(handler).unwrap();
    assert!(
        opt.a + opt.b <= MAX_VALUE,
        "Clap didn't validate all invariants"
    );
    dbg!(opt);
}

Additional Context

The klask link may show that it only supports clap v3 but several forks that support v4 exist (1, 2)

@Easyoakland Easyoakland added the C-enhancement Category: Raise on the bar on expectations label Jun 24, 2023
@epage
Copy link
Member

epage commented Jun 25, 2023

I'm going to close in favor of #3008 though it will be exposed at the lower levels of clap, rather than at the derive levels.

@epage epage closed this as not planned Won't fix, can't repro, duplicate, stale Jun 25, 2023
@epage epage added the A-validators Area: ArgMatches validation logi label Jun 25, 2023
@Easyoakland
Copy link
Author

So is there a better currently supported way of doing this or should I stick to let opt = Cli::parse().post_validate().map_err(handler).unwrap(); until #3008 issue is resolved?

@epage
Copy link
Member

epage commented Jun 25, 2023

Post-parse validation is the current way of doing this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-validators Area: ArgMatches validation logi C-enhancement Category: Raise on the bar on expectations
Projects
None yet
Development

No branches or pull requests

2 participants