1use crate::iam::PolicyDocument;
2use crate::intrinsic::{get_arn, get_ref};
3use crate::lambda::{FunctionRef, PermissionBuilder};
4use crate::shared::Id;
5use crate::sns::{SnsSubscriptionProperties, Subscription, Topic, TopicPolicy, TopicPolicyProperties, TopicPolicyRef, TopicProperties, TopicRef};
6use crate::stack::{Resource, StackBuilder};
7use crate::type_state;
8use crate::wrappers::{LambdaPermissionAction, StringWithOnlyAlphaNumericsUnderscoresAndHyphens};
9use serde_json::Value;
10use std::marker::PhantomData;
11
12const FIFO_SUFFIX: &str = ".fifo";
13
14pub enum FifoThroughputScope {
15 Topic,
16 MessageGroup
17}
18
19pub enum SubscriptionType<'a> {
20 Lambda(&'a FunctionRef)
21}
22
23impl From<FifoThroughputScope> for String {
24 fn from(value: FifoThroughputScope) -> Self {
25 match value {
26 FifoThroughputScope::Topic => "Topic".to_string(),
27 FifoThroughputScope::MessageGroup => "MessageGroup".to_string(),
28 }
29 }
30}
31
32type_state!(
33 TopicBuilderState,
34 StartState,
35 StandardStateWithSubscriptions,
36 FifoState,
37 FifoStateWithSubscriptions,
38);
39
40pub struct TopicBuilder<T: TopicBuilderState> {
69 state: PhantomData<T>,
70 id: Id,
71 topic_name: Option<String>,
72 content_based_deduplication: Option<bool>,
73 fifo_throughput_scope: Option<FifoThroughputScope>,
74 lambda_subscription_ids: Vec<(Id, String)>,
75}
76
77impl TopicBuilder<StartState> {
78 pub fn new(id: &str) -> Self {
83 Self {
84 state: Default::default(),
85 id: Id(id.to_string()),
86 topic_name: None,
87 content_based_deduplication: None,
88 fifo_throughput_scope: None,
89 lambda_subscription_ids: vec![],
90 }
91 }
92
93 pub fn add_subscription(mut self, subscription: SubscriptionType) -> TopicBuilder<StandardStateWithSubscriptions> {
97 self.add_subscription_internal(subscription);
98
99 TopicBuilder {
100 state: Default::default(),
101 id: self.id,
102 topic_name: self.topic_name,
103 content_based_deduplication: self.content_based_deduplication,
104 fifo_throughput_scope: self.fifo_throughput_scope,
105 lambda_subscription_ids: self.lambda_subscription_ids,
106 }
107 }
108
109 pub fn build(self, stack_builder: &mut StackBuilder) -> TopicRef {
110 self.build_internal(false, stack_builder)
111 }
112}
113
114impl TopicBuilder<StandardStateWithSubscriptions> {
115 pub fn add_subscription(mut self, subscription: SubscriptionType) -> TopicBuilder<StandardStateWithSubscriptions> {
116 self.add_subscription_internal(subscription);
117
118 TopicBuilder {
119 state: Default::default(),
120 id: self.id,
121 topic_name: self.topic_name,
122 content_based_deduplication: self.content_based_deduplication,
123 fifo_throughput_scope: self.fifo_throughput_scope,
124 lambda_subscription_ids: self.lambda_subscription_ids,
125 }
126 }
127
128 pub fn build(self, stack_builder: &mut StackBuilder) -> TopicRef {
129 self.build_internal(false, stack_builder)
130 }
131}
132
133impl<T: TopicBuilderState> TopicBuilder<T> {
134 pub fn topic_name(self, topic_name: StringWithOnlyAlphaNumericsUnderscoresAndHyphens) -> TopicBuilder<T> {
135 TopicBuilder {
136 topic_name: Some(topic_name.0),
137 id: self.id,
138 state: Default::default(),
139 content_based_deduplication: self.content_based_deduplication,
140 fifo_throughput_scope: self.fifo_throughput_scope,
141 lambda_subscription_ids: self.lambda_subscription_ids,
142 }
143 }
144
145 pub fn fifo(self) -> TopicBuilder<FifoState> {
146 TopicBuilder {
147 state: Default::default(),
148 id: self.id,
149 topic_name: self.topic_name,
150 content_based_deduplication: self.content_based_deduplication,
151 fifo_throughput_scope: self.fifo_throughput_scope,
152 lambda_subscription_ids: self.lambda_subscription_ids,
153 }
154 }
155
156 fn add_subscription_internal(&mut self, subscription: SubscriptionType) {
157 match subscription {
158 SubscriptionType::Lambda(l) => self.lambda_subscription_ids.push((l.get_id().clone(), l.get_resource_id().to_string()))
159 };
160 }
161
162 fn build_internal(self, fifo: bool, stack_builder: &mut StackBuilder) -> TopicRef {
163 let topic_resource_id = Resource::generate_id("SnsTopic");
164
165 self.lambda_subscription_ids.iter().for_each(|(to_subscribe_id, to_subscribe_resource_id)| {
166 let subscription_id = Id::combine_ids(&self.id, to_subscribe_id);
167 let subscription_resource_id = Resource::generate_id("SnsSubscription");
168
169 PermissionBuilder::new(&Id::generate_id(&subscription_id, "Permission"), LambdaPermissionAction("lambda:InvokeFunction".to_string()), get_arn(to_subscribe_resource_id), "sns.amazonaws.com")
170 .source_arn(get_ref(&topic_resource_id))
171 .build(stack_builder);
172
173 let subscription = Subscription {
174 id: subscription_id,
175 resource_id: subscription_resource_id,
176 r#type: "AWS::SNS::Subscription".to_string(),
177 properties: SnsSubscriptionProperties {
178 protocol: "lambda".to_string(),
179 endpoint: get_arn(to_subscribe_resource_id),
180 topic_arn: get_ref(&topic_resource_id),
181 },
182 };
183
184 stack_builder.add_resource(subscription);
185 });
186
187 let properties = TopicProperties {
188 topic_name: self.topic_name,
189 fifo_topic: Some(fifo),
190 content_based_deduplication: self.content_based_deduplication,
191 fifo_throughput_scope: self.fifo_throughput_scope.map(Into::into),
192 };
193
194 stack_builder.add_resource(Topic {
195 id: self.id,
196 resource_id: topic_resource_id.to_string(),
197 r#type: "AWS::SNS::Topic".to_string(),
198 properties,
199 });
200
201 TopicRef::new(topic_resource_id)
202 }
203}
204
205impl TopicBuilder<FifoState> {
206 pub fn fifo_throughput_scope(self, scope: FifoThroughputScope) -> TopicBuilder<FifoState> {
207 Self {
208 fifo_throughput_scope: Some(scope),
209 ..self
210 }
211 }
212
213 pub fn content_based_deduplication(self, content_based_deduplication: bool) -> TopicBuilder<FifoState> {
214 Self {
215 content_based_deduplication: Some(content_based_deduplication),
216 ..self
217 }
218 }
219
220 pub fn add_subscription(mut self, subscription: SubscriptionType) -> TopicBuilder<FifoStateWithSubscriptions> {
221 self.add_subscription_internal(subscription);
222
223 TopicBuilder {
224 state: Default::default(),
225 id: self.id,
226 topic_name: self.topic_name,
227 content_based_deduplication: self.content_based_deduplication,
228 fifo_throughput_scope: self.fifo_throughput_scope,
229 lambda_subscription_ids: self.lambda_subscription_ids,
230 }
231 }
232
233 pub fn build(mut self, stack_builder: &mut StackBuilder) -> TopicRef {
237 if let Some(ref name) = self.topic_name
238 && !name.ends_with(FIFO_SUFFIX) {
239 self.topic_name = Some(format!("{}{}", name, FIFO_SUFFIX));
240 }
241 self.build_internal(true, stack_builder)
242 }
243}
244
245impl TopicBuilder<FifoStateWithSubscriptions> {
246 pub fn fifo_throughput_scope(self, scope: FifoThroughputScope) -> TopicBuilder<FifoStateWithSubscriptions> {
247 Self {
248 fifo_throughput_scope: Some(scope),
249 ..self
250 }
251 }
252
253 pub fn content_based_deduplication(self, content_based_deduplication: bool) -> TopicBuilder<FifoStateWithSubscriptions> {
254 Self {
255 content_based_deduplication: Some(content_based_deduplication),
256 ..self
257 }
258 }
259
260 pub fn add_subscription(mut self, subscription: SubscriptionType) -> TopicBuilder<FifoStateWithSubscriptions> {
261 self.add_subscription_internal(subscription);
262
263 TopicBuilder {
264 state: Default::default(),
265 id: self.id,
266 topic_name: self.topic_name,
267 content_based_deduplication: self.content_based_deduplication,
268 fifo_throughput_scope: self.fifo_throughput_scope,
269 lambda_subscription_ids: self.lambda_subscription_ids,
270 }
271 }
272
273 pub fn build(mut self, stack_builder: &mut StackBuilder) -> TopicRef {
278 if let Some(ref name) = self.topic_name
279 && !name.ends_with(FIFO_SUFFIX) {
280 self.topic_name = Some(format!("{}{}", name, FIFO_SUFFIX));
281 }
282 self.build_internal(true, stack_builder)
283 }
284}
285
286pub struct TopicPolicyBuilder {
287 id: Id,
288 doc: PolicyDocument,
289 topics: Vec<Value>
290}
291
292impl TopicPolicyBuilder {
293 pub fn new(id: &str, doc: PolicyDocument, topics: Vec<&TopicRef>) -> Self {
305 Self::new_with_values(id, doc, topics.into_iter().map(|v| v.get_ref()).collect())
306 }
307
308 pub(crate) fn new_with_values(id: &str, doc: PolicyDocument, topics: Vec<Value>) -> Self {
309 Self {
310 id: Id(id.to_string()),
311 doc,
312 topics,
313 }
314 }
315
316 pub fn build(self, stack_builder: &mut StackBuilder) -> TopicPolicyRef {
317 let resource_id = Resource::generate_id("TopicPolicy");
318 stack_builder.add_resource(TopicPolicy {
319 id: self.id.clone(),
320 resource_id: resource_id.clone(),
321 r#type: "AWS::SNS::TopicPolicy".to_string(),
322 properties: TopicPolicyProperties {
323 doc: self.doc,
324 topics: self.topics,
325 },
326 });
327
328 TopicPolicyRef::new(self.id, resource_id)
329 }
330}
331