rusty_cdk_core/stack/
builder.rs

1use crate::stack::{Resource, Stack};
2use std::error::Error;
3use std::fmt::{Debug, Display, Formatter};
4
5#[derive(Debug)]
6pub enum StackBuilderError {
7    MissingPermissionsForRole(Vec<String>),
8}
9
10impl Display for StackBuilderError {
11    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
12        match self {
13            StackBuilderError::MissingPermissionsForRole(info) => {
14                let gathered_info = info.join(";");
15                f.write_fmt(format_args!("one or more roles seem to be missing permission to access services: `{}`?", gathered_info))
16            }
17        }
18    }
19}
20
21impl Error for StackBuilderError {}
22
23/// Builder for CloudFormation stacks.
24///
25/// Collects resources and manages their relationships.
26/// Might validate whether IAM roles are missing permissions for AWS services they need to access, based on Cargo.toml dependencies.
27///
28/// # Example
29///
30/// ```rust
31/// use rusty_cdk_core::stack::StackBuilder;
32/// use rusty_cdk_core::sqs::QueueBuilder;
33/// use rusty_cdk_core::wrappers::*;
34///
35/// let mut stack_builder = StackBuilder::new();
36///
37/// // Add resources to the stack
38/// let queue = QueueBuilder::new("my-queue")
39///     .standard_queue()
40///     .build(&mut stack_builder);
41///
42/// // Add tags to the stack
43/// stack_builder = stack_builder
44///     .add_tag("Environment", "Production")
45///     .add_tag("Owner", "Team");
46///
47/// // Build the stack
48/// let stack = stack_builder.build().expect("Stack to build successfully");
49/// ```
50pub struct StackBuilder {
51    resources: Vec<Resource>,
52    tags: Vec<(String, String)>,
53}
54
55impl Default for StackBuilder {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61impl StackBuilder {
62    pub fn new() -> Self {
63        Self { resources: vec![], tags: vec![] }
64    }
65
66    pub fn add_resource<T: Into<Resource>>(&mut self, resource: T) {
67        let resource = resource.into();
68        self.resources.push(resource);
69    }
70
71    pub fn add_tag<T: Into<String>>(mut self, key: T, value: T) -> Self {
72        self.tags.push((key.into(), value.into()));
73        self
74    }
75
76    /// Builds the stack and validates all resources.
77    ///
78    /// Might return an error if any IAM roles are missing permissions for AWS services they need to access, based on Cargo.toml dependencies.
79    pub fn build(self) -> Result<Stack, StackBuilderError> {
80        let metadata = self
81            .resources
82            .iter()
83            .map(|r| (r.get_id().to_string(), r.get_resource_id().to_string()))
84            .collect();
85        
86        let roles_with_potentially_missing_services: Vec<_> = self.resources.iter().filter_map(|r| {
87            match r {
88                Resource::Role(r) => {
89                    if !r.potentially_missing_services.is_empty() {
90                        Some(format!("{}: {}", r.resource_id, r.potentially_missing_services.join(",")))
91                    } else {
92                        None
93                    }
94                },
95                _ => None
96            }
97        }).collect();
98        
99        if !roles_with_potentially_missing_services.is_empty() {
100            return Err(StackBuilderError::MissingPermissionsForRole(roles_with_potentially_missing_services))
101        }
102
103        let resources = self.resources.into_iter().map(|r| (r.get_resource_id().to_string(), r)).collect();
104        Ok(Stack {
105            to_replace: vec![],
106            tags: self.tags,
107            resources,
108            metadata,
109        })
110    }
111}