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 kms_key;
15
16use std::collections::HashMap;
17use std::sync::OnceLock;
18
19use serde_json::Value;
20
21/// Context passed to shapers so they can construct ARNs and other
22/// region/account-scoped identifiers.
23pub struct ShapeContext<'a> {
24    pub region: &'a str,
25    pub account_id: &'a str,
26}
27
28/// Result of shaping a freshly created resource.
29pub struct ShapedResource {
30    /// The value of the resource type's `primaryIdentifier` after shaping
31    /// (e.g. `KeyId` for `AWS::KMS::Key`).
32    pub primary_identifier: String,
33    /// The fully shaped resource model — `writeOnlyProperties` removed,
34    /// `readOnlyProperties` generated, schema defaults filled in.
35    pub properties: Value,
36}
37
38pub trait CfnResourceShaper: Send + Sync {
39    /// Shape the stored model from the create-time `DesiredState`.
40    fn shape_create(
41        &self,
42        desired_state: &Value,
43        ctx: &ShapeContext<'_>,
44    ) -> Result<ShapedResource, String>;
45
46    /// Re-apply schema discipline after an `UpdateResource` JSON patch is
47    /// applied to the existing stored model. Default: passes through.
48    fn shape_update(
49        &self,
50        _previous: &Value,
51        patched: Value,
52        _ctx: &ShapeContext<'_>,
53    ) -> Result<Value, String> {
54        Ok(patched)
55    }
56}
57
58type ShaperBox = Box<dyn CfnResourceShaper>;
59
60fn registry() -> &'static HashMap<&'static str, ShaperBox> {
61    static REGISTRY: OnceLock<HashMap<&'static str, ShaperBox>> = OnceLock::new();
62    REGISTRY.get_or_init(|| {
63        let mut m: HashMap<&'static str, ShaperBox> = HashMap::new();
64        m.insert(
65            "AWS::DynamoDB::Table",
66            Box::new(dynamodb_table::DynamoDbTableShaper),
67        );
68        m.insert("AWS::ECS::Cluster", Box::new(ecs_cluster::EcsClusterShaper));
69        m.insert("AWS::KMS::Key", Box::new(kms_key::KmsKeyShaper));
70        m
71    })
72}
73
74/// Look up the shaper registered for a CloudFormation resource type name.
75pub fn lookup(type_name: &str) -> Option<&'static dyn CfnResourceShaper> {
76    registry().get(type_name).map(|b| b.as_ref())
77}