four_iam 0.0.6

framework for creating AWS CloudFormation template
Documentation
use four::{
    arn::Arn,
    convert::{WillBe, WillMappable},
    function::{
        getatt::{Attribute, HaveAtt},
        reference::{RefInner, Referenced},
    },
    logical_id::LogicalId,
    service::IAM,
    ManagedResource,
};
use serde::Serialize;

use crate::property::{
    action, policy_document::PolicyDocument, principal::Principal, statement::Statement,
};

#[derive(ManagedResource, Clone)]
#[resource_type = "AWS::IAM::Role"]
pub struct Role {
    logical_id: LogicalId,
    assume_role_policy_document: PolicyDocument,
    description: Option<String>,
    role_name: Option<WillBe<RoleName>>,
    managed_policy_arns: Option<Vec<WillBe<Arn<IAM>>>>,
}

impl Role {
    pub fn new(assume_role_policy_document: PolicyDocument, logical_id: LogicalId) -> Self {
        Self {
            logical_id,
            assume_role_policy_document,
            description: None,
            role_name: None,
            managed_policy_arns: None,
        }
    }

    pub fn assume_role(id: LogicalId, principal: Principal) -> Self {
        let statement = Statement::allow()
            .action(vec![Box::new(action::sts::AssumeRole)])
            .principal(principal);
        let policy_document = PolicyDocument::latest(vec![statement]);

        Self::new(policy_document, id)
    }

    pub fn description(mut self, description: &str) -> Self {
        self.description = Some(description.to_string());
        self
    }

    pub fn name(mut self, name: WillBe<String>) -> Self {
        self.role_name = Some(name.map());
        self
    }

    pub fn managed_policy_arns(mut self, arns: Vec<WillBe<Arn<IAM>>>) -> Self {
        self.managed_policy_arns = Some(arns);
        self
    }
}

impl Referenced for Role {
    type To = WillBe<RoleName>;

    fn referenced(&self) -> RefInner {
        RefInner::Id(self.logical_id.clone())
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct RoleName(String);

impl RoleName {
    pub fn new(name: String) -> Self {
        Self(name)
    }
}

impl WillMappable<String> for RoleName {}

#[derive(Debug, Clone, Serialize)]
pub struct RoleArn(Arn<IAM>);

impl From<Arn<IAM>> for RoleArn {
    fn from(value: Arn<IAM>) -> Self {
        RoleArn(value)
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct RoleId(String);

impl HaveAtt<RoleArn> for Role {}
impl HaveAtt<RoleId> for Role {}

impl Attribute for RoleArn {
    fn name() -> &'static str {
        "Arn"
    }
}

impl Attribute for RoleId {
    fn name() -> &'static str {
        "RoleId"
    }
}

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

    #[test]
    fn test_role1() {
        let role_id = LogicalId::try_from("role-id").unwrap();
        let statement = Statement::allow()
            .action(vec![Box::new(action::sts::AssumeRole)])
            .principal(Principal::from(ServicePrincipal::Lambda));
        let assume_role_policy_document = AssumeRolePolicyDocument::latest(vec![statement]);
        let role = Role::new(assume_role_policy_document, role_id);
        let mut rhs = r#"{
            "Type": "AWS::IAM::Role",
            "Properties": {
                "AssumeRolePolicyDocument": {
                    "Version": "2012-10-17",
                    "Statement": [
                        {
                            "Effect": "Allow",
                            "Action": [ "sts:AssumeRole" ],
                            "Principal": {
                                "Service": [ "lambda.amazonaws.com" ]
                            }
                        }
                    ]
                }
            }}"#
        .to_string();
        rhs.retain(|c| c != ' ' && c != '\n');
        assert_eq!(serde_json::to_string(&role).unwrap(), rhs);
    }
}