use facet::Facet;
use figue::{self as args, Driver, FigueBuiltins, MockEnv, builder};
#[derive(Facet, Debug)]
struct Args {
#[facet(args::config, args::env_prefix = "APP")]
config: ServerConfig,
#[facet(flatten)]
builtins: FigueBuiltins,
}
#[derive(Facet, Debug)]
struct ServerConfig {
#[facet(default = "localhost")]
host: String,
#[facet(default = 8080)]
port: u16,
#[facet(default)]
magic_link: Option<String>,
}
#[test]
fn test_help_works_with_unknown_env_keys_strict() {
let env = MockEnv::from_pairs([
("APP__AUTH__EMAIL_FROM", "noreply@example.com"),
("APP__AUTH__MAGIC_LINK_BASE_URL", "https://example.com"),
("APP__AUTH__RP_ID", "example.com"),
("APP__AUTH__RP_NAME", "Example"),
("APP__AUTH__RP_ORIGIN", "https://example.com"),
("APP__AUTH__SMTP_HOST", "smtp.example.com"),
("APP__AUTH__SMTP_PASSWORD", "secret"),
("APP__AUTH__SMTP_USERNAME", "user"),
]);
let config = builder::<Args>()
.unwrap()
.cli(|cli| cli.args(["--help"]).strict())
.env(|e| e.source(env).strict())
.build();
let driver = Driver::new(config);
let result = driver.run();
assert!(
result.is_err(),
"should be an error (Help is returned as error)"
);
let err = result.unwrap_err();
assert!(
err.is_help(),
"should be a Help error, not a parsing error. Got: {}",
err
);
let help = err.help_text().expect("should have help text");
assert!(help.contains("--help"), "help text should contain --help");
}
#[test]
fn test_help_short_flag_works_with_unknown_env_keys_strict() {
let env = MockEnv::from_pairs([("APP__UNKNOWN_FIELD", "value")]);
let config = builder::<Args>()
.unwrap()
.cli(|cli| cli.args(["-h"]).strict())
.env(|e| e.source(env).strict())
.build();
let driver = Driver::new(config);
let result = driver.run();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
err.is_help(),
"should be a Help error with -h flag. Got: {}",
err
);
}
#[test]
fn test_help_works_with_unknown_file_keys_strict() {
let config_json = r#"{
"auth": {
"email_from": "noreply@example.com"
}
}"#;
let config = builder::<Args>()
.unwrap()
.cli(|cli| cli.args(["--help"]).strict())
.file(|f| f.content(config_json, "config.json").strict())
.build();
let driver = Driver::new(config);
let result = driver.run();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
err.is_help(),
"should be a Help error, not a parsing error. Got: {}",
err
);
}
#[test]
fn test_unknown_key_in_env_strict_mode_shows_valid_fields() {
let env = MockEnv::from_pairs([
("APP__AUTH__EMAIL_FROM", "noreply@example.com"),
("APP__AUTH__SMTP_HOST", "smtp.example.com"),
]);
let config = builder::<Args>()
.unwrap()
.cli(|cli| cli.args::<[&str; 0], _>([]).strict())
.env(|e| e.source(env).strict())
.build();
let driver = Driver::new(config);
let err = driver.run().unwrap_err();
let err_str = err.to_string();
assert!(
err_str.contains("auth.email_from")
|| err_str.contains("AUTH__EMAIL_FROM")
|| err_str.contains("unknown"),
"error should mention the unknown key, got: {}",
err_str
);
assert!(
err_str.contains("host")
|| err_str.contains("port")
|| err_str.contains("magic_link")
|| err_str.contains("--help")
|| err_str.contains("Valid"),
"error should show valid fields or suggest --help, got: {}",
err_str
);
}
#[test]
fn test_unknown_key_in_file_strict_mode_shows_valid_fields() {
let config_json = r#"{
"auth": {
"email_from": "noreply@example.com",
"rp_id": "example.com"
}
}"#;
let config = builder::<Args>()
.unwrap()
.cli(|cli| cli.args::<[&str; 0], _>([]).strict())
.file(|f| f.content(config_json, "config.json").strict())
.build();
let driver = Driver::new(config);
let err = driver.run().unwrap_err();
let err_str = err.to_string();
assert!(
err_str.contains("host")
|| err_str.contains("port")
|| err_str.contains("magic_link")
|| err_str.contains("--help")
|| err_str.contains("Valid"),
"error should show valid fields or suggest --help, got: {}",
err_str
);
}
#[derive(Facet, Debug)]
struct ArgsWithConfigField {
#[facet(args::config, args::env_prefix = "APP")]
config: ServerConfig,
#[facet(flatten)]
builtins: FigueBuiltins,
}
#[derive(Facet, Debug)]
struct ArgsWithSettingsField {
#[facet(args::config, args::env_prefix = "APP")]
settings: ServerConfig,
#[facet(flatten)]
builtins: FigueBuiltins,
}
#[derive(Facet, Debug)]
struct ArgsWithRenamedConfigField {
#[facet(args::config, args::env_prefix = "APP", rename = "cfg")]
config: ServerConfig,
#[facet(flatten)]
builtins: FigueBuiltins,
}
#[test]
fn test_config_path_flag_not_silently_ignored() {
let config = builder::<ArgsWithConfigField>()
.unwrap()
.cli(|cli| cli.args(["--config", "/etc/app/config.json"]).strict())
.build();
let driver = Driver::new(config);
let result = driver.run();
match result.into_result() {
Ok(output) => {
panic!(
"--config flag was silently ignored! Got success with defaults: {:?}",
output.value
);
}
Err(err) => {
let err_str = err.to_string();
assert!(
!err_str.contains("unknown flag: --config"),
"--config should be recognized as the config file path flag, got: {}",
err_str
);
}
}
}
#[test]
fn test_settings_path_flag_not_silently_ignored() {
let config = builder::<ArgsWithSettingsField>()
.unwrap()
.cli(|cli| cli.args(["--settings", "/etc/app/settings.json"]).strict())
.build();
let driver = Driver::new(config);
let result = driver.run();
match result.into_result() {
Ok(output) => {
panic!(
"--settings flag was silently ignored! Got success with defaults: {:?}",
output.value
);
}
Err(err) => {
let err_str = err.to_string();
assert!(
!err_str.contains("unknown flag: --settings"),
"--settings should be recognized as the config file path flag, got: {}",
err_str
);
}
}
}
#[test]
fn test_renamed_config_path_flag_not_silently_ignored() {
let config = builder::<ArgsWithRenamedConfigField>()
.unwrap()
.cli(|cli| cli.args(["--cfg", "/etc/app/config.json"]).strict())
.build();
let driver = Driver::new(config);
let result = driver.run();
match result.into_result() {
Ok(output) => {
panic!(
"--cfg flag was silently ignored! Got success with defaults: {:?}",
output.value
);
}
Err(err) => {
let err_str = err.to_string();
assert!(
!err_str.contains("unknown flag: --cfg"),
"--cfg should be recognized as the config file path flag, got: {}",
err_str
);
}
}
}