stratosphere_core/
template.rs

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}