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> {
108 let (ids, resource_ids) = self.resources
109 .iter()
110 .map(|r| (r.get_id().to_string(), r.get_resource_id().to_string()))
111 .collect::<(Vec<_>, Vec<_>)>();
112
113 let duplicate_ids = Self::check_for_duplicate_ids(ids);
114 let resource_ids = Self::check_for_duplicate_ids(resource_ids);
115
116 if !duplicate_ids.is_empty() {
117 return Err(StackBuilderError::DuplicateIds(
118 duplicate_ids,
119 ));
120 }
121 if !resource_ids.is_empty() {
122 return Err(StackBuilderError::DuplicateResourceIds(
123 resource_ids,
124 ));
125 }
126
127 let roles_with_potentially_missing_services: Vec<_> = self.check_for_roles_with_missing_permissions();
128
129 if !roles_with_potentially_missing_services.is_empty() {
130 return Err(StackBuilderError::MissingPermissionsForRole(
131 roles_with_potentially_missing_services,
132 ));
133 }
134
135
136 let metadata = self
137 .resources
138 .iter()
139 .map(|r| (r.get_id().to_string(), r.get_resource_id().to_string()))
140 .collect();
141
142
143
144 let resources = self.resources.into_iter().map(|r| (r.get_resource_id().to_string(), r)).collect();
145 Ok(Stack {
146 resource_ids_to_replace: vec![],
147 tags: self.tags,
148 resources,
149 metadata,
150 })
151 }
152
153 fn check_for_roles_with_missing_permissions(&self) -> Vec<String> {
154 self.resources
155 .iter()
156 .filter_map(|r| match r {
157 Resource::Role(r) => {
158 if !r.potentially_missing_services.is_empty() {
159 Some(format!("{}: {}", r.resource_id, r.potentially_missing_services.join(",")))
160 } else {
161 None
162 }
163 }
164 _ => None,
165 })
166 .collect()
167 }
168
169 fn check_for_duplicate_ids(ids: Vec<String>) -> Vec<String> {
170 let results = ids.into_iter().fold((vec![], vec![]), |(mut all, mut duplicates), curr| {
171 if all.contains(&curr) && !duplicates.contains(&curr) {
172 duplicates.push(curr.clone());
173 }
174 all.push(curr);
175 (all, duplicates)
176 });
177 results.1
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use crate::stack::StackBuilder;
184
185 #[test]
186 fn test_check_for_duplicate_ids() {
187 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()]);
188
189 assert_eq!(duplicates, vec!["bucket", "topic"])
190 }
191}