use crate::context::ExecutionContext;
use crate::executor::CommandHandler;
use crate::parser::ParsedArgs;
use crate::plugin::Plugin;
use crate::Result;
const SENSITIVE_NAME_SUBSTRINGS: &[&str] = &[
"SECRET",
"TOKEN",
"KEY",
"PASS",
"CREDENTIAL",
"AUTH",
"PRIVATE",
];
fn is_sensitive_name(name: &str) -> bool {
let upper = name.to_uppercase();
SENSITIVE_NAME_SUBSTRINGS
.iter()
.any(|needle| upper.contains(needle))
}
pub struct EnvPlugin;
impl EnvPlugin {
pub fn new() -> Self {
Self
}
}
impl Default for EnvPlugin {
fn default() -> Self {
Self::new()
}
}
impl Plugin for EnvPlugin {
fn name(&self) -> &str {
"env"
}
fn version(&self) -> &str {
env!("CARGO_PKG_VERSION")
}
fn description(&self) -> &str {
"Filtered environment variable display (feature-gated, #46)"
}
fn handlers(&self) -> Vec<(String, Box<dyn CommandHandler>)> {
vec![("env_show".to_string(), Box::new(EnvShowHandler))]
}
}
struct EnvShowHandler;
impl CommandHandler for EnvShowHandler {
fn execute(&self, _ctx: &mut dyn ExecutionContext, _args: &ParsedArgs) -> Result<()> {
let mut vars: Vec<(String, String)> = std::env::vars().collect();
vars.sort_by(|a, b| a.0.cmp(&b.0));
let mut hidden_count = 0usize;
for (name, value) in &vars {
if is_sensitive_name(name) {
hidden_count += 1;
println!("{name} = <hidden>");
} else {
println!("{name} = {value}");
}
}
if hidden_count > 0 {
println!("\n{hidden_count} variable(s) hidden (name looked sensitive)");
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::any::Any;
#[derive(Default)]
struct TestContext;
impl ExecutionContext for TestContext {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
#[test]
fn test_env_plugin_metadata() {
let p = EnvPlugin::new();
assert_eq!(p.name(), "env");
assert!(!p.version().is_empty());
assert!(!p.description().is_empty());
}
#[test]
fn test_env_plugin_default() {
let p = EnvPlugin::default();
assert_eq!(p.name(), "env");
}
#[test]
fn test_env_plugin_handler_name() {
let handlers = EnvPlugin::new().handlers();
assert_eq!(handlers.len(), 1);
assert_eq!(handlers[0].0, "env_show");
}
#[test]
fn test_env_handler_executes() {
let handlers = EnvPlugin::new().handlers();
let (_, handler) = &handlers[0];
let mut ctx = TestContext;
assert!(handler
.execute(&mut ctx, &ParsedArgs::from_scalars(Default::default()))
.is_ok());
}
#[test]
fn test_env_plugin_is_send_sync() {
fn assert_send_sync<T: Send + Sync>(_: T) {}
assert_send_sync(EnvPlugin::new());
}
#[test]
fn test_is_sensitive_name_matches_expected_patterns() {
assert!(is_sensitive_name("API_SECRET"));
assert!(is_sensitive_name("AWS_ACCESS_KEY_ID"));
assert!(is_sensitive_name("DATABASE_PASSWORD"));
assert!(is_sensitive_name("GITHUB_TOKEN"));
assert!(is_sensitive_name("MY_APP_CREDENTIAL"));
assert!(is_sensitive_name("BASIC_AUTH_HEADER"));
assert!(is_sensitive_name("SSH_PRIVATE_KEY_PATH"));
assert!(is_sensitive_name("api_secret"));
assert!(is_sensitive_name("Api_Secret"));
}
#[test]
fn test_is_sensitive_name_leaves_ordinary_vars_alone() {
assert!(!is_sensitive_name("PATH"));
assert!(!is_sensitive_name("HOME"));
assert!(!is_sensitive_name("LANG"));
assert!(!is_sensitive_name("EDITOR"));
assert!(!is_sensitive_name("RUST_LOG"));
}
}