ggen-cli-lib 26.5.19

CLI interface for ggen
Documentation
//! Compile-Time Validation Module
//!
//! Part of the Andon Signal Validation Framework - Layer 1: Compile-Time Validation
//!
//! This module provides compile-time guarantees for CLI command correctness:
//! - Validates CLI command structure
//! - Ensures required arguments are present
//! - Verifies command signatures match expectations
//! - Validates test configurations

use std::collections::HashMap;
use std::path::Path;

/// CLI Command Validation
///
/// Validates that CLI commands have correct structure and required arguments.
/// This is a compile-time check to catch issues before code runs.
pub struct CommandValidator;

impl CommandValidator {
    /// Validate a CLI command definition
    ///
    /// # Compile-Time Checks
    ///
    /// - Command name is valid
    /// - Required arguments are present
    /// - Argument types are valid
    /// - Command structure matches expectations
    pub fn validate_command(
        noun: &str,
        verb: &str,
        args: &[(&str, bool)], // (arg_name, required)
    ) -> Result<(), ValidationError> {
        // Validate noun and verb names
        if noun.is_empty() {
            return Err(ValidationError::InvalidCommandName(
                "Noun cannot be empty".to_string(),
            ));
        }
        if verb.is_empty() {
            return Err(ValidationError::InvalidCommandName(
                "Verb cannot be empty".to_string(),
            ));
        }

        // Validate argument names
        for (arg_name, _required) in args {
            if arg_name.is_empty() {
                return Err(ValidationError::InvalidArgument(
                    "Argument name cannot be empty".to_string(),
                ));
            }
        }

        Ok(())
    }

    /// Get all registered CLI commands for validation
    ///
    /// This is used at compile time to ensure all commands are properly registered.
    pub fn get_registered_commands() -> HashMap<String, Vec<String>> {
        // This would be populated by a build script or proc-macro
        // For now, return known commands
        let mut commands = HashMap::new();
        commands.insert("ci".to_string(), vec!["workflow".to_string()]);
        commands.insert("workflow".to_string(), vec!["init".to_string()]);
        commands.insert(
            "template".to_string(),
            vec!["show".to_string(), "generate".to_string()],
        );
        commands.insert("graph".to_string(), vec!["query".to_string()]);
        commands.insert("ontology".to_string(), vec!["extract".to_string()]);
        commands.insert("project".to_string(), vec!["init".to_string()]);
        commands
    }
}

/// Test Configuration Validation
///
/// Validates clnrm test configuration files at compile time.
pub struct TestConfigValidator;

impl TestConfigValidator {
    /// Validate a clnrm test configuration file
    ///
    /// # Compile-Time Checks
    ///
    /// - TOML syntax is valid
    /// - Required fields are present
    /// - Container images are specified
    /// - Assertions are valid
    pub fn validate_clnrm_config(path: &Path) -> Result<(), ValidationError> {
        if !path.exists() {
            return Err(ValidationError::FileNotFound(
                path.to_string_lossy().to_string(),
            ));
        }

        // Basic validation - file exists and is readable
        // Full TOML parsing would be done at runtime by clnrm
        Ok(())
    }

    /// Validate all clnrm test files in the project
    pub fn validate_all_clnrm_configs() -> Result<(), Vec<ValidationError>> {
        let test_dir = Path::new("tests/clnrm");
        let mut errors = Vec::new();

        if test_dir.exists() {
            // Validate main CLI commands test
            let cli_commands = test_dir.join("cli_commands.clnrm.toml");
            if let Err(e) = Self::validate_clnrm_config(&cli_commands) {
                errors.push(e);
            }
        }

        if errors.is_empty() {
            Ok(())
        } else {
            Err(errors)
        }
    }
}

/// Validation Error
#[derive(Debug, Clone)]
pub enum ValidationError {
    InvalidCommandName(String),
    InvalidArgument(String),
    FileNotFound(String),
    InvalidConfig(String),
}

impl std::fmt::Display for ValidationError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ValidationError::InvalidCommandName(msg) => {
                write!(f, "Invalid command name: {}", msg)
            }
            ValidationError::InvalidArgument(msg) => {
                write!(f, "Invalid argument: {}", msg)
            }
            ValidationError::FileNotFound(path) => {
                write!(f, "File not found: {}", path)
            }
            ValidationError::InvalidConfig(msg) => {
                write!(f, "Invalid configuration: {}", msg)
            }
        }
    }
}

impl std::error::Error for ValidationError {}

/// Compile-time assertion macro
///
/// Use this to validate CLI commands at compile time.
///
/// # Example
///
/// ```rust,ignore
/// validate_command!("ci", "workflow", [("name", false)]);
/// ```
#[macro_export]
macro_rules! validate_command {
    ($noun:expr, $verb:expr, [$($arg:expr),*]) => {
        const _: () = {
            use $crate::validation::CommandValidator;
            let args = [$($arg),*];
            if let Err(e) = CommandValidator::validate_command($noun, $verb, &args) {
                panic!("Command validation failed: {}", e);
            }
        };
    };
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_command_validator() {
        assert!(CommandValidator::validate_command("ci", "workflow", &[("name", false)]).is_ok());
    }

    #[test]
    fn test_command_validator_empty_noun() {
        assert!(CommandValidator::validate_command("", "workflow", &[]).is_err());
    }

    #[test]
    fn test_test_config_validator() {
        // This test would validate actual clnrm config files if they exist
        // For now, just test the structure
        let path = Path::new("tests/clnrm/cli_commands.clnrm.toml");
        // Don't fail if file doesn't exist in test environment
        let _ = TestConfigValidator::validate_clnrm_config(path);
    }
}