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    #[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}