stack_deploy/types.rs
1pub use stratosphere::template::OutputKey;
2
3const INLINE_TEMPLATE_LIMIT_BYTES: usize = 51200;
4
5#[derive(Clone, Debug, Eq, PartialEq)]
6pub struct StackName(pub String);
7
8impl From<&str> for StackName {
9 fn from(value: &str) -> Self {
10 Self(value.into())
11 }
12}
13
14#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
15pub struct TemplateName(pub String);
16
17impl From<&str> for TemplateName {
18 fn from(value: &str) -> Self {
19 Self(value.into())
20 }
21}
22
23impl TemplateName {
24 #[must_use]
25 pub fn as_str(&self) -> &str {
26 self.0.as_str()
27 }
28}
29
30pub trait StackIdentifier: std::fmt::Debug + std::fmt::Display {
31 fn as_str(&self) -> &str;
32}
33
34impl std::str::FromStr for StackName {
35 type Err = &'static str;
36
37 fn from_str(input: &str) -> Result<StackName, Self::Err> {
38 Ok(Self(String::from(input)))
39 }
40}
41
42impl AsRef<str> for StackName {
43 fn as_ref(&self) -> &str {
44 &self.0
45 }
46}
47
48impl From<&StackName> for String {
49 fn from(value: &StackName) -> Self {
50 value.0.clone()
51 }
52}
53
54impl StackIdentifier for StackName {
55 fn as_str(&self) -> &str {
56 &self.0
57 }
58}
59
60impl StackIdentifier for &StackName {
61 fn as_str(&self) -> &str {
62 &self.0
63 }
64}
65
66impl std::fmt::Display for StackName {
67 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
68 write!(formatter, "{}", self.0)
69 }
70}
71
72#[derive(Debug, PartialEq)]
73pub struct StackId(pub String);
74
75impl From<&StackId> for String {
76 fn from(value: &StackId) -> Self {
77 value.0.clone()
78 }
79}
80
81impl StackIdentifier for &StackId {
82 fn as_str(&self) -> &str {
83 &self.0
84 }
85}
86
87impl std::fmt::Display for StackId {
88 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
89 write!(formatter, "{}", self.0)
90 }
91}
92
93#[derive(Debug, PartialEq)]
94pub struct ClientRequestToken(pub String);
95
96impl ClientRequestToken {
97 #[must_use]
98 pub fn generate() -> Self {
99 Self(uuid::Uuid::new_v4().to_string())
100 }
101}
102
103impl From<&ClientRequestToken> for String {
104 fn from(value: &ClientRequestToken) -> Self {
105 value.0.clone()
106 }
107}
108
109#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize)]
110pub struct ParameterValue(pub String);
111
112pub use stratosphere::template::{ParameterKey, ParameterKeys};
113
114#[derive(Clone, Debug, Eq, PartialEq)]
115pub struct Parameter {
116 pub key: ParameterKey,
117 pub value: ParameterValue,
118}
119
120impl std::str::FromStr for Parameter {
121 type Err = &'static str;
122
123 /// Parse parameter from string
124 ///
125 /// ### Examples
126 ///
127 /// Success, both present
128 ///
129 /// ```
130 /// # use stack_deploy::types::*;
131 /// assert_eq!(
132 /// Ok(Parameter {
133 /// key: ParameterKey(String::from("some-key")),
134 /// value: ParameterValue(String::from("some-value")),
135 /// }),
136 /// std::str::FromStr::from_str("some-key:some-value")
137 /// )
138 /// ```
139 ///
140 /// Success, empty value
141 ///
142 /// ```
143 /// # use stack_deploy::types::*;
144 /// assert_eq!(
145 /// Ok(Parameter {
146 /// key: ParameterKey(String::from("some-key")),
147 /// value: ParameterValue(String::from("")),
148 /// }),
149 /// std::str::FromStr::from_str("some-key:")
150 /// )
151 /// ```
152 ///
153 /// Missing delimiter
154 ///
155 /// ```
156 /// # use stack_deploy::types::*;
157 /// assert_eq!(
158 /// Err("missing ':' in input"),
159 /// <Parameter as std::str::FromStr>::from_str("some-key")
160 /// )
161 /// ```
162 ///
163 /// Empty key
164 ///
165 /// ```
166 /// # use stack_deploy::types::*;
167 /// assert_eq!(
168 /// Err("parameter key cannot be empty"),
169 /// <Parameter as std::str::FromStr>::from_str(":value")
170 /// )
171 /// ```
172 ///
173 /// Overlong key
174 ///
175 /// ```
176 /// # use stack_deploy::types::*;
177 /// assert_eq!(
178 /// Err("parameter key cannot be longer than 255 bytes"),
179 /// <Parameter as std::str::FromStr>::from_str(&("a".repeat(256) + ":"))
180 /// )
181 /// ```
182 ///
183 /// Overlong value
184 ///
185 /// ```
186 /// # use stack_deploy::types::*;
187 /// assert_eq!(
188 /// Err("parameter value cannot be longer than 4096 bytes"),
189 /// <Parameter as std::str::FromStr>::from_str(&("a:".to_owned() + &"b".repeat(4097)))
190 /// )
191 /// ```
192 fn from_str(input: &str) -> Result<Parameter, Self::Err> {
193 match input.split_once(':') {
194 None => Err("missing ':' in input"),
195 Some((key, value)) => match (key.len(), value.len()) {
196 (0, _) => Err("parameter key cannot be empty"),
197 (key_len, _) if key_len > 255 => {
198 Err("parameter key cannot be longer than 255 bytes")
199 }
200 (_, value_len) if value_len > 4096 => {
201 Err("parameter value cannot be longer than 4096 bytes")
202 }
203 _other => Ok(Parameter {
204 key: ParameterKey(String::from(key)),
205 value: ParameterValue(String::from(value)),
206 }),
207 },
208 }
209 }
210}
211
212#[derive(Debug, PartialEq)]
213pub struct ParameterMap(pub std::collections::BTreeMap<ParameterKey, ParameterValue>);
214
215impl Default for ParameterMap {
216 fn default() -> Self {
217 Self::new()
218 }
219}
220
221impl ParameterMap {
222 #[must_use]
223 pub fn new() -> Self {
224 Self(std::collections::BTreeMap::new())
225 }
226
227 /// Parse a parameter vector into a parameter map
228 ///
229 /// ### Examples
230 ///
231 /// No duplicates
232 ///
233 /// ```
234 /// # use stack_deploy::types::*;
235 ///
236 /// let parameters = vec![
237 /// Parameter {
238 /// key: ParameterKey("key-a".to_string()),
239 /// value: ParameterValue("value-a".to_string()),
240 /// },
241 /// Parameter {
242 /// key: ParameterKey("key-b".to_string()),
243 /// value: ParameterValue("value-b".to_string()),
244 /// },
245 /// ];
246 ///
247 /// assert_eq!(
248 /// Ok(ParameterMap(std::collections::BTreeMap::from([
249 /// (
250 /// ParameterKey(String::from("key-a")),
251 /// ParameterValue(String::from("value-a"))
252 /// ),
253 /// (
254 /// ParameterKey(String::from("key-b")),
255 /// ParameterValue(String::from("value-b"))
256 /// ),
257 /// ]))),
258 /// ParameterMap::parse(¶meters)
259 /// );
260 /// ```
261 ///
262 /// Present duplicate, errors on first duplicate
263 ///
264 /// ```
265 /// # use stack_deploy::types::*;
266 ///
267 /// let parameters = vec![
268 /// Parameter {
269 /// key: ParameterKey("key-a".to_string()),
270 /// value: ParameterValue("value-a".to_string()),
271 /// },
272 /// Parameter {
273 /// key: ParameterKey("key-a".to_string()),
274 /// value: ParameterValue("value-a".to_string()),
275 /// },
276 /// Parameter {
277 /// key: ParameterKey("key-b".to_string()),
278 /// value: ParameterValue("value-b".to_string()),
279 /// },
280 /// Parameter {
281 /// key: ParameterKey("key-b".to_string()),
282 /// value: ParameterValue("value-b".to_string()),
283 /// },
284 /// ];
285 ///
286 /// assert_eq!(
287 /// Err(String::from(
288 /// "Parameter key: key-a is present multiple times"
289 /// )),
290 /// ParameterMap::parse(¶meters)
291 /// );
292 /// ```
293 pub fn parse<'a>(
294 values: impl IntoIterator<Item = &'a Parameter>,
295 ) -> Result<ParameterMap, String> {
296 let mut map = std::collections::BTreeMap::new();
297
298 for parameter in values {
299 if map.contains_key(¶meter.key) {
300 return Err(format!(
301 "Parameter key: {} is present multiple times",
302 parameter.key.0
303 ));
304 } else {
305 map.insert(parameter.key.clone(), parameter.value.clone());
306 }
307 }
308
309 Ok(ParameterMap(map))
310 }
311
312 /// Merge parameter map into receiver
313 ///
314 /// ### Examples
315 ///
316 /// ```
317 /// # use stack_deploy::types::*;
318 /// let original = ParameterMap(std::collections::BTreeMap::from([
319 /// (
320 /// ParameterKey(String::from("key-a")),
321 /// ParameterValue(String::from("value-a")),
322 /// ),
323 /// (
324 /// ParameterKey(String::from("key-b")),
325 /// ParameterValue(String::from("value-b1")),
326 /// ),
327 /// ]));
328 ///
329 /// let extra = ParameterMap(std::collections::BTreeMap::from([
330 /// (
331 /// ParameterKey(String::from("key-b")),
332 /// ParameterValue(String::from("value-b2")),
333 /// ),
334 /// (
335 /// ParameterKey(String::from("key-c")),
336 /// ParameterValue(String::from("value-c")),
337 /// ),
338 /// ]));
339 ///
340 /// assert_eq!(
341 /// ParameterMap(std::collections::BTreeMap::from([
342 /// (
343 /// ParameterKey(String::from("key-a")),
344 /// ParameterValue(String::from("value-a"))
345 /// ),
346 /// (
347 /// ParameterKey(String::from("key-b")),
348 /// ParameterValue(String::from("value-b2"))
349 /// ),
350 /// (
351 /// ParameterKey(String::from("key-c")),
352 /// ParameterValue(String::from("value-c"))
353 /// ),
354 /// ])),
355 /// original.merge(&extra)
356 /// )
357 /// ```
358 #[must_use]
359 pub fn merge(&self, other: &Self) -> Self {
360 let mut self_new = self.0.clone();
361
362 self_new.append(&mut other.0.clone());
363
364 ParameterMap(self_new)
365 }
366
367 /// To create parameters
368 ///
369 /// ### Examples
370 ///
371 /// ```
372 /// # use stack_deploy::types::*;
373 /// assert_eq!(
374 /// vec![
375 /// aws_sdk_cloudformation::types::Parameter::builder()
376 /// .parameter_key("key-a")
377 /// .parameter_value("value-a")
378 /// .build(),
379 /// aws_sdk_cloudformation::types::Parameter::builder()
380 /// .parameter_key("key-b")
381 /// .parameter_value("value-b")
382 /// .build(),
383 /// ],
384 /// ParameterMap(std::collections::BTreeMap::from([
385 /// (
386 /// ParameterKey(String::from("key-a")),
387 /// ParameterValue(String::from("value-a"))
388 /// ),
389 /// (
390 /// ParameterKey(String::from("key-b")),
391 /// ParameterValue(String::from("value-b"))
392 /// ),
393 /// ]))
394 /// .to_create_parameters(),
395 /// )
396 /// ```
397 #[must_use]
398 pub fn to_create_parameters(&self) -> Vec<aws_sdk_cloudformation::types::Parameter> {
399 self.0
400 .iter()
401 .map(|(key, value)| {
402 aws_sdk_cloudformation::types::Parameter::builder()
403 .parameter_key(&key.0)
404 .parameter_value(&value.0)
405 .build()
406 })
407 .collect()
408 }
409
410 /// To parameter update parameters
411 ///
412 /// ### Examples
413 ///
414 /// Some updates
415 ///
416 /// ```
417 /// # use stack_deploy::types::*;
418 ///
419 /// let existing_stack = aws_sdk_cloudformation::types::Stack::builder()
420 /// .set_parameters(Some(vec![
421 /// aws_sdk_cloudformation::types::Parameter::builder()
422 /// .parameter_key("key-a")
423 /// .parameter_value("value-a1")
424 /// .build(),
425 /// aws_sdk_cloudformation::types::Parameter::builder()
426 /// .parameter_key("key-b")
427 /// .parameter_value("value-b1")
428 /// .build(),
429 /// ]))
430 /// .build();
431 ///
432 /// assert_eq!(
433 /// vec![
434 /// aws_sdk_cloudformation::types::Parameter::builder()
435 /// .parameter_key("key-a")
436 /// .use_previous_value(true)
437 /// .build(),
438 /// aws_sdk_cloudformation::types::Parameter::builder()
439 /// .parameter_key("key-b")
440 /// .parameter_value("value-b2")
441 /// .build(),
442 /// ],
443 /// ParameterMap(std::collections::BTreeMap::from([(
444 /// ParameterKey(String::from("key-b")),
445 /// ParameterValue(String::from("value-b2"))
446 /// ),]))
447 /// .to_parameter_update_parameters(&existing_stack),
448 /// )
449 /// ```
450 #[must_use]
451 pub fn to_parameter_update_parameters(
452 &self,
453 existing_stack: &aws_sdk_cloudformation::types::Stack,
454 ) -> Vec<aws_sdk_cloudformation::types::Parameter> {
455 match existing_stack.parameters.as_ref() {
456 None => vec![],
457 Some(parameters) => parameters
458 .iter()
459 .map(|parameter| {
460 let existing_parameter_key: &str = parameter.parameter_key().unwrap();
461
462 let builder = aws_sdk_cloudformation::types::Parameter::builder()
463 .parameter_key(existing_parameter_key);
464
465 match self
466 .0
467 .get(&ParameterKey(String::from(existing_parameter_key)))
468 {
469 Some(value) => builder.parameter_value(value.0.clone()),
470 None => builder.use_previous_value(true),
471 }
472 .build()
473 })
474 .collect(),
475 }
476 }
477
478 /// To template update parameters
479 ///
480 /// ### Examples
481 ///
482 /// One parameter unchanged `key-a`, one parameter changed `key-b`, one parameter
483 /// is probably new in the template `key-c`.
484 ///
485 /// ```
486 /// # use stack_deploy::types::*;
487 ///
488 /// let template_parameter_keys = ParameterKeys::from([
489 /// ParameterKey(String::from("key-a")),
490 /// ParameterKey(String::from("key-b")),
491 /// ParameterKey(String::from("key-c")),
492 /// ]);
493 ///
494 /// let existing_stack_parameter_keys = ParameterKeys::from([ParameterKey(String::from("key-a"))]);
495 ///
496 /// assert_eq!(
497 /// vec![
498 /// aws_sdk_cloudformation::types::Parameter::builder()
499 /// .parameter_key("key-a")
500 /// .use_previous_value(true)
501 /// .build(),
502 /// aws_sdk_cloudformation::types::Parameter::builder()
503 /// .parameter_key("key-b")
504 /// .parameter_value("value-b")
505 /// .build(),
506 /// ],
507 /// ParameterMap(std::collections::BTreeMap::from([(
508 /// ParameterKey(String::from("key-b")),
509 /// ParameterValue(String::from("value-b"))
510 /// ),]))
511 /// .to_template_update_parameters(&template_parameter_keys, &existing_stack_parameter_keys),
512 /// )
513 /// ```
514 #[must_use]
515 pub fn to_template_update_parameters(
516 &self,
517 template_parameter_keys: &ParameterKeys,
518 existing_stack_parameter_keys: &ParameterKeys,
519 ) -> Vec<aws_sdk_cloudformation::types::Parameter> {
520 let mut parameters = vec![];
521
522 for key in self.to_parameter_keys().union(template_parameter_keys) {
523 let builder = aws_sdk_cloudformation::types::Parameter::builder().parameter_key(&key.0);
524
525 parameters.push(
526 match self.0.get(key) {
527 Some(value) => builder.parameter_value(value.0.clone()),
528 None => {
529 if existing_stack_parameter_keys.contains(key) {
530 builder.use_previous_value(true)
531 } else {
532 continue;
533 }
534 }
535 }
536 .build(),
537 )
538 }
539
540 parameters
541 }
542
543 /// To parameter keys
544 ///
545 /// ### Examples
546 ///
547 /// ```
548 /// # use stack_deploy::types::*;
549 ///
550 /// let template = Template::Plain {
551 /// name: "example".into(),
552 /// rendered: TemplateRendered {
553 /// body: "ununsed".into(),
554 /// format: TemplateFormat::JSON,
555 /// },
556 /// parameter_keys: ParameterKeys::from([ParameterKey(String::from("key-a"))]),
557 /// };
558 ///
559 /// assert_eq!(
560 /// ParameterKeys::from([
561 /// ParameterKey(String::from("key-a")),
562 /// ParameterKey(String::from("key-b")),
563 /// ]),
564 /// ParameterMap(std::collections::BTreeMap::from([
565 /// (
566 /// ParameterKey(String::from("key-a")),
567 /// ParameterValue(String::from("value-a"))
568 /// ),
569 /// (
570 /// ParameterKey(String::from("key-b")),
571 /// ParameterValue(String::from("value-b"))
572 /// ),
573 /// ]))
574 /// .to_parameter_keys()
575 /// )
576 /// ```
577 #[must_use]
578 pub fn to_parameter_keys(&self) -> ParameterKeys {
579 ParameterKeys::from_iter(self.0.keys().cloned())
580 }
581}
582
583#[derive(Debug, PartialEq)]
584pub enum Template {
585 Plain {
586 name: TemplateName,
587 parameter_keys: ParameterKeys,
588 rendered: TemplateRendered,
589 },
590 Stratosphere {
591 name: TemplateName,
592 template: stratosphere::Template<'static>,
593 },
594}
595
596impl Template {
597 #[must_use]
598 pub fn parameter_keys(&self) -> ParameterKeys {
599 match self {
600 Self::Stratosphere { template, .. } => template.parameter_keys(),
601 Self::Plain { parameter_keys, .. } => parameter_keys.clone(),
602 }
603 }
604
605 #[must_use]
606 pub fn name(&self) -> &TemplateName {
607 match self {
608 Self::Stratosphere { name, .. } => name,
609 Self::Plain { name, .. } => name,
610 }
611 }
612
613 #[must_use]
614 pub fn rendered(&self) -> TemplateRendered {
615 match self {
616 Self::Plain { rendered, .. } => rendered.clone(),
617 Self::Stratosphere { template, .. } => TemplateRendered {
618 body: template.render_json().into(),
619 format: TemplateFormat::JSON,
620 },
621 }
622 }
623
624 #[must_use]
625 pub fn rendered_pretty(&self) -> TemplateRendered {
626 match self {
627 Self::Plain { rendered, .. } => rendered.clone(),
628 Self::Stratosphere { template, .. } => TemplateRendered {
629 body: template.render_json_pretty().into(),
630 format: TemplateFormat::JSON,
631 },
632 }
633 }
634}
635
636#[derive(Clone, Debug, PartialEq)]
637pub struct TemplateBody(String);
638
639impl AsRef<[u8]> for TemplateBody {
640 fn as_ref(&self) -> &[u8] {
641 self.0.as_bytes()
642 }
643}
644
645impl From<String> for TemplateBody {
646 fn from(value: String) -> Self {
647 Self(value)
648 }
649}
650
651impl From<TemplateBody> for String {
652 fn from(value: TemplateBody) -> Self {
653 value.0
654 }
655}
656
657impl From<&TemplateBody> for String {
658 fn from(value: &TemplateBody) -> Self {
659 value.0.clone()
660 }
661}
662
663impl From<&str> for TemplateBody {
664 fn from(value: &str) -> Self {
665 value.to_string().into()
666 }
667}
668
669impl From<&TemplateBody> for aws_sdk_s3::primitives::ByteStream {
670 fn from(value: &TemplateBody) -> Self {
671 value.0.as_bytes().to_vec().into()
672 }
673}
674
675impl TemplateBody {
676 #[must_use]
677 pub fn needs_upload(&self) -> bool {
678 self.0.len() > INLINE_TEMPLATE_LIMIT_BYTES
679 }
680
681 #[must_use]
682 pub fn as_str(&self) -> &str {
683 &self.0
684 }
685}
686
687#[derive(Clone, Debug, PartialEq)]
688pub enum TemplateFormat {
689 JSON,
690 YAML,
691}
692
693impl TemplateFormat {
694 #[must_use]
695 pub fn file_ext(&self) -> &str {
696 match self {
697 Self::JSON => "json",
698 Self::YAML => "yaml",
699 }
700 }
701}
702
703#[derive(Clone, Debug, PartialEq)]
704pub struct TemplateRendered {
705 pub format: TemplateFormat,
706 pub body: TemplateBody,
707}
708
709#[derive(Debug)]
710pub struct TemplateUrl(String);
711
712impl From<&str> for TemplateUrl {
713 fn from(value: &str) -> Self {
714 value.to_string().into()
715 }
716}
717
718impl From<String> for TemplateUrl {
719 fn from(value: String) -> Self {
720 Self(value)
721 }
722}
723
724impl From<TemplateUrl> for String {
725 fn from(value: TemplateUrl) -> Self {
726 value.0
727 }
728}
729
730#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
731pub struct TagKey(pub String);
732
733impl From<&str> for TagKey {
734 fn from(value: &str) -> Self {
735 Self(value.into())
736 }
737}
738
739#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
740pub struct TagValue(pub String);
741
742impl From<&str> for TagValue {
743 fn from(value: &str) -> Self {
744 Self(value.into())
745 }
746}
747
748#[derive(Debug, PartialEq)]
749pub struct TagMap(pub std::collections::BTreeMap<TagKey, TagValue>);
750
751impl Default for TagMap {
752 fn default() -> Self {
753 Self::new()
754 }
755}
756
757impl TagMap {
758 #[must_use]
759 pub fn new() -> Self {
760 Self(std::collections::BTreeMap::new())
761 }
762
763 #[must_use]
764 pub fn to_sdk_tags(&self) -> Vec<aws_sdk_cloudformation::types::Tag> {
765 self.0
766 .iter()
767 .map(|(key, value)| {
768 aws_sdk_cloudformation::types::Tag::builder()
769 .key(&key.0)
770 .value(&value.0)
771 .build()
772 })
773 .collect()
774 }
775}
776
777#[derive(Clone, Debug, Eq, PartialEq)]
778pub struct ChangeSetName(String);
779
780impl std::str::FromStr for ChangeSetName {
781 type Err = &'static str;
782
783 fn from_str(input: &str) -> Result<Self, Self::Err> {
784 Ok(Self(input.to_string()))
785 }
786}
787
788impl From<&ChangeSetName> for String {
789 fn from(value: &ChangeSetName) -> String {
790 value.0.to_string()
791 }
792}
793
794impl From<String> for ChangeSetName {
795 fn from(value: String) -> Self {
796 Self(value)
797 }
798}
799
800impl std::fmt::Display for ChangeSetName {
801 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
802 write!(formatter, "{}", self.0)
803 }
804}
805
806#[derive(Clone, Debug)]
807pub struct ChangeSetArn(pub String);
808
809impl ChangeSetArn {
810 #[must_use]
811 pub fn as_str(&self) -> &str {
812 &self.0
813 }
814}
815
816impl std::fmt::Display for ChangeSetArn {
817 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
818 write!(formatter, "{}", self.0)
819 }
820}
821
822impl From<ChangeSetArn> for String {
823 fn from(value: ChangeSetArn) -> Self {
824 (&value.0).into()
825 }
826}
827
828impl From<&ChangeSetArn> for String {
829 fn from(value: &ChangeSetArn) -> Self {
830 value.0.to_string()
831 }
832}