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 #[must_use]
114 pub fn as_str(&self) -> &str {
115 &self.0
116 }
117}
118
119#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize)]
120pub struct MapName(pub String);
121
122impl std::fmt::Display for MapName {
123 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
124 write!(formatter, "{}", self.0)
125 }
126}
127
128impl From<&str> for MapName {
129 fn from(value: &str) -> Self {
130 Self(value.into())
131 }
132}
133
134impl From<&MapName> for MapName {
135 fn from(value: &MapName) -> Self {
136 value.clone()
137 }
138}
139
140impl From<MapName> for String {
141 fn from(value: MapName) -> Self {
142 value.0
143 }
144}
145
146impl From<&MapName> for String {
147 fn from(value: &MapName) -> Self {
148 value.0.clone()
149 }
150}
151
152impl MapName {
153 #[must_use]
154 pub fn as_str(&self) -> &str {
155 &self.0
156 }
157}
158
159pub type Mapping =
160 std::collections::BTreeMap<String, std::collections::BTreeMap<String, serde_json::Value>>;
161
162#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
163pub struct Output {
164 #[serde(rename = "Description")]
165 pub description: value::ExpString,
166 #[serde(rename = "Value")]
167 pub value: value::ExpString,
168 #[serde(rename = "Export", skip_serializing_if = "Option::is_none")]
169 pub export: Option<OutputExport>,
170}
171
172#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
173pub struct OutputExport {
174 #[serde(rename = "Name")]
175 pub name: value::ExpString,
176 #[serde(rename = "Value", skip_serializing_if = "Option::is_none")]
177 pub value: Option<value::ExpString>,
178}
179
180#[derive(Debug, PartialEq, serde::Serialize)]
181pub struct Template<'a> {
182 #[serde(rename = "AWSTemplateFormatVersion")]
183 version: Version,
184 #[serde(rename = "Description", skip_serializing_if = "Option::is_none")]
185 description: Option<String>,
186 #[serde(
187 rename = "Mappings",
188 skip_serializing_if = "std::collections::BTreeMap::is_empty"
189 )]
190 mappings: std::collections::BTreeMap<MapName, Mapping>,
191 #[serde(
192 rename = "Outputs",
193 skip_serializing_if = "std::collections::BTreeMap::is_empty"
194 )]
195 outputs: std::collections::BTreeMap<OutputKey, Output>,
196 #[serde(
197 rename = "Parameters",
198 skip_serializing_if = "std::collections::BTreeMap::is_empty"
199 )]
200 parameters: std::collections::BTreeMap<ParameterKey, Parameter>,
201 #[serde(rename = "Resources")]
202 resources: std::collections::BTreeMap<LogicalResourceName, Resource<'a>>,
203}
204
205impl Default for Template<'_> {
206 fn default() -> Self {
207 Self::new()
208 }
209}
210
211impl Template<'_> {
212 #[must_use]
213 pub fn new() -> Self {
214 Self {
215 description: None,
216 mappings: std::collections::BTreeMap::new(),
217 outputs: std::collections::BTreeMap::new(),
218 parameters: std::collections::BTreeMap::new(),
219 resources: std::collections::BTreeMap::new(),
220 version: Version::V2010_09_09,
221 }
222 }
223
224 pub fn build(action: fn(&mut Self) -> ()) -> Self {
225 let mut template = Self::new();
226
227 action(&mut template);
228
229 template
230 }
231
232 pub fn resource<R: ToResource>(
233 &mut self,
234 logical_resource_name: impl Into<LogicalResourceName>,
235 resource: R,
236 ) -> LogicalResourceName {
237 let logical_resource_name = logical_resource_name.into();
238
239 let resource = Resource {
240 resource_type_identifier: R::RESOURCE_TYPE_NAME,
241 resource_properties: resource.to_resource_properties(),
242 };
243
244 match self
245 .resources
246 .insert(logical_resource_name.clone(), resource)
247 {
248 None => logical_resource_name,
249 Some(_existing) => {
250 panic!("Logical resource with name: {logical_resource_name} already exists")
251 }
252 }
253 }
254
255 pub fn parameter(
256 &mut self,
257 parameter_key: impl Into<ParameterKey>,
258 parameter: Parameter,
259 ) -> ParameterKey {
260 let parameter_key = parameter_key.into();
261
262 if let Some(_existing) = self.parameters.insert(parameter_key.clone(), parameter) {
263 panic!("Parameter key: {parameter_key} already exists")
264 }
265
266 parameter_key
267 }
268
269 pub fn parameter_(
270 mut self,
271 parameter_key: impl Into<ParameterKey>,
272 parameter: Parameter,
273 ) -> Self {
274 self.parameter(parameter_key, parameter);
275 self
276 }
277
278 pub fn mapping(&mut self, map_name: impl Into<MapName>, mapping: Mapping) -> MapName {
279 let map_name = map_name.into();
280
281 if let Some(_existing) = self.mappings.insert(map_name.clone(), mapping) {
282 panic!("Mapping with name: {map_name} already exists")
283 }
284
285 map_name
286 }
287
288 pub fn mapping_(mut self, map_name: impl Into<MapName>, mapping: Mapping) -> Self {
289 self.mapping(map_name, mapping);
290 self
291 }
292
293 pub fn output(&mut self, output_key: impl Into<OutputKey>, output: Output) -> OutputKey {
294 let output_key = output_key.into();
295
296 if let Some(_old) = self.outputs.insert(output_key.clone(), output) {
297 panic!("Output with name: {output_key} already exists")
298 }
299
300 output_key
301 }
302
303 pub fn output_(mut self, output_key: impl Into<OutputKey>, output: Output) -> Self {
304 self.output(output_key, output);
305 self
306 }
307
308 pub fn output_export(
309 &mut self,
310 output_key: impl Into<OutputKey>,
311 description: &str,
312 value: impl Into<value::ExpString>,
313 ) -> OutputKey {
314 let output_key = output_key.into();
315
316 self.output(
317 output_key.clone(),
318 Output {
319 description: description.into(),
320 value: value.into(),
321 export: Some(OutputExport {
322 name: value::join(":", [value::AWS_STACK_NAME, output_key.as_str().into()]),
323 value: None,
324 }),
325 },
326 )
327 }
328
329 pub fn resource_<R: ToResource>(
330 mut self,
331 logical_resource_name: impl Into<LogicalResourceName>,
332 resource: R,
333 ) -> Self {
334 self.resource(logical_resource_name, resource);
335 self
336 }
337
338 #[must_use]
339 pub fn render_json_pretty(&self) -> String {
340 let mut string = serde_json::to_string_pretty(&self).unwrap();
341 string.push('\n');
342 string
343 }
344
345 #[must_use]
346 pub fn render_json(&self) -> String {
347 serde_json::to_string(&self).unwrap()
348 }
349
350 #[must_use]
351 pub fn parameter_keys(&self) -> std::collections::BTreeSet<ParameterKey> {
352 self.parameters.keys().cloned().collect()
353 }
354}