alien-permissions 1.2.2

Alien Developer Platform
Documentation
use std::collections::HashMap;
use std::env;
use std::fs;
use std::path::Path;

fn main() {
    let out_dir = env::var("OUT_DIR").unwrap();
    let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();

    // Path to permission sets directory
    let permission_sets_dir = Path::new(&manifest_dir).join("permission-sets");

    // Tell cargo to rerun if permission sets change
    println!("cargo:rerun-if-changed={}", permission_sets_dir.display());

    // Read all JSONC files and generate registry
    let registry_code = generate_permission_set_registry(&permission_sets_dir);

    // Write the generated code to OUT_DIR
    let dest_path = Path::new(&out_dir).join("permission_sets_registry.rs");
    fs::write(&dest_path, registry_code).unwrap();

    println!(
        "Generated permission sets registry at: {}",
        dest_path.display()
    );
}

fn generate_permission_set_registry(permission_sets_dir: &Path) -> String {
    let mut code = String::new();

    // Add imports and dependencies
    code.push_str(
        r#"
// This file is auto-generated by build.rs
// Do not edit manually!

use std::collections::HashMap;
use std::sync::OnceLock;

"#,
    );

    // Collect all permission sets
    let mut permission_sets = HashMap::new();
    scan_permission_sets(permission_sets_dir, &mut permission_sets);

    // Generate constants for each permission set
    for (id, content) in &permission_sets {
        let const_name = id_to_const_name(id);
        // Use a delimiter that won't appear in the JSONC content
        code.push_str(&format!(
            "const {}: &str = r###\"{}\"###;\n\n",
            const_name, content
        ));
    }

    // Generate the OnceLock static registry
    code.push_str(r#"
static PERMISSION_SETS_REGISTRY: OnceLock<HashMap<&'static str, alien_core::permissions::PermissionSet>> = OnceLock::new();

fn get_permission_sets_registry() -> &'static HashMap<&'static str, alien_core::permissions::PermissionSet> {
    PERMISSION_SETS_REGISTRY.get_or_init(|| {
        let mut registry = HashMap::new();
        
"#);

    // Add each permission set to the registry
    for id in permission_sets.keys() {
        let const_name = id_to_const_name(id);
        code.push_str(&format!(
            r#"    registry.insert("{}", parse_permission_set({}));
"#,
            id, const_name
        ));
    }

    code.push_str(r#"        
        registry
    })
}

fn parse_permission_set(jsonc_content: &str) -> alien_core::permissions::PermissionSet {
    // Parse JSONC using json5
    json5::from_str(jsonc_content)
        .expect("Failed to parse permission set JSONC")
}

/// Normalize permission set ID to handle both hyphenated and underscore formats
fn normalize_permission_id(id: &str) -> String {
    id.replace('_', "-")
}

/// Get a permission set by ID from the built-in registry
/// Supports both hyphenated (azure-container-apps-environment) and underscore (azure_container_apps_environment) formats
pub fn get_permission_set(id: &str) -> Option<&'static alien_core::permissions::PermissionSet> {
    let registry = get_permission_sets_registry();
    
    // Try exact match first
    if let Some(perm_set) = registry.get(id) {
        return Some(perm_set);
    }
    
    // Try normalized version (convert underscores to hyphens)
    let normalized_id = normalize_permission_id(id);
    registry.get(normalized_id.as_str())
}

/// Get all available permission set IDs
pub fn list_permission_set_ids() -> Vec<&'static str> {
    get_permission_sets_registry().keys().copied().collect()
}

/// Check if a permission set exists in the registry
/// Supports both hyphenated (azure-container-apps-environment) and underscore (azure_container_apps_environment) formats
pub fn has_permission_set(id: &str) -> bool {
    let registry = get_permission_sets_registry();
    
    // Try exact match first
    if registry.contains_key(id) {
        return true;
    }
    
    // Try normalized version (convert underscores to hyphens)
    let normalized_id = normalize_permission_id(id);
    registry.contains_key(normalized_id.as_str())
}
"#);

    code
}

fn scan_permission_sets(dir: &Path, permission_sets: &mut HashMap<String, String>) {
    if !dir.exists() || !dir.is_dir() {
        return;
    }

    for entry in fs::read_dir(dir).unwrap() {
        let entry = entry.unwrap();
        let path = entry.path();

        if path.is_dir() {
            scan_permission_sets(&path, permission_sets);
        } else if path.extension().and_then(|s| s.to_str()) == Some("jsonc") {
            let content = fs::read_to_string(&path).unwrap();

            // Validate that the JSONC parses correctly at build time
            match validate_permission_set(&content, &path) {
                Ok(id) => {
                    permission_sets.insert(id, content);
                }
                Err(err) => {
                    panic!(
                        "Failed to parse permission set file at compile time: {}\nError: {}",
                        path.display(),
                        err
                    );
                }
            }
        }
    }
}

/// Validates a permission set JSONC at build time and returns the ID if successful
fn validate_permission_set(content: &str, path: &Path) -> Result<String, String> {
    // Parse as PermissionSet to validate structure and fields
    let permission_set: alien_core::permissions::PermissionSet =
        json5::from_str(content).map_err(|e| format!("JSON parsing error: {}", e))?;

    // Ensure ID is not empty
    if permission_set.id.is_empty() {
        return Err("Permission set ID cannot be empty".to_string());
    }

    // Validate description is not empty
    if permission_set.description.is_empty() {
        return Err("Permission set description cannot be empty".to_string());
    }

    println!(
        "✓ Validated permission set: {} ({})",
        permission_set.id,
        path.display()
    );

    Ok(permission_set.id)
}

fn id_to_const_name(id: &str) -> String {
    id.replace('/', "_").replace('-', "_").to_uppercase() + "_PERMISSION_SET"
}