Skip to main content

winterbaume_cloudcontrol/cfn_schema/
mod.rs

1//! Per-CFN-resource-type shaping of Cloud Control API stored models.
2//!
3//! Real Cloud Control builds the read model from each resource type's
4//! CloudFormation schema: `writeOnlyProperties` are stripped from the stored
5//! model, `readOnlyProperties` are generated by the service, and schema
6//! defaults are filled in when absent from the create request. This module
7//! provides a per-type [`CfnResourceShaper`] trait and a static registry.
8//! Types with a registered shaper produce AWS-compatible `GetResource` output;
9//! unregistered types fall back to storing the create-time desired state
10//! verbatim (the prior behaviour).
11
12mod dynamodb_table;
13mod ecs_cluster;
14mod elbv2_listener;
15mod elbv2_load_balancer;
16mod elbv2_target_group;
17mod kms_key;
18
19use std::collections::HashMap;
20use std::sync::OnceLock;
21
22use serde_json::Value;
23
24/// Context passed to shapers so they can construct ARNs and other
25/// region/account-scoped identifiers.
26pub struct ShapeContext<'a> {
27    pub region: &'a str,
28    pub account_id: &'a str,
29}
30
31/// Result of shaping a freshly created resource.
32pub struct ShapedResource {
33    /// The value of the resource type's `primaryIdentifier` after shaping
34    /// (e.g. `KeyId` for `AWS::KMS::Key`).
35    pub primary_identifier: String,
36    /// The fully shaped resource model — `writeOnlyProperties` removed,
37    /// `readOnlyProperties` generated, schema defaults filled in.
38    pub properties: Value,
39}
40
41pub trait CfnResourceShaper: Send + Sync {
42    /// Shape the stored model from the create-time `DesiredState`.
43    fn shape_create(
44        &self,
45        desired_state: &Value,
46        ctx: &ShapeContext<'_>,
47    ) -> Result<ShapedResource, String>;
48
49    /// Re-apply schema discipline after an `UpdateResource` JSON patch is
50    /// applied to the existing stored model. Default: passes through.
51    fn shape_update(
52        &self,
53        _previous: &Value,
54        patched: Value,
55        _ctx: &ShapeContext<'_>,
56    ) -> Result<Value, String> {
57        Ok(patched)
58    }
59}
60
61type ShaperBox = Box<dyn CfnResourceShaper>;
62
63fn registry() -> &'static HashMap<&'static str, ShaperBox> {
64    static REGISTRY: OnceLock<HashMap<&'static str, ShaperBox>> = OnceLock::new();
65    REGISTRY.get_or_init(|| {
66        let mut m: HashMap<&'static str, ShaperBox> = HashMap::new();
67        m.insert(
68            "AWS::DynamoDB::Table",
69            Box::new(dynamodb_table::DynamoDbTableShaper),
70        );
71        m.insert("AWS::ECS::Cluster", Box::new(ecs_cluster::EcsClusterShaper));
72        m.insert(
73            "AWS::ElasticLoadBalancingV2::Listener",
74            Box::new(elbv2_listener::ElbV2ListenerShaper),
75        );
76        m.insert(
77            "AWS::ElasticLoadBalancingV2::LoadBalancer",
78            Box::new(elbv2_load_balancer::ElbV2LoadBalancerShaper),
79        );
80        m.insert(
81            "AWS::ElasticLoadBalancingV2::TargetGroup",
82            Box::new(elbv2_target_group::ElbV2TargetGroupShaper),
83        );
84        m.insert("AWS::KMS::Key", Box::new(kms_key::KmsKeyShaper));
85        m
86    })
87}
88
89/// Look up the shaper registered for a CloudFormation resource type name.
90pub fn lookup(type_name: &str) -> Option<&'static dyn CfnResourceShaper> {
91    registry().get(type_name).map(|b| b.as_ref())
92}