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