rusty_cdk_core/stack/
builder.rs1use crate::stack::{Resource, Stack};
2use std::error::Error;
3use std::fmt::{Debug, Display, Formatter};
4use crate::shared::Id;
5
6#[derive(Debug)]
7pub enum StackBuilderError {
8 MissingPermissionsForRole(Vec<String>),
9 DuplicateIds(Vec<String>),
10 DuplicateResourceIds(Vec<String>),
11}
12
13impl Display for StackBuilderError {
14 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
15 match self {
16 StackBuilderError::MissingPermissionsForRole(info) => {
17 let gathered_info = info.join(";");
18 f.write_fmt(format_args!(
19 "one or more roles seem to be missing permission to access services: `{}`?",
20 gathered_info
21 ))
22 }
23 StackBuilderError::DuplicateIds(info) => {
24 let gathered_info = info.join(";");
25 f.write_fmt(format_args!(
26 "ids should be unique, but the following duplicates were detected: `{}`",
27 gathered_info
28 ))
29 }
30 StackBuilderError::DuplicateResourceIds(info) => {
31 let gathered_info = info.join(";");
32 f.write_fmt(format_args!(
33 "duplicate resource ids were detected (`{}`), rerunning this command should generate new ones",
34 gathered_info
35 ))
36 }
37 }
38 }
39}
40
41impl Error for StackBuilderError {}
42
43pub struct StackBuilder {
71 resources: Vec<Resource>,
72 tags: Vec<(String, String)>,
73}
74
75impl Default for StackBuilder {
76 fn default() -> Self {
77 Self::new()
78 }
79}
80
81impl StackBuilder {
82 pub fn new() -> Self {
83 Self {
84 resources: vec![],
85 tags: vec![],
86 }
87 }
88
89 pub fn add_resource<T: Into<Resource>>(&mut self, resource: T) {
90 let resource = resource.into();
91 self.resources.push(resource);
92 }
93
94 pub fn add_tag<T: Into<String>>(mut self, key: T, value: T) -> Self {
95 self.tags.push((key.into(), value.into()));
96 self
97 }
98
99 pub(crate) fn get_resource(&mut self, id: &Id) -> Option<&mut Resource> {
100 self.resources.iter_mut()
101 .find(|v| &v.get_id() == id)
102 }
103
104 pub fn build(self) -> Result<Stack, StackBuilderError> {
110 let (ids, resource_ids) = self.resources
111 .iter()
112 .map(|r| (r.get_id().to_string(), r.get_resource_id().to_string()))
113 .collect::<(Vec<_>, Vec<_>)>();
114
115 let duplicate_ids = Self::check_for_duplicate_ids(ids);
116 let resource_ids = Self::check_for_duplicate_ids(resource_ids);
117
118 if !duplicate_ids.is_empty() {
119 return Err(StackBuilderError::DuplicateIds(
120 duplicate_ids,
121 ));
122 }
123 if !resource_ids.is_empty() {
124 return Err(StackBuilderError::DuplicateResourceIds(
125 resource_ids,
126 ));
127 }
128
129 let roles_with_potentially_missing_services: Vec<_> = self.check_for_roles_with_missing_permissions();
130
131 if !roles_with_potentially_missing_services.is_empty() {
132 return Err(StackBuilderError::MissingPermissionsForRole(
133 roles_with_potentially_missing_services,
134 ));
135 }
136
137
138 let metadata = self
139 .resources
140 .iter()
141 .map(|r| (r.get_id().to_string(), r.get_resource_id().to_string()))
142 .collect();
143
144
145
146 let resources = self.resources.into_iter().map(|r| (r.get_resource_id().to_string(), r)).collect();
147 Ok(Stack {
148 resource_ids_to_replace: vec![],
149 tags: self.tags,
150 resources,
151 metadata,
152 })
153 }
154
155 fn check_for_roles_with_missing_permissions(&self) -> Vec<String> {
156 self.resources
157 .iter()
158 .filter_map(|r| match r {
159 Resource::Role(r) => {
160 if !r.potentially_missing_services.is_empty() {
161 Some(format!("{}: {}", r.resource_id, r.potentially_missing_services.join(",")))
162 } else {
163 None
164 }
165 }
166 _ => None,
167 })
168 .collect()
169 }
170
171 fn check_for_duplicate_ids(ids: Vec<String>) -> Vec<String> {
172 let results = ids.into_iter().fold((vec![], vec![]), |(mut all, mut duplicates), curr| {
173 if all.contains(&curr) && !duplicates.contains(&curr) {
174 duplicates.push(curr.clone());
175 }
176 all.push(curr);
177 (all, duplicates)
178 });
179 results.1
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use crate::stack::StackBuilder;
186
187 #[test]
188 fn test_check_for_duplicate_ids() {
189 let duplicates = StackBuilder::check_for_duplicate_ids(vec!["bucket".to_string(), "bucket".to_string(), "topic".to_string(), "queue".to_string(), "bucket".to_string(), "table".to_string(), "topic".to_string()]);
190
191 assert_eq!(duplicates, vec!["bucket", "topic"])
192 }
193}