winterbaume-cloudcontrol 1.0.1

Cloud Control API service implementation for winterbaume
Documentation
//! Per-CFN-resource-type shaping of Cloud Control API stored models.
//!
//! Real Cloud Control builds the read model from each resource type's
//! CloudFormation schema: `writeOnlyProperties` are stripped from the stored
//! model, `readOnlyProperties` are generated by the service, and schema
//! defaults are filled in when absent from the create request. This module
//! provides a per-type [`CfnResourceShaper`] trait and a static registry.
//! Types with a registered shaper produce AWS-compatible `GetResource` output;
//! unregistered types fall back to storing the create-time desired state
//! verbatim (the prior behaviour).

mod dynamodb_table;
mod ecs_cluster;
mod elbv2_listener;
mod elbv2_load_balancer;
mod elbv2_target_group;
mod kms_key;

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

use serde_json::Value;

/// Context passed to shapers so they can construct ARNs and other
/// region/account-scoped identifiers.
pub struct ShapeContext<'a> {
    pub region: &'a str,
    pub account_id: &'a str,
}

/// Result of shaping a freshly created resource.
pub struct ShapedResource {
    /// The value of the resource type's `primaryIdentifier` after shaping
    /// (e.g. `KeyId` for `AWS::KMS::Key`).
    pub primary_identifier: String,
    /// The fully shaped resource model — `writeOnlyProperties` removed,
    /// `readOnlyProperties` generated, schema defaults filled in.
    pub properties: Value,
}

pub trait CfnResourceShaper: Send + Sync {
    /// Shape the stored model from the create-time `DesiredState`.
    fn shape_create(
        &self,
        desired_state: &Value,
        ctx: &ShapeContext<'_>,
    ) -> Result<ShapedResource, String>;

    /// Re-apply schema discipline after an `UpdateResource` JSON patch is
    /// applied to the existing stored model. Default: passes through.
    fn shape_update(
        &self,
        _previous: &Value,
        patched: Value,
        _ctx: &ShapeContext<'_>,
    ) -> Result<Value, String> {
        Ok(patched)
    }
}

type ShaperBox = Box<dyn CfnResourceShaper>;

fn registry() -> &'static HashMap<&'static str, ShaperBox> {
    static REGISTRY: OnceLock<HashMap<&'static str, ShaperBox>> = OnceLock::new();
    REGISTRY.get_or_init(|| {
        let mut m: HashMap<&'static str, ShaperBox> = HashMap::new();
        m.insert(
            "AWS::DynamoDB::Table",
            Box::new(dynamodb_table::DynamoDbTableShaper),
        );
        m.insert("AWS::ECS::Cluster", Box::new(ecs_cluster::EcsClusterShaper));
        m.insert(
            "AWS::ElasticLoadBalancingV2::Listener",
            Box::new(elbv2_listener::ElbV2ListenerShaper),
        );
        m.insert(
            "AWS::ElasticLoadBalancingV2::LoadBalancer",
            Box::new(elbv2_load_balancer::ElbV2LoadBalancerShaper),
        );
        m.insert(
            "AWS::ElasticLoadBalancingV2::TargetGroup",
            Box::new(elbv2_target_group::ElbV2TargetGroupShaper),
        );
        m.insert("AWS::KMS::Key", Box::new(kms_key::KmsKeyShaper));
        m
    })
}

/// Look up the shaper registered for a CloudFormation resource type name.
pub fn lookup(type_name: &str) -> Option<&'static dyn CfnResourceShaper> {
    registry().get(type_name).map(|b| b.as_ref())
}