stratosphere_core/value.rs
1use crate::template::LogicalResourceName;
2use serde;
3use serde_json;
4use serde_json::json;
5
6#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
7pub struct AttributeName(String);
8
9impl From<&str> for AttributeName {
10 fn from(value: &str) -> Self {
11 Self(value.into())
12 }
13}
14
15#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize)]
16pub struct ConditionName(String);
17
18impl std::fmt::Display for ConditionName {
19 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
20 write!(formatter, "{}", self.0)
21 }
22}
23
24impl From<&str> for ConditionName {
25 fn from(value: &str) -> Self {
26 Self(value.to_string())
27 }
28}
29
30impl From<&ConditionName> for ConditionName {
31 fn from(value: &ConditionName) -> Self {
32 value.clone()
33 }
34}
35
36pub fn equals_bool<A: Into<ExpBool>, B: Into<ExpBool>>(left: A, right: B) -> ExpBool {
37 ExpBool::Equals(ExpPair::Bool {
38 left: Box::new(left.into()),
39 right: Box::new(right.into()),
40 })
41}
42
43pub fn equals_string<A: Into<ExpString>, B: Into<ExpString>>(left: A, right: B) -> ExpBool {
44 ExpBool::Equals(ExpPair::String {
45 left: Box::new(left.into()),
46 right: Box::new(right.into()),
47 })
48}
49
50/// Trait for expression types that support conditional (`Fn::If`) expressions
51///
52/// This trait enables generic helper functions for constructing conditional
53/// expressions across different expression types (ExpString, ExpBool, etc.)
54pub trait FnIf: Sized {
55 /// Creates a conditional expression using `Fn::If`
56 ///
57 /// # Arguments
58 ///
59 /// * `condition_name` - Name of the condition to evaluate
60 /// * `true_branch` - Value returned when condition is true
61 /// * `else_branch` - Value returned when condition is false
62 fn fn_if(
63 condition_name: impl Into<ConditionName>,
64 true_branch: impl Into<Self>,
65 else_branch: impl Into<Self>,
66 ) -> Self;
67}
68
69/// Generic helper function to create conditional (`Fn::If`) expressions
70///
71/// This function works with any type implementing [`FnIf`],
72/// allowing for type-safe conditional expressions across different expression types.
73///
74/// # Arguments
75///
76/// * `condition_name` - Name of the condition to evaluate
77/// * `true_branch` - Value returned when condition is true
78/// * `else_branch` - Value returned when condition is false
79///
80/// # Examples
81///
82/// ```
83/// # use stratosphere_core::value::*;
84/// let expr: ExpString = fn_if("MyCondition", "value-if-true", "value-if-false");
85/// ```
86pub fn fn_if<T: FnIf>(
87 condition_name: impl Into<ConditionName>,
88 true_branch: impl Into<T>,
89 else_branch: impl Into<T>,
90) -> T {
91 T::fn_if(condition_name, true_branch, else_branch)
92}
93
94/// Type-specific helper for boolean conditional expressions that doesn't require turbofish syntax.
95/// Use this when working with ExpBool values.
96pub fn fn_if_bool(
97 condition_name: impl Into<ConditionName>,
98 true_branch: impl Into<ExpBool>,
99 false_branch: impl Into<ExpBool>,
100) -> ExpBool {
101 ExpBool::fn_if(condition_name, true_branch, false_branch)
102}
103
104/// Trait for expression types that support select (`Fn::Select`) expressions
105///
106/// This trait enables generic helper functions for selecting an item from a list
107/// across different expression types (ExpString, ExpBool, etc.)
108pub trait FnSelect: Sized {
109 /// The type representing a list of values that can be selected from
110 type ValueList;
111
112 /// Creates a select expression using `Fn::Select`
113 ///
114 /// # Arguments
115 ///
116 /// * `index` - Zero-based index of the item to select
117 /// * `values` - List of values to select from
118 fn fn_select(index: u8, values: Self::ValueList) -> Self;
119}
120
121/// Generic helper function to create select (`Fn::Select`) expressions
122///
123/// This function works with any type implementing [`FnSelect`],
124/// allowing for type-safe selection from lists across different expression types.
125///
126/// # Arguments
127///
128/// * `index` - Zero-based index of the item to select
129/// * `values` - List of values to select from
130///
131/// # Examples
132///
133/// ```
134/// # use stratosphere_core::value::*;
135/// let expr: ExpString = fn_select(0, vec!["first".into(), "second".into()].into());
136/// ```
137pub fn fn_select<T: FnSelect>(index: u8, values: T::ValueList) -> T {
138 T::fn_select(index, values)
139}
140
141/// Type-specific helper for boolean select expressions that doesn't require turbofish syntax.
142/// Use this when working with ExpBool values.
143#[must_use]
144pub fn fn_select_bool(index: u8, values: Vec<ExpBool>) -> ExpBool {
145 ExpBool::fn_select(index, values)
146}
147
148/// Type-specific helper for string select expressions that doesn't require turbofish syntax.
149/// Use this when working with ExpString values.
150pub fn fn_select_string(index: u8, values: impl Into<ExpStringList>) -> ExpString {
151 ExpString::fn_select(index, values.into())
152}
153
154/// Helper function to create a split expression
155///
156/// Returns an ExpString::Split expression representing a Fn::Split operation.
157/// Typically used with fn_select_string to extract specific elements.
158///
159/// # Arguments
160///
161/// * `delimiter` - The delimiter to split on
162/// * `source` - The source string to split
163///
164/// # Examples
165///
166/// ```
167/// # use stratosphere_core::value::*;
168/// // Select first element from split result
169/// let first = fn_select_string(0, vec![fn_split(",", "a,b,c")]);
170/// ```
171pub fn fn_split(delimiter: impl Into<String>, source: impl Into<ExpString>) -> ExpString {
172 ExpString::Split {
173 delimiter: delimiter.into(),
174 source: Box::new(source.into()),
175 }
176}
177
178/// Trait for expression types that support find in map (`Fn::FindInMap`) expressions
179///
180/// This trait enables generic helper functions for retrieving values from mappings
181/// across different expression types (ExpString, ExpBool, etc.)
182pub trait FnFindInMap: Sized {
183 /// Creates a find in map expression using `Fn::FindInMap`
184 ///
185 /// # Arguments
186 ///
187 /// * `map_name` - MapName referencing the mapping to look up
188 /// * `top_level_key` - First-level key in the mapping
189 /// * `second_level_key` - Second-level key in the mapping
190 fn fn_find_in_map(
191 map_name: impl Into<crate::template::MapName>,
192 top_level_key: impl Into<ExpString>,
193 second_level_key: impl Into<String>,
194 ) -> Self;
195}
196
197/// Generic helper function to create find in map (`Fn::FindInMap`) expressions
198///
199/// This function works with any type implementing [`FnFindInMap`],
200/// allowing for type-safe mapping lookups across different expression types.
201///
202/// # Arguments
203///
204/// * `map_name` - MapName referencing the mapping to look up
205/// * `top_level_key` - First-level key in the mapping
206/// * `second_level_key` - Second-level key in the mapping
207///
208/// # Examples
209///
210/// ```
211/// # use stratosphere_core::value::*;
212/// # use stratosphere_core::template::MapName;
213/// let region_map: MapName = "RegionMap".into();
214/// let expr: ExpString = fn_find_in_map(®ion_map, "us-east-1", "AMI");
215/// ```
216pub fn fn_find_in_map<T: FnFindInMap>(
217 map_name: impl Into<crate::template::MapName>,
218 top_level_key: impl Into<ExpString>,
219 second_level_key: impl Into<String>,
220) -> T {
221 T::fn_find_in_map(map_name, top_level_key, second_level_key)
222}
223
224/// Type-specific helper for string find in map expressions that doesn't require turbofish syntax.
225/// Use this when working with ExpString values.
226pub fn fn_find_in_map_string(
227 map_name: impl Into<crate::template::MapName>,
228 top_level_key: impl Into<ExpString>,
229 second_level_key: impl Into<String>,
230) -> ExpString {
231 ExpString::fn_find_in_map(map_name, top_level_key, second_level_key)
232}
233
234/// Type-specific helper for boolean find in map expressions that doesn't require turbofish syntax.
235/// Use this when working with ExpBool values.
236pub fn fn_find_in_map_bool(
237 map_name: impl Into<crate::template::MapName>,
238 top_level_key: impl Into<ExpString>,
239 second_level_key: impl Into<String>,
240) -> ExpBool {
241 ExpBool::fn_find_in_map(map_name, top_level_key, second_level_key)
242}
243
244/// Helper function to create a Fn::GetAZs expression
245///
246/// Returns an ExpStringList representing the list of Availability Zones for a region.
247/// Pass an empty string to get AZs for the current region.
248///
249/// # Arguments
250///
251/// * `region` - The region to get AZs for, or empty string for current region
252///
253/// # Examples
254///
255/// ```
256/// # use stratosphere_core::value::*;
257/// // Get AZs for current region
258/// let azs = fn_get_azs("");
259///
260/// // Get AZs for specific region
261/// let azs = fn_get_azs("us-west-2");
262///
263/// // Use with Fn::Select to pick first AZ
264/// let first_az = fn_select_string(0, fn_get_azs(""));
265/// ```
266pub fn fn_get_azs(region: impl Into<ExpString>) -> ExpStringList {
267 ExpStringList::GetAZs {
268 region: Box::new(region.into()),
269 }
270}
271
272/// Helper function to create a Fn::Cidr expression
273///
274/// Returns an ExpStringList representing a list of CIDR address blocks.
275/// Used to generate subnet CIDR blocks from a larger VPC CIDR block.
276///
277/// # Arguments
278///
279/// * `ip_block` - The CIDR block to divide (e.g., "10.0.0.0/16")
280/// * `count` - The number of CIDRs to generate (1-256)
281/// * `cidr_bits` - The number of subnet bits for the CIDR (typically 8 for /24 subnets)
282///
283/// # Examples
284///
285/// ```
286/// # use stratosphere_core::value::*;
287/// // Generate 6 /24 subnets from a /16 VPC
288/// let subnets = fn_cidr("10.0.0.0/16", 6, 8);
289///
290/// // Use with Fn::Select to pick specific subnet
291/// let first_subnet = fn_select_string(0, fn_cidr("10.0.0.0/16", 6, 8));
292/// ```
293pub fn fn_cidr(ip_block: impl Into<ExpString>, count: u8, cidr_bits: u8) -> ExpStringList {
294 ExpStringList::Cidr {
295 ip_block: Box::new(ip_block.into()),
296 count,
297 cidr_bits,
298 }
299}
300
301pub trait ToValue {
302 fn to_value(&self) -> serde_json::Value;
303}
304
305#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
306pub struct OutputExportName(String);
307
308impl From<&str> for OutputExportName {
309 fn from(value: &str) -> Self {
310 Self(value.to_string())
311 }
312}
313
314impl<T: ToValue> ToValue for Box<T> {
315 fn to_value(&self) -> serde_json::Value {
316 self.as_ref().to_value()
317 }
318}
319
320#[derive(Clone, Debug, Eq, PartialEq)]
321pub enum ExpString {
322 Base64(Box<ExpString>),
323 FindInMap {
324 map_name: crate::template::MapName,
325 top_level_key: Box<ExpString>,
326 second_level_key: String,
327 },
328 GetAtt {
329 logical_resource_name: LogicalResourceName,
330 attribute_name: AttributeName,
331 },
332 If {
333 condition_name: ConditionName,
334 true_branch: Box<ExpString>,
335 else_branch: Box<ExpString>,
336 },
337 ImportValue(OutputExportName),
338 Join {
339 delimiter: String,
340 values: Vec<ExpString>,
341 },
342 Literal(String),
343 Ref(LogicalResourceName),
344 Select {
345 index: u8,
346 values: Box<ExpStringList>,
347 },
348 Split {
349 delimiter: String,
350 source: Box<ExpString>,
351 },
352 Sub {
353 pattern: String,
354 },
355 // Pseudo parameters that return strings
356 AwsAccountId,
357 AwsPartition,
358 AwsRegion,
359 AwsStackId,
360 AwsStackName,
361 AwsUrlSuffix,
362}
363
364impl ExpString {
365 #[must_use]
366 pub fn base64(self) -> ExpString {
367 ExpString::Base64(Box::new(self))
368 }
369}
370
371impl FnIf for ExpString {
372 fn fn_if(
373 condition_name: impl Into<ConditionName>,
374 true_branch: impl Into<Self>,
375 else_branch: impl Into<Self>,
376 ) -> Self {
377 ExpString::If {
378 condition_name: condition_name.into(),
379 true_branch: Box::new(true_branch.into()),
380 else_branch: Box::new(else_branch.into()),
381 }
382 }
383}
384
385impl FnSelect for ExpString {
386 type ValueList = ExpStringList;
387
388 fn fn_select(index: u8, values: Self::ValueList) -> Self {
389 ExpString::Select {
390 index,
391 values: Box::new(values),
392 }
393 }
394}
395
396impl FnFindInMap for ExpString {
397 fn fn_find_in_map(
398 map_name: impl Into<crate::template::MapName>,
399 top_level_key: impl Into<ExpString>,
400 second_level_key: impl Into<String>,
401 ) -> Self {
402 ExpString::FindInMap {
403 map_name: map_name.into(),
404 top_level_key: Box::new(top_level_key.into()),
405 second_level_key: second_level_key.into(),
406 }
407 }
408}
409
410impl serde::Serialize for ExpString {
411 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
412 self.to_value().serialize(serializer)
413 }
414}
415
416impl From<&str> for ExpString {
417 fn from(value: &str) -> Self {
418 Self::Literal(value.to_string())
419 }
420}
421
422impl From<LogicalResourceName> for ExpString {
423 fn from(value: LogicalResourceName) -> Self {
424 Self::Ref(value)
425 }
426}
427
428impl From<&LogicalResourceName> for ExpString {
429 fn from(value: &LogicalResourceName) -> Self {
430 Self::Ref(value.clone())
431 }
432}
433
434pub fn join(delimiter: &str, values: impl IntoIterator<Item = ExpString>) -> ExpString {
435 ExpString::Join {
436 delimiter: delimiter.to_string(),
437 values: values.into_iter().collect(),
438 }
439}
440
441pub fn get_att(
442 logical_resource_name: impl Into<LogicalResourceName>,
443 attribute_name: impl Into<AttributeName>,
444) -> ExpString {
445 ExpString::GetAtt {
446 logical_resource_name: logical_resource_name.into(),
447 attribute_name: attribute_name.into(),
448 }
449}
450
451pub fn get_att_arn(logical_resource_name: impl Into<LogicalResourceName>) -> ExpString {
452 get_att(logical_resource_name, "Arn")
453}
454
455pub fn mk_name(suffix: impl Into<ExpString>) -> ExpString {
456 join("-", [AWS_STACK_NAME, suffix.into()])
457}
458
459impl ToValue for &String {
460 fn to_value(&self) -> serde_json::Value {
461 ExpString::Literal(self.to_string()).to_value()
462 }
463}
464
465impl ToValue for &LogicalResourceName {
466 fn to_value(&self) -> serde_json::Value {
467 serde_json::to_value(&self.0).unwrap()
468 }
469}
470
471impl ToValue for ExpString {
472 /// Render expression to CF template value^
473 ///
474 /// # Panics
475 ///
476 /// On internal errors/bugs, there is no public API that
477 /// allows to construct values that panic on this call.
478 ///
479 /// # Examples
480 ///
481 /// [Fn::Base64](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-base64.html)
482 ///
483 /// ```
484 /// # use stratosphere_core::value::*;
485 /// # use serde_json::json;
486 ///
487 /// assert_eq!(
488 /// json!({"Fn::Base64":"some-literal"}),
489 /// ExpString::from("some-literal").base64().to_value()
490 /// )
491 /// ```
492 ///
493 /// [Fn::If](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-conditions.html#intrinsic-function-reference-conditions-if)
494 ///
495 /// ```
496 /// # use stratosphere_core::template::*;
497 /// # use stratosphere_core::value::*;
498 /// # use serde_json::json;
499 /// assert_eq!(
500 /// json!({"Fn::If":["condition-name",{"Ref":"resource-a"},{"Ref":"resource-b"}]}),
501 /// ExpString::If{
502 /// condition_name: "condition-name".into(),
503 /// true_branch: Box::new(LogicalResourceName::from("resource-a").into()),
504 /// else_branch: Box::new(LogicalResourceName::from("resource-b").into()),
505 /// }.to_value()
506 /// )
507 /// ```
508 ///
509 /// [Ref](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html)
510 ///
511 /// ```
512 /// # use stratosphere_core::template::LogicalResourceName;
513 /// # use stratosphere_core::value::*;
514 /// # use serde_json::json;
515 ///
516 /// let logical_resource_name = LogicalResourceName::from("some-logical-resource-name");
517 /// let reference : ExpString = logical_resource_name.into();
518 ///
519 /// assert_eq!(
520 /// json!({"Ref":"some-logical-resource-name"}),
521 /// reference.to_value()
522 /// )
523 /// ```
524 ///
525 /// [Fn::GetAtt](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html)
526 ///
527 /// ```
528 /// # use stratosphere_core::value::*;
529 /// # use serde_json::json;
530 /// assert_eq!(
531 /// json!({"Fn::GetAtt":["some-logical-resource-name", "some-attribute-name"]}),
532 /// ExpString::GetAtt{
533 /// logical_resource_name: "some-logical-resource-name".into(),
534 /// attribute_name: "some-attribute-name".into()
535 /// }.to_value()
536 /// )
537 /// ```
538 ///
539 /// String Literal
540 ///
541 /// ```
542 /// # use stratosphere_core::value::*;
543 /// # use serde_json::json;
544 ///
545 /// let exp : ExpString = "some-literal".into();
546 ///
547 /// assert_eq!(json!{"some-literal"}, exp.to_value())
548 /// ```
549 ///
550 /// [Fn::Join](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-join.html)
551 ///
552 /// ```
553 /// # use stratosphere_core::template::*;
554 /// # use stratosphere_core::value::*;
555 /// # use serde_json::json;
556 /// assert_eq!(
557 /// json!({"Fn::Join":[',', [{"Ref": "some-logical-resource-name"}, "some-literal"]]}),
558 /// ExpString::Join{
559 /// delimiter: String::from(","),
560 /// values: vec![
561 /// LogicalResourceName::from("some-logical-resource-name").into(),
562 /// "some-literal".into()
563 /// ]
564 /// }.to_value()
565 /// )
566 /// ```
567 fn to_value(&self) -> serde_json::Value {
568 match self {
569 ExpString::Base64(value) => mk_func("Fn::Base64", value.to_value()),
570 ExpString::FindInMap {
571 map_name,
572 top_level_key,
573 second_level_key,
574 } => mk_func(
575 "Fn::FindInMap",
576 vec![
577 serde_json::to_value(map_name).unwrap(),
578 top_level_key.to_value(),
579 serde_json::to_value(second_level_key).unwrap(),
580 ],
581 ),
582 ExpString::GetAtt {
583 logical_resource_name,
584 attribute_name,
585 } => mk_func(
586 "Fn::GetAtt",
587 &[
588 serde_json::to_value(logical_resource_name).unwrap(),
589 serde_json::to_value(attribute_name).unwrap(),
590 ],
591 ),
592 ExpString::If {
593 condition_name,
594 true_branch,
595 else_branch,
596 } => mk_func(
597 "Fn::If",
598 &[
599 serde_json::to_value(condition_name).unwrap(),
600 true_branch.to_value(),
601 else_branch.to_value(),
602 ],
603 ),
604 ExpString::Join { delimiter, values } => mk_func(
605 "Fn::Join",
606 vec![
607 delimiter.to_value(),
608 serde_json::to_value(
609 values
610 .iter()
611 .map(|item| item.to_value())
612 .collect::<Vec<_>>(),
613 )
614 .unwrap(),
615 ],
616 ),
617 ExpString::Literal(value) => serde_json::to_value(value).unwrap(),
618 ExpString::Ref(value) => mk_ref(value),
619 ExpString::ImportValue(value) => mk_func("Fn::ImportValue", value),
620 ExpString::Sub { pattern } => mk_func("Fn::Sub", pattern),
621 ExpString::Select { index, values } => mk_func(
622 "Fn::Select",
623 vec![serde_json::to_value(index).unwrap(), values.to_value()],
624 ),
625 ExpString::Split { delimiter, source } => mk_func(
626 "Fn::Split",
627 vec![serde_json::to_value(delimiter).unwrap(), source.to_value()],
628 ),
629 // Pseudo parameters serialize as Refs
630 ExpString::AwsAccountId => mk_ref("AWS::AccountId"),
631 ExpString::AwsPartition => mk_ref("AWS::Partition"),
632 ExpString::AwsRegion => mk_ref("AWS::Region"),
633 ExpString::AwsStackId => mk_ref("AWS::StackId"),
634 ExpString::AwsStackName => mk_ref("AWS::StackName"),
635 ExpString::AwsUrlSuffix => mk_ref("AWS::URLSuffix"),
636 }
637 }
638}
639
640impl<A: ToValue> ToValue for Vec<A> {
641 fn to_value(&self) -> serde_json::Value {
642 self.iter().map(ToValue::to_value).collect()
643 }
644}
645
646impl ToValue for i32 {
647 fn to_value(&self) -> serde_json::Value {
648 serde_json::to_value(self).unwrap()
649 }
650}
651
652impl ToValue for i64 {
653 fn to_value(&self) -> serde_json::Value {
654 serde_json::to_value(self).unwrap()
655 }
656}
657
658impl ToValue for f64 {
659 fn to_value(&self) -> serde_json::Value {
660 serde_json::to_value(self).unwrap()
661 }
662}
663
664impl ToValue for bool {
665 fn to_value(&self) -> serde_json::Value {
666 serde_json::to_value(self).unwrap()
667 }
668}
669
670impl ToValue for serde_json::Value {
671 fn to_value(&self) -> serde_json::Value {
672 self.clone()
673 }
674}
675
676impl<T: ToValue> ToValue for std::collections::BTreeMap<String, T> {
677 fn to_value(&self) -> serde_json::Value {
678 serde_json::Value::Object(serde_json::Map::from_iter(
679 self.iter()
680 .map(|(key, value)| (key.clone(), value.to_value())),
681 ))
682 }
683}
684
685impl ToValue for chrono::DateTime<chrono::Utc> {
686 /// Converts a timestamp to a JSON string value in RFC3339 format
687 ///
688 /// # Examples
689 ///
690 /// ```
691 /// # use stratosphere_core::value::ToValue;
692 /// # use chrono::{DateTime, Utc, TimeZone};
693 /// # use serde_json::json;
694 ///
695 /// let timestamp = Utc.with_ymd_and_hms(2024, 10, 13, 19, 0, 0).unwrap();
696 /// let value = timestamp.to_value();
697 ///
698 /// assert_eq!(value, json!("2024-10-13T19:00:00+00:00"));
699 /// ```
700 fn to_value(&self) -> serde_json::Value {
701 serde_json::to_value(self.to_rfc3339()).unwrap()
702 }
703}
704
705fn mk_func<T: serde::Serialize>(name: &str, value: T) -> serde_json::Value {
706 json!({name:value})
707}
708
709fn mk_ref<T: serde::Serialize>(value: T) -> serde_json::Value {
710 mk_func("Ref", value)
711}
712
713#[derive(Clone, Debug, Eq, PartialEq)]
714pub enum ExpPair {
715 Bool {
716 left: Box<ExpBool>,
717 right: Box<ExpBool>,
718 },
719 String {
720 left: Box<ExpString>,
721 right: Box<ExpString>,
722 },
723}
724
725#[derive(Clone, Debug, Eq, PartialEq)]
726pub enum ExpStringList {
727 Cidr {
728 ip_block: Box<ExpString>,
729 count: u8,
730 cidr_bits: u8,
731 },
732 GetAZs {
733 region: Box<ExpString>,
734 },
735 Literal(Vec<ExpString>),
736 // Pseudo parameter that returns a list of strings
737 AwsNotificationArns,
738}
739
740impl From<Vec<ExpString>> for ExpStringList {
741 fn from(values: Vec<ExpString>) -> Self {
742 ExpStringList::Literal(values)
743 }
744}
745
746impl ToValue for ExpStringList {
747 fn to_value(&self) -> serde_json::Value {
748 match self {
749 ExpStringList::Cidr {
750 ip_block,
751 count,
752 cidr_bits,
753 } => mk_func(
754 "Fn::Cidr",
755 vec![
756 ip_block.to_value(),
757 serde_json::to_value(count).unwrap(),
758 serde_json::to_value(cidr_bits).unwrap(),
759 ],
760 ),
761 ExpStringList::GetAZs { region } => mk_func("Fn::GetAZs", region.to_value()),
762 ExpStringList::Literal(values) => serde_json::to_value(
763 values
764 .iter()
765 .map(|item| item.to_value())
766 .collect::<Vec<_>>(),
767 )
768 .unwrap(),
769 // Pseudo parameter that returns a list serializes as a Ref
770 ExpStringList::AwsNotificationArns => mk_ref("AWS::NotificationARNs"),
771 }
772 }
773}
774
775#[derive(Clone, Debug, Eq, PartialEq)]
776pub enum ExpBool {
777 And(Box<ExpBool>, Box<ExpBool>),
778 Condition(ConditionName),
779 Equals(ExpPair),
780 FindInMap {
781 map_name: crate::template::MapName,
782 top_level_key: Box<ExpString>,
783 second_level_key: String,
784 },
785 If {
786 condition_name: ConditionName,
787 true_branch: Box<ExpBool>,
788 else_branch: Box<ExpBool>,
789 },
790 Literal(bool),
791 Not(Box<ExpBool>),
792 Or(Vec<ExpBool>),
793 Select {
794 index: u8,
795 values: Vec<ExpBool>,
796 },
797}
798
799impl From<bool> for ExpBool {
800 fn from(value: bool) -> Self {
801 Self::Literal(value)
802 }
803}
804
805impl From<ConditionName> for ExpBool {
806 fn from(value: ConditionName) -> Self {
807 Self::Condition(value)
808 }
809}
810
811impl From<&ConditionName> for ExpBool {
812 fn from(value: &ConditionName) -> Self {
813 Self::Condition(value.clone())
814 }
815}
816
817impl serde::Serialize for ExpBool {
818 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
819 self.to_value().serialize(serializer)
820 }
821}
822
823impl FnIf for ExpBool {
824 fn fn_if(
825 condition_name: impl Into<ConditionName>,
826 true_branch: impl Into<Self>,
827 else_branch: impl Into<Self>,
828 ) -> Self {
829 Self::If {
830 condition_name: condition_name.into(),
831 true_branch: Box::new(true_branch.into()),
832 else_branch: Box::new(else_branch.into()),
833 }
834 }
835}
836
837impl FnSelect for ExpBool {
838 type ValueList = Vec<ExpBool>;
839
840 fn fn_select(index: u8, values: Self::ValueList) -> Self {
841 ExpBool::Select { index, values }
842 }
843}
844
845impl FnFindInMap for ExpBool {
846 fn fn_find_in_map(
847 map_name: impl Into<crate::template::MapName>,
848 top_level_key: impl Into<ExpString>,
849 second_level_key: impl Into<String>,
850 ) -> Self {
851 ExpBool::FindInMap {
852 map_name: map_name.into(),
853 top_level_key: Box::new(top_level_key.into()),
854 second_level_key: second_level_key.into(),
855 }
856 }
857}
858
859impl ToValue for ExpBool {
860 /// Literal
861 ///
862 /// ```
863 /// # use stratosphere_core::value::*;
864 /// # use serde_json::json;
865 /// assert_eq!(
866 /// serde_json::Value::Bool(true),
867 /// ExpBool::Literal(true).to_value()
868 /// )
869 /// ```
870 ///
871 /// [Fn::Equals](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-conditions.html#intrinsic-function-reference-conditions-equals)
872 ///
873 /// ```
874 /// # use stratosphere_core::template::*;
875 /// # use stratosphere_core::value::*;
876 /// # use serde_json::json;
877 ///
878 /// assert_eq!(
879 /// json!({"Fn::Equals":[{"Ref":"resource-a"},"some-literal"]}),
880 /// equals_string(
881 /// LogicalResourceName::from("resource-a"),
882 /// "some-literal"
883 /// ).to_value()
884 /// );
885 ///
886 /// assert_eq!(
887 /// json!({"Fn::Equals":[true,false]}),
888 /// equals_bool(
889 /// true,
890 /// false
891 /// ).to_value()
892 /// )
893 /// ```
894 ///
895 fn to_value(&self) -> serde_json::Value {
896 match self {
897 ExpBool::And(left, right) => mk_func("Fn::And", [left.to_value(), right.to_value()]),
898 ExpBool::Condition(name) => mk_func("Condition", name),
899 ExpBool::Equals(pair) => match pair {
900 ExpPair::Bool { left, right } => {
901 mk_func("Fn::Equals", [left.to_value(), right.to_value()])
902 }
903 ExpPair::String { left, right } => {
904 mk_func("Fn::Equals", [left.to_value(), right.to_value()])
905 }
906 },
907 ExpBool::FindInMap {
908 map_name,
909 top_level_key,
910 second_level_key,
911 } => mk_func(
912 "Fn::FindInMap",
913 vec![
914 serde_json::to_value(map_name).unwrap(),
915 top_level_key.to_value(),
916 serde_json::to_value(second_level_key).unwrap(),
917 ],
918 ),
919 ExpBool::If {
920 condition_name,
921 true_branch,
922 else_branch,
923 } => mk_func(
924 "Fn::If",
925 [
926 serde_json::to_value(condition_name).unwrap(),
927 true_branch.to_value(),
928 else_branch.to_value(),
929 ],
930 ),
931 ExpBool::Literal(value) => serde_json::Value::Bool(*value),
932 ExpBool::Not(value) => mk_func("Fn::Not", [value.to_value()]),
933 ExpBool::Or(conditions) => mk_func(
934 "Fn::Or",
935 conditions
936 .iter()
937 .map(|condition| condition.to_value())
938 .collect::<Vec<_>>(),
939 ),
940 ExpBool::Select { index, values } => mk_func(
941 "Fn::Select",
942 [
943 serde_json::Value::Number((*index).into()),
944 serde_json::Value::Array(
945 values.iter().map(|v| v.to_value()).collect::<Vec<_>>(),
946 ),
947 ],
948 ),
949 }
950 }
951}
952
953// Pseudo parameter constants
954
955/// AWS account ID (e.g., "123456789012")
956///
957/// This pseudo parameter resolves to the AWS account ID of the account
958/// in which the CloudFormation stack is being created.
959///
960/// # Examples
961///
962/// ```
963/// # use stratosphere_core::value::*;
964/// # use serde_json::json;
965/// assert_eq!(AWS_ACCOUNT_ID.to_value(), json!({"Ref": "AWS::AccountId"}));
966/// ```
967pub const AWS_ACCOUNT_ID: ExpString = ExpString::AwsAccountId;
968
969/// AWS partition (e.g., "aws", "aws-cn", "aws-us-gov")
970///
971/// This pseudo parameter resolves to the partition that the resource is in.
972/// For standard AWS Regions, the partition is "aws". For resources in other
973/// partitions, the partition is "aws-partitionname".
974///
975/// # Examples
976///
977/// ```
978/// # use stratosphere_core::value::*;
979/// # use serde_json::json;
980/// assert_eq!(AWS_PARTITION.to_value(), json!({"Ref": "AWS::Partition"}));
981/// ```
982pub const AWS_PARTITION: ExpString = ExpString::AwsPartition;
983
984/// AWS region (e.g., "us-east-1")
985///
986/// This pseudo parameter resolves to the AWS Region in which the
987/// CloudFormation stack is being created.
988///
989/// # Examples
990///
991/// ```
992/// # use stratosphere_core::value::*;
993/// # use serde_json::json;
994/// assert_eq!(AWS_REGION.to_value(), json!({"Ref": "AWS::Region"}));
995/// ```
996pub const AWS_REGION: ExpString = ExpString::AwsRegion;
997
998/// Stack ID/ARN
999///
1000/// This pseudo parameter resolves to the ID (ARN) of the stack.
1001///
1002/// # Examples
1003///
1004/// ```
1005/// # use stratosphere_core::value::*;
1006/// # use serde_json::json;
1007/// assert_eq!(AWS_STACK_ID.to_value(), json!({"Ref": "AWS::StackId"}));
1008/// ```
1009pub const AWS_STACK_ID: ExpString = ExpString::AwsStackId;
1010
1011/// Stack name
1012///
1013/// This pseudo parameter resolves to the name of the CloudFormation stack.
1014///
1015/// # Examples
1016///
1017/// ```
1018/// # use stratosphere_core::value::*;
1019/// # use serde_json::json;
1020/// assert_eq!(AWS_STACK_NAME.to_value(), json!({"Ref": "AWS::StackName"}));
1021/// ```
1022pub const AWS_STACK_NAME: ExpString = ExpString::AwsStackName;
1023
1024/// AWS domain suffix (e.g., "amazonaws.com")
1025///
1026/// This pseudo parameter resolves to the suffix for a domain. The suffix is
1027/// typically "amazonaws.com", but might differ by Region. For example, in
1028/// China (Beijing), the suffix is "amazonaws.com.cn".
1029///
1030/// # Examples
1031///
1032/// ```
1033/// # use stratosphere_core::value::*;
1034/// # use serde_json::json;
1035/// assert_eq!(AWS_URL_SUFFIX.to_value(), json!({"Ref": "AWS::URLSuffix"}));
1036/// ```
1037pub const AWS_URL_SUFFIX: ExpString = ExpString::AwsUrlSuffix;
1038
1039/// List of SNS topic ARNs for stack notifications
1040///
1041/// This pseudo parameter resolves to the list of notification Amazon SNS
1042/// topic ARNs for the current stack.
1043///
1044/// # Examples
1045///
1046/// ```
1047/// # use stratosphere_core::value::*;
1048/// # use serde_json::json;
1049/// assert_eq!(AWS_NOTIFICATION_ARNS.to_value(), json!({"Ref": "AWS::NotificationARNs"}));
1050/// ```
1051pub const AWS_NOTIFICATION_ARNS: ExpStringList = ExpStringList::AwsNotificationArns;