cumulus 0.0.6

AWS CloudFormation Template Generator
Documentation
use std::collections::HashMap;

use serde::Serialize;

use crate::error;

use crate::condition::Condition;
use crate::mapping::Mapping;
use crate::metadata::Metadata;
use crate::output::Output;
use crate::parameter::Parameter;
use crate::resource::{Resource, ToResource};
use crate::transform::Transform;

const AWS_TEMPLATE_FORMAT_VERSION: &str = "2010-09-09";

#[derive(Debug, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct Template {
    #[serde(rename = "AWSTemplateFormatVersion")]
    aws_template_format_version: String,
    #[serde(skip_serializing_if = "String::is_empty")]
    description: String,
    #[serde(skip_serializing_if = "HashMap::is_empty")]
    metadata: HashMap<String, Metadata>,
    #[serde(skip_serializing_if = "HashMap::is_empty")]
    parameters: HashMap<String, Parameter>,
    #[serde(skip_serializing_if = "HashMap::is_empty")]
    mappings: HashMap<String, Mapping>,
    #[serde(skip_serializing_if = "HashMap::is_empty")]
    conditions: HashMap<String, Condition>,
    #[serde(skip_serializing_if = "HashMap::is_empty")]
    transform: HashMap<String, Transform>,
    resources: HashMap<String, Resource>,
    #[serde(skip_serializing_if = "HashMap::is_empty")]
    outputs: HashMap<String, Output>,
}

impl Default for Template {
    fn default() -> Self {
        let aws_template_format_version = String::from(AWS_TEMPLATE_FORMAT_VERSION);

        Self {
            aws_template_format_version,
            description: String::new(),
            metadata: HashMap::default(),
            parameters: HashMap::default(),
            mappings: HashMap::default(),
            conditions: HashMap::default(),
            transform: HashMap::default(),
            resources: HashMap::default(),
            outputs: HashMap::default(),
        }
    }
}

impl Template {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn description(&mut self, description: impl Into<String>) -> &mut Self {
        self.description = description.into();
        // See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-description-structure.html
        self.description.truncate(1024);
        self
    }

    pub fn resource(&mut self, resource: impl ToResource) -> &mut Self {
        let name = resource.name();
        let resource = resource.into();
        self.resources.insert(name, resource);
        self
    }

    pub fn parameter(&mut self, parameter: Parameter) -> &mut Self {
        let name = parameter.name();
        self.parameters.insert(name, parameter);
        self
    }

    pub fn mapping(&mut self, name: impl Into<String>, mapping: Mapping) -> &mut Self {
        let name = name.into();
        self.mappings.insert(name, mapping);
        self
    }

    pub fn output(&mut self, name: impl Into<String>, output: Output) -> &mut Self {
        let name = name.into();
        self.outputs.insert(name, output);
        self
    }

    pub fn validate(&self) -> Result<&Self, error::Error> {
        Ok(self)
    }

    pub fn json(&self) -> String {
        serde_json::to_string(self).expect("Template must be serializable")
    }

    pub fn json_pretty(&self) -> String {
        serde_json::to_string_pretty(self).expect("Template must be serializable")
    }

    #[cfg(feature = "yaml")]
    pub fn yaml(&self) -> String {
        serde_yaml::to_string(self).expect("Template must be serializable")
    }
}

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

    #[test]
    fn short_description() {
        let mut template = Template::new();
        template.description("Example");
        assert_eq!(template.description, "Example");
    }

    #[test]
    fn long_description() {
        let mut template = Template::new();
        let long = "0123456789abcdef".repeat(128);
        let short = "0123456789abcdef".repeat(64);
        template.description(&long);
        assert_ne!(template.description, long);
        assert_eq!(template.description, short);
    }
}