1use crate::resource_specification::ResourceTypeName;
2use crate::value;
3
4#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize)]
5pub struct LogicalResourceName(pub String);
6
7impl From<&str> for LogicalResourceName {
8 fn from(value: &str) -> Self {
9 Self(value.to_string())
10 }
11}
12
13impl From<&LogicalResourceName> for LogicalResourceName {
14 fn from(value: &Self) -> Self {
15 value.clone()
16 }
17}
18
19impl std::fmt::Display for LogicalResourceName {
20 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
21 write!(formatter, "{}", self.0)
22 }
23}
24
25pub type ResourceProperties = serde_json::Map<String, serde_json::Value>;
26
27pub trait ToResource {
28 const RESOURCE_TYPE_NAME: ResourceTypeName<'static>;
29 fn to_resource_properties(&self) -> ResourceProperties;
30}
31
32#[derive(Debug, PartialEq, serde::Serialize)]
33pub struct Resource<'a> {
34 #[serde(rename = "Type")]
35 resource_type_identifier: ResourceTypeName<'a>,
36 #[serde(rename = "Properties")]
37 resource_properties: ResourceProperties,
38}
39
40#[derive(Debug, Eq, PartialEq)]
41pub enum Version {
42 V2010_09_09,
43}
44
45impl serde::Serialize for Version {
46 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
47 match self {
48 Version::V2010_09_09 => serializer.serialize_str("2010-09-09"),
49 }
50 }
51}
52
53#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize)]
54pub struct ParameterKey(pub String);
55
56impl std::fmt::Display for ParameterKey {
57 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
58 write!(formatter, "{}", self.0)
59 }
60}
61
62impl From<&str> for ParameterKey {
63 fn from(value: &str) -> Self {
64 Self(value.into())
65 }
66}
67
68impl From<ParameterKey> for value::ExpString {
69 fn from(value: ParameterKey) -> Self {
70 (&value).into()
71 }
72}
73
74impl From<&ParameterKey> for value::ExpString {
75 fn from(value: &ParameterKey) -> Self {
76 Self::Ref(value.0.as_str().into())
77 }
78}
79
80#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
81pub enum ParameterType {
82 String,
83}
84
85#[derive(Clone, Debug, PartialEq, serde::Serialize)]
86pub struct Parameter {
87 #[serde(rename = "Description")]
88 pub description: Option<String>,
89 #[serde(rename = "Type")]
90 pub r#type: ParameterType,
91 #[serde(rename = "AllowedPattern", skip_serializing_if = "Option::is_none")]
92 pub allowed_pattern: Option<String>,
93}
94
95pub type ParameterKeys = std::collections::BTreeSet<ParameterKey>;
96
97#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize)]
98pub struct OutputKey(pub String);
99
100impl std::fmt::Display for OutputKey {
101 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
102 write!(formatter, "{}", self.0)
103 }
104}
105
106impl From<&str> for OutputKey {
107 fn from(value: &str) -> Self {
108 Self(value.into())
109 }
110}
111
112impl OutputKey {
113 pub fn as_str(&self) -> &str {
114 &self.0
115 }
116}
117
118#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize)]
119pub struct MapName(pub String);
120
121impl std::fmt::Display for MapName {
122 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
123 write!(formatter, "{}", self.0)
124 }
125}
126
127impl From<&str> for MapName {
128 fn from(value: &str) -> Self {
129 Self(value.into())
130 }
131}
132
133impl From<&MapName> for MapName {
134 fn from(value: &MapName) -> Self {
135 value.clone()
136 }
137}
138
139impl From<MapName> for String {
140 fn from(value: MapName) -> Self {
141 value.0
142 }
143}
144
145impl From<&MapName> for String {
146 fn from(value: &MapName) -> Self {
147 value.0.clone()
148 }
149}
150
151impl MapName {
152 pub fn as_str(&self) -> &str {
153 &self.0
154 }
155}
156
157pub type Mapping =
158 std::collections::BTreeMap<String, std::collections::BTreeMap<String, serde_json::Value>>;
159
160#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
161pub struct Output {
162 #[serde(rename = "Description")]
163 pub description: value::ExpString,
164 #[serde(rename = "Value")]
165 pub value: value::ExpString,
166 #[serde(rename = "Export", skip_serializing_if = "Option::is_none")]
167 pub export: Option<OutputExport>,
168}
169
170#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
171pub struct OutputExport {
172 #[serde(rename = "Name")]
173 pub name: value::ExpString,
174 #[serde(rename = "Value", skip_serializing_if = "Option::is_none")]
175 pub value: Option<value::ExpString>,
176}
177
178#[derive(Debug, PartialEq, serde::Serialize)]
179pub struct Template<'a> {
180 #[serde(rename = "AWSTemplateFormatVersion")]
181 version: Version,
182 #[serde(rename = "Description", skip_serializing_if = "Option::is_none")]
183 description: Option<String>,
184 #[serde(
185 rename = "Mappings",
186 skip_serializing_if = "std::collections::BTreeMap::is_empty"
187 )]
188 mappings: std::collections::BTreeMap<MapName, Mapping>,
189 #[serde(
190 rename = "Outputs",
191 skip_serializing_if = "std::collections::BTreeMap::is_empty"
192 )]
193 outputs: std::collections::BTreeMap<OutputKey, Output>,
194 #[serde(
195 rename = "Parameters",
196 skip_serializing_if = "std::collections::BTreeMap::is_empty"
197 )]
198 parameters: std::collections::BTreeMap<ParameterKey, Parameter>,
199 #[serde(rename = "Resources")]
200 resources: std::collections::BTreeMap<LogicalResourceName, Resource<'a>>,
201}
202
203impl Default for Template<'_> {
204 fn default() -> Self {
205 Self::new()
206 }
207}
208
209impl Template<'_> {
210 pub fn new() -> Self {
211 Self {
212 description: None,
213 mappings: std::collections::BTreeMap::new(),
214 outputs: std::collections::BTreeMap::new(),
215 parameters: std::collections::BTreeMap::new(),
216 resources: std::collections::BTreeMap::new(),
217 version: Version::V2010_09_09,
218 }
219 }
220
221 pub fn build(action: fn(&mut Self) -> ()) -> Self {
222 let mut template = Self::new();
223
224 action(&mut template);
225
226 template
227 }
228
229 pub fn resource<R: ToResource>(
230 &mut self,
231 logical_resource_name: impl Into<LogicalResourceName>,
232 resource: R,
233 ) -> LogicalResourceName {
234 let logical_resource_name = logical_resource_name.into();
235
236 let resource = Resource {
237 resource_type_identifier: R::RESOURCE_TYPE_NAME,
238 resource_properties: resource.to_resource_properties(),
239 };
240
241 match self
242 .resources
243 .insert(logical_resource_name.clone(), resource)
244 {
245 None => logical_resource_name,
246 Some(_existing) => {
247 panic!("Logical resource with name: {logical_resource_name} already exists")
248 }
249 }
250 }
251
252 pub fn parameter(
253 &mut self,
254 parameter_key: impl Into<ParameterKey>,
255 parameter: Parameter,
256 ) -> ParameterKey {
257 let parameter_key = parameter_key.into();
258
259 if let Some(_existing) = self.parameters.insert(parameter_key.clone(), parameter) {
260 panic!("Parameter key: {parameter_key} already exists")
261 }
262
263 parameter_key
264 }
265
266 pub fn parameter_(
267 mut self,
268 parameter_key: impl Into<ParameterKey>,
269 parameter: Parameter,
270 ) -> Self {
271 self.parameter(parameter_key, parameter);
272 self
273 }
274
275 pub fn mapping(&mut self, map_name: impl Into<MapName>, mapping: Mapping) -> MapName {
276 let map_name = map_name.into();
277
278 if let Some(_existing) = self.mappings.insert(map_name.clone(), mapping) {
279 panic!("Mapping with name: {map_name} already exists")
280 }
281
282 map_name
283 }
284
285 pub fn mapping_(mut self, map_name: impl Into<MapName>, mapping: Mapping) -> Self {
286 self.mapping(map_name, mapping);
287 self
288 }
289
290 pub fn output(&mut self, output_key: impl Into<OutputKey>, output: Output) -> OutputKey {
291 let output_key = output_key.into();
292
293 if let Some(_old) = self.outputs.insert(output_key.clone(), output) {
294 panic!("Output with name: {} already exists", output_key)
295 }
296
297 output_key
298 }
299
300 pub fn output_(mut self, output_key: impl Into<OutputKey>, output: Output) -> Self {
301 self.output(output_key, output);
302 self
303 }
304
305 pub fn output_export(
306 &mut self,
307 output_key: impl Into<OutputKey>,
308 description: &str,
309 value: impl Into<value::ExpString>,
310 ) -> OutputKey {
311 let output_key = output_key.into();
312
313 self.output(
314 output_key.clone(),
315 Output {
316 description: description.into(),
317 value: value.into(),
318 export: Some(OutputExport {
319 name: value::join(":", [value::AWS_STACK_NAME, output_key.as_str().into()]),
320 value: None,
321 }),
322 },
323 )
324 }
325
326 pub fn resource_<R: ToResource>(
327 mut self,
328 logical_resource_name: impl Into<LogicalResourceName>,
329 resource: R,
330 ) -> Self {
331 self.resource(logical_resource_name, resource);
332 self
333 }
334
335 pub fn render_json_pretty(&self) -> String {
336 let mut string = serde_json::to_string_pretty(&self).unwrap();
337 string.push('\n');
338 string
339 }
340
341 pub fn render_json(&self) -> String {
342 serde_json::to_string(&self).unwrap()
343 }
344
345 pub fn parameter_keys(&self) -> std::collections::BTreeSet<ParameterKey> {
346 self.parameters.keys().cloned().collect()
347 }
348}