Skip to main content

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(&parameters)
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(&parameters)
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(&parameter.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}