cumulus 0.0.6

AWS CloudFormation Template Generator
Documentation
use serde::Serialize;

use crate::skip_serializing_if::SkipSerializingIf;

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct BlockDeviceMapping {
    device_name: String,
    #[serde(flatten)]
    device: BlockDevice,
}

impl BlockDeviceMapping {
    pub fn ephemeral(n: u8, name: impl Into<String>) -> Self {
        let device_name = name.into();
        let device = BlockDevice::ephemeral(n);
        Self {
            device_name,
            device,
        }
    }

    pub fn ebs(name: impl Into<String>, size: u64) -> Self {
        let device_name = name.into();
        let device = BlockDevice::ebs(size);
        Self {
            device_name,
            device,
        }
    }

    pub fn with_ebs(name: impl Into<String>, ebs: Ebs) -> Self {
        let device_name = name.into();
        let device = BlockDevice::with_ebs(ebs);
        Self {
            device_name,
            device,
        }
    }

    pub fn no_device(name: impl Into<String>) -> Self {
        let device_name = name.into();
        let device = BlockDevice::no_device();
        Self {
            device_name,
            device,
        }
    }
}

#[derive(Clone, Debug, Serialize)]
struct NoDevice {}

#[derive(Clone, Debug, Serialize)]
enum BlockDevice {
    Ebs(Ebs),
    VirtualName(String),
    NoDevice(NoDevice),
}

impl BlockDevice {
    fn ephemeral(n: u8) -> Self {
        let name = format!("ephemeral{}", n);
        Self::VirtualName(name)
    }

    fn ebs(size: u64) -> Self {
        let ebs = Ebs::new(size);
        Self::Ebs(ebs)
    }

    fn with_ebs(ebs: Ebs) -> Self {
        Self::Ebs(ebs)
    }

    fn no_device() -> Self {
        Self::NoDevice(NoDevice {})
    }
}

//{
//    "DeleteOnTermination" : Boolean,
//    "Encrypted" : Boolean,
//    "Iops" : Integer,
//    "KmsKeyId" : String,
//    "SnapshotId" : String,
//    "VolumeSize" : Integer,
//    "VolumeType" : String
//}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct Ebs {
    #[serde(skip_serializing_if = "bool::is_true")]
    delete_on_termination: bool,
    #[serde(skip_serializing_if = "bool::is_false")]
    encrypted: bool,
    #[serde(skip_serializing_if = "u64::is_zero")]
    iops: u64,
    #[serde(skip_serializing_if = "String::is_empty")]
    kms_key_id: String,
    #[serde(skip_serializing_if = "String::is_empty")]
    snapshot_id: String,
    volume_size: u64,
    #[serde(skip_serializing_if = "VolumeType::is_gp2")]
    volume_type: VolumeType,
}

impl Default for Ebs {
    fn default() -> Self {
        Self {
            delete_on_termination: true,
            encrypted: false,
            iops: 0,
            kms_key_id: String::new(),
            snapshot_id: String::new(),
            volume_size: 0,
            volume_type: VolumeType::default(),
        }
    }
}

impl Ebs {
    pub fn new(size: u64) -> Self {
        Self {
            volume_size: size,
            ..Self::default()
        }
    }

    pub fn standard(self) -> Self {
        let volume_type = VolumeType::Standard;
        Self {
            volume_type,
            ..self
        }
    }

    pub fn io1(self, iops: u64) -> Self {
        let volume_type = VolumeType::Io1;
        Self {
            iops,
            volume_type,
            ..self
        }
    }

    pub fn sc1(self) -> Self {
        let volume_type = VolumeType::Sc1;
        Self {
            volume_type,
            ..self
        }
    }

    pub fn st1(self) -> Self {
        let volume_type = VolumeType::St1;
        Self {
            volume_type,
            ..self
        }
    }
}

#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
enum VolumeType {
    Gp2,
    Io1,
    Sc1,
    St1,
    Standard,
}

impl Default for VolumeType {
    fn default() -> Self {
        Self::Gp2
    }
}

impl VolumeType {
    fn is_gp2(&self) -> bool {
        *self == Self::Gp2
    }
}

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

    use inspector::ResultInspector;

    #[test]
    fn ephemeral() {
        let ephemeral = r#"{"DeviceName":"/dev/sdc","VirtualName":"ephemeral3"}"#;
        let device = BlockDeviceMapping::ephemeral(3, "/dev/sdc");
        let json = serde_json::to_string(&device)
            .inspect(|text| println!("{}", text))
            .unwrap();
        assert_eq!(json, ephemeral);
    }

    #[test]
    fn ebs() {
        let ebs = r#"{"DeviceName":"/dev/sdc","Ebs":{"VolumeSize":100}}"#;
        let device = BlockDeviceMapping::ebs("/dev/sdc", 100);
        let json = serde_json::to_string(&device)
            .inspect(|text| println!("{}", text))
            .unwrap();
        assert_eq!(json, ebs);
    }

    #[test]
    fn no_device() {
        let no_device = r#"{"DeviceName":"/dev/sdc","NoDevice":{}}"#;
        let device = BlockDeviceMapping::no_device("/dev/sdc");
        let json = serde_json::to_string(&device)
            .inspect(|text| println!("{}", text))
            .unwrap();
        assert_eq!(json, no_device);
    }
}