rusty_cdk_core/sns/
builder.rs

1use std::marker::PhantomData;
2use crate::intrinsic::{get_arn, get_ref};
3use crate::lambda::{FunctionRef, PermissionBuilder};
4use crate::shared::Id;
5use crate::sns::{Subscription, SnsSubscriptionProperties, Topic, TopicProperties, TopicRef};
6use crate::stack::{Resource, StackBuilder};
7use crate::type_state;
8use crate::wrappers::{LambdaPermissionAction, StringWithOnlyAlphaNumericsUnderscoresAndHyphens};
9
10const FIFO_SUFFIX: &str = ".fifo";
11
12pub enum FifoThroughputScope {
13    Topic,
14    MessageGroup
15}
16
17pub enum SubscriptionType<'a> {
18    Lambda(&'a FunctionRef)
19}
20
21impl From<FifoThroughputScope> for String {
22    fn from(value: FifoThroughputScope) -> Self {
23        match value {
24            FifoThroughputScope::Topic => "Topic".to_string(),
25            FifoThroughputScope::MessageGroup => "MessageGroup".to_string(),
26        }
27    }
28}
29
30type_state!(
31    TopicBuilderState,
32    StartState,
33    StandardStateWithSubscriptions,
34    FifoState,
35    FifoStateWithSubscriptions,
36);
37
38/// Builder for SNS topics.
39///
40/// Supports both standard and FIFO topics with Lambda subscriptions.
41/// FIFO topics have additional configuration for deduplication and throughput.
42///
43/// # Example
44///
45/// ```rust,no_run
46/// use rusty_cdk_core::stack::StackBuilder;
47/// use rusty_cdk_core::sns::{TopicBuilder, SubscriptionType};
48/// # use rusty_cdk_core::lambda::{FunctionBuilder, Architecture, Runtime, Zip};
49/// # use rusty_cdk_core::wrappers::*;
50/// # use rusty_cdk_macros::{memory, timeout, zip_file};
51///
52/// let mut stack_builder = StackBuilder::new();
53///
54/// // Create a simple topic without subscriptions
55/// let simple_topic = TopicBuilder::new("simple-topic")
56///     .build(&mut stack_builder);
57/// 
58/// let function = unimplemented!("create a function");
59///
60/// // Create a topic with a Lambda subscription
61/// let topic = TopicBuilder::new("my-topic")
62///     .add_subscription(SubscriptionType::Lambda(&function))
63///     .build(&mut stack_builder);
64///
65/// ```
66pub struct TopicBuilder<T: TopicBuilderState> {
67    state: PhantomData<T>,
68    id: Id,
69    topic_name: Option<String>,
70    content_based_deduplication: Option<bool>,
71    fifo_throughput_scope: Option<FifoThroughputScope>,
72    lambda_subscription_ids: Vec<(Id, String)>,
73}
74
75impl TopicBuilder<StartState> {
76    /// Creates a new SNS topic builder.
77    ///
78    /// # Arguments
79    /// * `id` - Unique identifier for the topic
80    pub fn new(id: &str) -> Self {
81        Self {
82            state: Default::default(),
83            id: Id(id.to_string()),
84            topic_name: None,
85            content_based_deduplication: None,
86            fifo_throughput_scope: None,
87            lambda_subscription_ids: vec![],
88        }
89    }
90
91    /// Adds a subscription to the topic.
92    ///
93    /// For Lambda subscriptions, automatically creates the necessary permission.
94    pub fn add_subscription(mut self, subscription: SubscriptionType) -> TopicBuilder<StandardStateWithSubscriptions> {
95        self.add_subscription_internal(subscription);
96
97        TopicBuilder {
98            state: Default::default(),
99            id: self.id,
100            topic_name: self.topic_name,
101            content_based_deduplication: self.content_based_deduplication,
102            fifo_throughput_scope: self.fifo_throughput_scope,
103            lambda_subscription_ids: self.lambda_subscription_ids,
104        }
105    }
106
107    pub fn build(self, stack_builder: &mut StackBuilder) -> TopicRef {
108        self.build_internal(false, stack_builder)
109    }
110}
111
112impl TopicBuilder<StandardStateWithSubscriptions> {
113    pub fn add_subscription(mut self, subscription: SubscriptionType) -> TopicBuilder<StandardStateWithSubscriptions> {
114        self.add_subscription_internal(subscription);
115
116        TopicBuilder {
117            state: Default::default(),
118            id: self.id,
119            topic_name: self.topic_name,
120            content_based_deduplication: self.content_based_deduplication,
121            fifo_throughput_scope: self.fifo_throughput_scope,
122            lambda_subscription_ids: self.lambda_subscription_ids,
123        }
124    }
125
126    pub fn build(self, stack_builder: &mut StackBuilder) -> TopicRef {
127        self.build_internal(false, stack_builder)
128    }
129}
130
131impl<T: TopicBuilderState> TopicBuilder<T> {
132    pub fn topic_name(self, topic_name: StringWithOnlyAlphaNumericsUnderscoresAndHyphens) -> TopicBuilder<T> {
133        TopicBuilder {
134            topic_name: Some(topic_name.0),
135            id: self.id,
136            state: Default::default(),
137            content_based_deduplication: self.content_based_deduplication,
138            fifo_throughput_scope: self.fifo_throughput_scope,
139            lambda_subscription_ids: self.lambda_subscription_ids,
140        }
141    }
142
143    pub fn fifo(self) -> TopicBuilder<FifoState> {
144        TopicBuilder {
145            state: Default::default(),
146            id: self.id,
147            topic_name: self.topic_name,
148            content_based_deduplication: self.content_based_deduplication,
149            fifo_throughput_scope: self.fifo_throughput_scope,
150            lambda_subscription_ids: self.lambda_subscription_ids,
151        }
152    }
153    
154    fn add_subscription_internal(&mut self, subscription: SubscriptionType) {
155        match subscription {
156            SubscriptionType::Lambda(l) => self.lambda_subscription_ids.push((l.get_id().clone(), l.get_resource_id().to_string()))
157        };
158    }
159    
160    fn build_internal(self, fifo: bool, stack_builder: &mut StackBuilder) -> TopicRef {
161        let topic_resource_id = Resource::generate_id("SnsTopic");
162        
163        self.lambda_subscription_ids.iter().for_each(|(to_subscribe_id, to_subscribe_resource_id)| {
164            let subscription_id = Id::combine_ids(&self.id, to_subscribe_id);
165            let subscription_resource_id = Resource::generate_id("SnsSubscription");
166            
167            PermissionBuilder::new(&Id::generate_id(&subscription_id, "Permission"), LambdaPermissionAction("lambda:InvokeFunction".to_string()), get_arn(to_subscribe_resource_id), "sns.amazonaws.com")
168                .source_arn(get_ref(&topic_resource_id))
169                .build(stack_builder);
170
171            let subscription = Subscription {
172                id: subscription_id,
173                resource_id: subscription_resource_id,
174                r#type: "AWS::SNS::Subscription".to_string(),
175                properties: SnsSubscriptionProperties {
176                    protocol: "lambda".to_string(),
177                    endpoint: get_arn(to_subscribe_resource_id),
178                    topic_arn: get_ref(&topic_resource_id),
179                },
180            };
181
182            stack_builder.add_resource(subscription);
183        });
184        
185        let properties = TopicProperties {
186            topic_name: self.topic_name,
187            fifo_topic: Some(fifo),
188            content_based_deduplication: self.content_based_deduplication,
189            fifo_throughput_scope: self.fifo_throughput_scope.map(Into::into),
190        };
191        
192        stack_builder.add_resource(Topic {
193            id: self.id,
194            resource_id: topic_resource_id.to_string(),
195            r#type: "AWS::SNS::Topic".to_string(),
196            properties,
197        });
198
199        TopicRef::new(topic_resource_id)
200    }
201}
202
203impl TopicBuilder<FifoState> {
204    pub fn fifo_throughput_scope(self, scope: FifoThroughputScope) -> TopicBuilder<FifoState> {
205        Self {
206            fifo_throughput_scope: Some(scope),
207            ..self
208        }
209    }
210
211    pub fn content_based_deduplication(self, content_based_deduplication: bool) -> TopicBuilder<FifoState> {
212        Self {
213            content_based_deduplication: Some(content_based_deduplication),
214            ..self
215        }
216    }
217
218    pub fn add_subscription(mut self, subscription: SubscriptionType) -> TopicBuilder<FifoStateWithSubscriptions> {
219        self.add_subscription_internal(subscription);
220
221        TopicBuilder {
222            state: Default::default(),
223            id: self.id,
224            topic_name: self.topic_name,
225            content_based_deduplication: self.content_based_deduplication,
226            fifo_throughput_scope: self.fifo_throughput_scope,
227            lambda_subscription_ids: self.lambda_subscription_ids,
228        }
229    }
230
231    /// Builds the FIFO topic and adds it to the stack.
232    ///
233    /// Automatically appends the required ".fifo" suffix to the topic name if not already present.
234    pub fn build(mut self, stack_builder: &mut StackBuilder) -> TopicRef {
235        if let Some(ref name) = self.topic_name
236            && !name.ends_with(FIFO_SUFFIX) {
237                self.topic_name = Some(format!("{}{}", name, FIFO_SUFFIX));
238            }
239        self.build_internal(true, stack_builder)
240    }
241}
242
243impl TopicBuilder<FifoStateWithSubscriptions> {
244    pub fn fifo_throughput_scope(self, scope: FifoThroughputScope) -> TopicBuilder<FifoStateWithSubscriptions> {
245        Self {
246            fifo_throughput_scope: Some(scope),
247            ..self
248        }
249    }
250
251    pub fn content_based_deduplication(self, content_based_deduplication: bool) -> TopicBuilder<FifoStateWithSubscriptions> {
252        Self {
253            content_based_deduplication: Some(content_based_deduplication),
254            ..self
255        }
256    }
257
258    pub fn add_subscription(mut self, subscription: SubscriptionType) -> TopicBuilder<FifoStateWithSubscriptions> {
259        self.add_subscription_internal(subscription);
260
261        TopicBuilder {
262            state: Default::default(),
263            id: self.id,
264            topic_name: self.topic_name,
265            content_based_deduplication: self.content_based_deduplication,
266            fifo_throughput_scope: self.fifo_throughput_scope,
267            lambda_subscription_ids: self.lambda_subscription_ids,
268        }
269    }
270    
271    /// Builds the FIFO topic with subscriptions and adds it to the stack.
272    ///
273    /// Automatically appends the required ".fifo" suffix to the topic name if not already present.
274    /// Creates Lambda permissions for all subscriptions.
275    pub fn build(mut self, stack_builder: &mut StackBuilder) -> TopicRef {
276        if let Some(ref name) = self.topic_name
277            && !name.ends_with(FIFO_SUFFIX) {
278                self.topic_name = Some(format!("{}{}", name, FIFO_SUFFIX));
279            }
280        self.build_internal(true, stack_builder)
281    }
282}