horkos 0.2.0

Cloud infrastructure language where insecure code won't compile
Documentation
//! Central registry of all resource definitions.

use super::generated;
use super::ResourceDefinition;
use crate::types::ResolvedType;
use std::collections::HashMap;
use std::sync::OnceLock;

/// Global registry of all resource definitions.
static REGISTRY: OnceLock<ResourceRegistry> = OnceLock::new();

/// Get the global resource registry.
pub fn get() -> &'static ResourceRegistry {
    REGISTRY.get_or_init(ResourceRegistry::new)
}

/// Registry of all resource definitions, indexed by module.function.
pub struct ResourceRegistry {
    resources: HashMap<(String, String), ResourceDefinition>,
    modules: Vec<&'static str>,
}

impl ResourceRegistry {
    fn new() -> Self {
        let mut registry = Self {
            resources: HashMap::new(),
            modules: vec!["S3", "Network", "CloudWatch", "IAM", "EC2", "Secret", "RDS"],
        };

        // Register all generated resources
        for def in generated::all_resources() {
            registry.register(def);
        }

        registry
    }

    fn register(&mut self, def: ResourceDefinition) {
        let key = (def.module.to_string(), def.function.to_string());
        self.resources.insert(key, def);
    }

    /// Look up a resource definition by module and function name.
    pub fn get(&self, module: &str, function: &str) -> Option<&ResourceDefinition> {
        let key = (module.to_string(), function.to_string());
        self.resources.get(&key)
    }

    /// Check if a module exists.
    pub fn is_module(&self, name: &str) -> bool {
        self.modules.contains(&name)
    }

    /// Get the function type for a module member.
    pub fn resolve_member(&self, module: &str, function: &str) -> ResolvedType {
        if let Some(def) = self.get(module, function) {
            // Build function type from definition
            let params: Vec<(String, ResolvedType)> = def
                .all_params()
                .iter()
                .map(|p| (p.name.to_string(), p.param_type.clone()))
                .collect();

            ResolvedType::Function {
                params,
                returns: Box::new(def.returns.clone()),
            }
        } else {
            ResolvedType::Unknown
        }
    }

    /// Get the return type of a function.
    pub fn resolve_return(&self, module: &str, function: &str) -> ResolvedType {
        self.get(module, function)
            .map(|def| def.returns.clone())
            .unwrap_or(ResolvedType::Unknown)
    }
}

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

    #[test]
    fn test_registry_lookup() {
        let registry = get();

        let bucket = registry.get("S3", "createBucket");
        assert!(bucket.is_some());

        let def = bucket.unwrap();
        assert_eq!(def.module, "S3");
        assert_eq!(def.function, "createBucket");
        assert_eq!(def.required_params.len(), 1);
        assert_eq!(def.security_params.len(), 2); // encryption, publicAccess (security-critical)
        assert_eq!(def.preferred_params.len(), 2); // versioning, logging (recommended)
    }

    #[test]
    fn test_registry_resolve_member() {
        let registry = get();

        let fn_type = registry.resolve_member("S3", "createBucket");
        assert!(matches!(fn_type, ResolvedType::Function { .. }));
    }

    #[test]
    fn test_registry_unknown() {
        let registry = get();

        let unknown = registry.get("Unknown", "foo");
        assert!(unknown.is_none());
    }
}