Skip to main content

alien_core/
network_ids.rs

1//! Normalization at AWS network input and checkpoint boundaries.
2//!
3//! An unset CloudFormation CommaDelimitedList resolves to `[""]`. Terraform
4//! callers can supply the same value explicitly. Empty identifiers mean absence.
5
6use serde::{Deserialize, Deserializer};
7
8/// Deserialize network IDs, trimming whitespace and removing empty entries.
9pub fn deserialize_ids<'de, D: Deserializer<'de>>(
10    deserializer: D,
11) -> Result<Vec<String>, D::Error> {
12    let ids = Vec::<String>::deserialize(deserializer)?;
13    Ok(ids
14        .into_iter()
15        .map(|id| id.trim().to_string())
16        .filter(|id| !id.is_empty())
17        .collect())
18}
19
20/// Custom-resource attributes are strings when CloudFormation builds JSON outputs.
21/// Accept their comma-separated representation while always serializing typed lists.
22pub fn deserialize_ids_or_csv<'de, D: Deserializer<'de>>(
23    deserializer: D,
24) -> Result<Vec<String>, D::Error> {
25    #[derive(Deserialize)]
26    #[serde(untagged)]
27    enum Input {
28        List(Vec<String>),
29        Csv(String),
30    }
31    let values = match Input::deserialize(deserializer)? {
32        Input::List(values) => values,
33        Input::Csv(value) => value.split(',').map(str::to_string).collect(),
34    };
35    Ok(values
36        .into_iter()
37        .map(|value| value.trim().to_string())
38        .filter(|value| !value.is_empty())
39        .collect())
40}
41
42/// Deserialize an optional network ID; blank values mean no ID was supplied.
43pub fn deserialize_optional_id<'de, D: Deserializer<'de>>(
44    deserializer: D,
45) -> Result<Option<String>, D::Error> {
46    let id = Option::<String>::deserialize(deserializer)?;
47    Ok(id
48        .map(|id| id.trim().to_string())
49        .filter(|id| !id.is_empty()))
50}
51
52#[cfg(test)]
53mod tests {
54    use crate::{import::data::AwsNetworkImportData, NetworkSettings};
55    use serde_json::json;
56
57    #[test]
58    fn setup_network_settings_normalize_all_aws_id_lists_and_round_trip() {
59        for (ids, expected) in [
60            (json!([]), json!([])),
61            (json!([""]), json!([])),
62            (json!(["  "]), json!([])),
63            (json!(["", " id-a ", "id-b", ""]), json!(["id-a", "id-b"])),
64        ] {
65            let value = json!({"type": "byo-vpc-aws", "vpc_id": "vpc-test", "public_subnet_ids": ids, "private_subnet_ids": ids, "security_group_ids": ids});
66            let parsed: NetworkSettings = serde_json::from_value(value).unwrap();
67            let serialized = serde_json::to_value(&parsed).unwrap();
68            for key in [
69                "public_subnet_ids",
70                "private_subnet_ids",
71                "security_group_ids",
72            ] {
73                assert_eq!(serialized[key], expected);
74            }
75            assert_eq!(
76                serde_json::from_value::<NetworkSettings>(serialized).unwrap(),
77                parsed
78            );
79        }
80    }
81
82    #[test]
83    fn network_import_normalizes_flat_and_zonal_ids_and_optional_security_group() {
84        for group in [json!(null), json!(""), json!("  "), json!(" sg-real ")] {
85            let data: AwsNetworkImportData = serde_json::from_value(json!({
86                "vpcId": "vpc-test", "publicSubnetIds": ["", "  "],
87                "privateSubnetIds": ["", " subnet-private "], "securityGroupId": group,
88                "availabilityZones": [], "isByoVpc": "true",
89                "subnetsByFailureDomain": {"us-east-1a": {"publicSubnetIds": [""], "privateSubnetIds": [" subnet-private ", ""]}}
90            })).unwrap();
91            assert!(data.public_subnet_ids.is_empty());
92            assert_eq!(data.private_subnet_ids, ["subnet-private"]);
93            assert_eq!(
94                data.security_group_id.as_deref(),
95                if group == " sg-real " {
96                    Some("sg-real")
97                } else {
98                    None
99                }
100            );
101            assert!(data.subnets_by_failure_domain["us-east-1a"]
102                .public_subnet_ids
103                .is_empty());
104            assert_eq!(
105                data.subnets_by_failure_domain["us-east-1a"].private_subnet_ids,
106                ["subnet-private"]
107            );
108        }
109    }
110}