naru-config 0.7.0

A security-first configuration manager with encryption and audit logging
Documentation
use crate::core::constants::SCHEMA_FILE;
use crate::core::models::SchemaFile;
use crate::core::persistence;
use crate::core::security;
use anyhow::Result;
use console::{style, Emoji};

pub struct ListCommand {
    pub env: String,
}

impl ListCommand {
    pub fn new(env: String) -> Self {
        ListCommand { env }
    }

    pub fn execute(&self) -> Result<()> {
        security::validate_environment_name(&self.env)
            .map_err(|e| anyhow::anyhow!("Invalid environment name: {}", e))?;

        let schema: SchemaFile =
            persistence::atomic_read_json(SCHEMA_FILE, |s: &SchemaFile| s.clone()).unwrap_or_else(
                |_| SchemaFile {
                    version: "1.0".to_string(),
                    fields: vec![],
                },
            );

        persistence::atomic_read_config(|config| {
            let env_config = config
                .environments
                .get(&self.env)
                .ok_or_else(|| anyhow::anyhow!("Environment '{}' not found.", self.env))?;

            println!(
                "\n{} {}",
                Emoji("📁", ""),
                style(format!("Environment: {}", self.env)).bold().cyan()
            );
            println!("{}", style("=".repeat(60)).dim());

            let mut all_keys: std::collections::BTreeSet<String> =
                env_config.entries.keys().cloned().collect();
            for field in &schema.fields {
                all_keys.insert(field.key.clone());
            }

            if all_keys.is_empty() {
                println!("  (empty)");
            } else {
                for key in all_keys {
                    let entry = env_config.entries.get(&key);
                    let field_def = schema.fields.iter().find(|f| f.key == key);

                    let key_style = if entry.is_some() {
                        style(&key).bold()
                    } else {
                        style(&key).dim()
                    };
                    let secret_icon = if entry.is_some_and(|e| e.is_secret) {
                        Emoji("🔒 ", "")
                    } else {
                        Emoji("   ", "")
                    };

                    let value_str = match entry {
                        Some(e) => {
                            if e.is_secret {
                                style("********").dim().italic().to_string()
                            } else {
                                style(&e.value).green().to_string()
                            }
                        }
                        None => style("MISSING").red().italic().to_string(),
                    };

                    let type_str = match field_def {
                        Some(f) => style(format!("[{}]", f.r#type)).dim().to_string(),
                        _ => "".to_string(),
                    };

                    println!(
                        "  {} {:<20} = {:<15} {}",
                        secret_icon, key_style, value_str, type_str
                    );

                    if let Some(f) = field_def {
                        if let Some(desc) = &f.description {
                            println!("     {}", style(format!("└─ {}", desc)).dim().italic());
                        }
                    }
                }
            }
            println!("{}", style("=".repeat(60)).dim());
            Ok(())
        })
        .map_err(|e| anyhow::anyhow!("Read error: {}", e))?
    }
}