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

Possible bug with enum fields #4865

Closed
2 tasks done
nerdo opened this issue Apr 27, 2023 · 2 comments · Fixed by #4867
Closed
2 tasks done

Possible bug with enum fields #4865

nerdo opened this issue Apr 27, 2023 · 2 comments · Fixed by #4867
Labels
C-bug Category: Updating dependencies

Comments

@nerdo
Copy link

nerdo commented Apr 27, 2023

Please complete the following tasks

Rust Version

1.6.9

Clap Version

4.2.1

Minimal reproducible code

// https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0ba3a366598032fb8252f66cc8b11851
// This could probably be more concise, but I pulled it out of a real-world example and kept the fields in
// because I think it has something to do with dependencies between fields. Demo on the rust playground link above.

use clap::{Parser, ValueEnum}; // 4.2.3

fn main() -> Result<(), anyhow::Error> {
    // This prints out the help as expected because not enough args.
    // let ok1 = ["--dry-run", "--save-config"];
    // let a1 = EtlConfig::parse_from(ok1.iter());
    // dbg!("a1 = {?:}", a1);
    
    // This prints out the help as expected because not enough args.
    // let ok2 = ["--dry-run", "--save-config", "--mysql-host", "localhost"];
    // let a2 = EtlConfig::parse_from(ok2.iter());
    // dbg!("a2 = {?:}", a2);
    
    // This panics! --target is required, and is missing here, 
    // but rather than print the help, it panics.
    let panicking_args = ["--dry-run", "--save-config", "--mysql-host", "127.0.0.1", "--mongo-host", "127.0.0.1"];
    EtlConfig::parse_from(panicking_args.iter());
    
    Ok(())
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum TargetDatabase {
    /// MongoDB
    MongoDB,

    /// SurrealDB
    SurrealDB,
}

/// Configuration.
#[derive(Debug, Parser, Clone)]
struct EtlConfig {
    /// Save the configuration.
    ///
    /// If the --config-file option is not set, it will output to STDOUT.
    #[arg(long)]
    save_config: bool,

    /// Load configuration from file.
    ///
    /// If the --config-file option is not set, it will read from STDIN.
    #[arg(long)]
    load_config: bool,

    /// Configuration file to save to or load from.
    #[arg(long)]
    config_file: Option<String>,

    /// Start by resetting the new database and starting from scratch.
    #[arg(long)]
    reset: bool,

    /// Do a dry-run: Don't write to the new database, but do everything else (as much as possible).
    #[arg(long)]
    dry_run: bool,

    /// Target database system.
    #[arg(long, value_enum)]
    target: TargetDatabase,

    /// MySQL (Chili) connection configuration.
    #[command(flatten)]
    mysql: MySqlConfig,

    /// MongoDB connection configuration.
    #[command(flatten)]
    mongo: Option<MongoDbConfig>,

    /// SurrealDB connection configuration.
    #[command(flatten)]
    surreal: Option<SurrealDbConfig>,
}


/// MySql (Chili) Configuration.
#[derive(Debug, Parser,  Clone)]
#[group(multiple = false)]
struct MySqlConfig {
    /// MySQL username.
    #[arg(long, default_value_t = String::from("chili"))]
    mysql_username: String,

    /// MySQL password.
    #[arg(long, default_value_t = String::from("chili"))]
    mysql_password: String,

    /// MySQL server host name.
    #[arg(long, default_value_t = String::from("localhost"))]
    mysql_host: String,

    /// MySQL port.
    #[arg(long, default_value_t = 3306, value_parser = clap::value_parser!(u16).range(1..))]
    mysql_port: u16,

    /// MySQL database.
    #[arg(long, default_value_t = String::from("chili"))]
    mysql_database: String,
}

/// MongoDB Configuration.
#[derive(Debug, Parser,  Clone)]
#[group(multiple = false, requires("mongo-db"))]
struct MongoDbConfig {
    /// MongoDB username.
    #[arg(long, default_value_t = String::from("root"))]
    mongo_username: String,

    /// MongoDB password.
    #[arg(long, default_value_t = String::from("root"))]
    #[arg(requires_if("mongo-db", "target"))]
    mongo_password: String,

    /// MongoDB server host name.
    #[arg(long, default_value_t = String::from("localhost"))]
    #[arg(requires_if("mongo-db", "target"))]
    mongo_host: String,

    /// MongoDB port.
    #[arg(long, default_value_t = 27017, value_parser = clap::value_parser!(u16).range(1..))]
    #[arg(requires_if("mongo-db", "target"))]
    mongo_port: u16,

    /// MongoDB database.
    #[arg(long, default_value_t = String::from("reaper"))]
    #[arg(requires_if("mongo-db", "target"))]

    mongo_database: String,
}

/// SurrealDB Configuration.
#[derive(Debug, Parser,  Clone)]
#[group(multiple = false, requires("surreal-db"))]
struct SurrealDbConfig {
    /// SurrealDB username.
    #[arg(long, default_value_t = String::from("root"))]

    surreal_username: String,

    /// SurrealDB password.
    #[arg(long, default_value_t = String::from("root"))]
    #[arg(requires_if("surreal-db", "target"))]

    surreal_password: String,

    /// SurrealDB server host name.
    #[arg(long, default_value_t = String::from("localhost"))]
    #[arg(requires_if("surreal-db", "target"))]

    surreal_host: String,

    /// SurrealDB port.
    #[arg(long, default_value_t = 8000, value_parser = clap::value_parser!(u16).range(1..))]
    #[arg(requires_if("surreal-db", "target"))]

    surreal_port: u16,

    /// SurrealDB namespace.
    #[arg(long, default_value_t = String::from("reaper"))]
    #[arg(requires_if("surreal-db", "target"))]

    surreal_namespace: String,

    /// SurrealDB database.
    #[arg(long, default_value_t = String::from("reaper"))]
    #[arg(requires_if("surreal-db", "target"))]

    surreal_database: String,
}

Steps to reproduce the bug with the above code

Run the code in the playground.

There are some commented out sections of code, with descriptions to illustrate other behavior. This ticket documents the code as it currently stands, though.

Actual Behaviour

Code panics and crashes during parsing.

Expected Behaviour

I expected to see the help screen saying that --target is a required field.

Additional Context

No response

Debug Output

No response

@nerdo nerdo added the C-bug Category: Updating dependencies label Apr 27, 2023
epage added a commit to epage/clap that referenced this issue Apr 27, 2023
epage added a commit to epage/clap that referenced this issue Apr 27, 2023
@epage
Copy link
Member

epage commented Apr 27, 2023

The application bug is the following two lines:

#[group(multiple = false, requires("mongo-db"))]
// ...
#[group(multiple = false, requires("surreal-db"))]
// ...

The MongoDbConfig is requiring that a group or argument named mongo-db to be present but nothing exists with that ID. The same with surreal-db.

I have a PR up for improving the error messages for this

@nerdo
Copy link
Author

nerdo commented Apr 28, 2023

Ah, I think I wrote that in attempt to make those fields required if surreal or mongo were selected as the target respectively, but I obviously did it wrong - and honestly, forgot I even had that there. Thanks for the feedback. And thanks for adding the PR improving the error message. I had no idea what the issue was.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: Updating dependencies
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants