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