use core::fmt;
#[derive(Debug, clap::Args)]
pub struct KvSetArgs {
pub key: String,
pub value: String,
}
#[derive(Debug, clap::Args)]
pub struct KvGetArgs {
pub key: Option<String>,
}
#[derive(Debug, clap::Args)]
pub struct KvUnsetArgs {
#[arg(required_unless_present = "all", conflicts_with = "all")]
pub key: Option<String>,
#[arg(long)]
pub all: bool,
}
#[derive(clap::Args)]
pub struct SecretArgs {
#[command(subcommand)]
pub command: SecretCommand,
}
impl fmt::Debug for SecretArgs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SecretArgs")
.field("command", &self.command)
.finish()
}
}
#[derive(clap::Subcommand)]
pub enum SecretCommand {
Set {
key: String,
#[arg(required_unless_present = "stdin", conflicts_with = "stdin")]
value: Option<String>,
#[arg(long)]
env: Option<String>,
#[arg(long)]
stdin: bool,
},
Get {
key: String,
#[arg(long)]
env: Option<String>,
},
Unset {
key: String,
#[arg(long)]
env: Option<String>,
},
List,
}
impl fmt::Debug for SecretCommand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Set {
key,
value,
env,
stdin,
} => {
let value = value.as_ref().map_or_else(
|| "None".to_string(),
|value| format!("Some(<{} bytes>)", value.len()),
);
f.debug_struct("Set")
.field("key", key)
.field("value", &format_args!("{value}"))
.field("env", env)
.field("stdin", stdin)
.finish()
}
Self::Get { key, env } => f
.debug_struct("Get")
.field("key", key)
.field("env", env)
.finish(),
Self::Unset { key, env } => f
.debug_struct("Unset")
.field("key", key)
.field("env", env)
.finish(),
Self::List => f.write_str("List"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cli::{Cli, Commands};
#[test]
fn unset_needs_a_key_or_the_all_flag() {
use clap::Parser;
assert!(Cli::try_parse_from(["shep", "unset"]).is_err());
assert!(Cli::try_parse_from(["shep", "unset", "a"]).is_ok());
assert!(Cli::try_parse_from(["shep", "unset", "--all"]).is_ok());
}
#[test]
fn unset_refuses_a_key_and_all_together() {
use clap::Parser;
assert!(Cli::try_parse_from(["shep", "unset", "a", "--all"]).is_err());
}
#[test]
fn get_takes_an_optional_key() {
use clap::Parser;
assert!(Cli::try_parse_from(["shep", "get"]).is_ok());
assert!(Cli::try_parse_from(["shep", "get", "a"]).is_ok());
}
#[test]
fn set_needs_both_a_key_and_a_value() {
use clap::Parser;
assert!(Cli::try_parse_from(["shep", "set", "a"]).is_err());
assert!(Cli::try_parse_from(["shep", "set", "a", "1"]).is_ok());
}
#[test]
fn secret_command_debug_does_not_leak() {
use clap::Parser;
let Commands::Secret(args) = Cli::try_parse_from([
"shep",
"secret",
"set",
"DB_PASSWORD",
"hunter2",
"--env",
"staging",
])
.unwrap()
.command
else {
panic!("secret parses to its own variant")
};
assert_eq!(
format!("{args:?}"),
"SecretArgs { command: Set { key: \"DB_PASSWORD\", value: Some(<7 bytes>), \
env: Some(\"staging\"), stdin: false } }"
);
assert_eq!(
format!("{:?}", SecretCommand::List),
"List",
"a variant carrying no value still has to render"
);
}
}