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
38pub 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 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 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 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 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}