rusty_cdk_core/events/
builder.rs

1use std::marker::PhantomData;
2use serde_json::Value;
3use crate::events::{FlexibleTimeWindow, RetryPolicy, Schedule, ScheduleProperties, ScheduleRef, Target};
4use crate::lambda::FunctionRef;
5use crate::shared::Id;
6use crate::sns::TopicRef;
7use crate::sqs::QueueRef;
8use crate::stack::{Resource, StackBuilder};
9use crate::type_state;
10use crate::wrappers::{MaxFlexibleTimeWindow, RetryPolicyEventAge, RetryPolicyRetries, ScheduleAtExpression, ScheduleCronExpression, ScheduleName, ScheduleRateExpression};
11
12type_state!(
13    ScheduleBuilderState,
14    StartState,
15    OneTimeScheduleState,
16    RepeatedScheduleState,
17);
18
19#[derive(Debug)]
20pub enum State {
21    Enabled,
22    Disabled,
23}
24
25impl From<State> for String {
26    fn from(value: State) -> String {
27        match value {
28            State::Enabled => "ENABLED".to_string(),
29            State::Disabled => "DISABLED".to_string(),
30        }
31    }
32}
33
34pub struct ScheduleBuilder<T: ScheduleBuilderState> {
35    phantom_data: PhantomData<T>,
36    id: Id,
37    start_date: Option<String>,
38    end_date: Option<String>,
39    flexible_time_window: FlexibleTimeWindow,
40    group_name: Option<String>,
41    name: Option<String>,
42    state: Option<String>,
43    schedule_expression: Option<String>,
44    target: Target
45}
46
47impl<T: ScheduleBuilderState> ScheduleBuilder<T> {
48    pub fn name(self, name: ScheduleName) -> Self {
49        Self {
50            name: Some(name.0),
51            ..self
52        }
53    }
54    
55    pub fn group_name(self, group_name: ScheduleName) -> Self {
56        Self {
57            group_name: Some(group_name.0),
58            ..self
59        }
60    }
61    
62    pub fn state(self, state: State) -> Self {
63        Self {
64            state: Some(state.into()),
65            ..self
66        }
67    }
68    
69    fn build_internal(self, stack_builder: &mut StackBuilder) -> ScheduleRef {
70        let resource_id = Resource::generate_id("Schedule");
71        stack_builder.add_resource(Schedule {
72            id: self.id,
73            resource_id: resource_id.clone(),
74            r#type: "AWS::Scheduler::Schedule".to_string(),
75            properties: ScheduleProperties {
76                start_date: self.start_date,
77                end_date: self.end_date,
78                flexible_time_window: self.flexible_time_window,
79                group_name: self.group_name,
80                name: self.name,
81                state: self.state,
82                schedule_expression: self.schedule_expression.expect("schedule expression to be present, enforced by builder"),
83                target: self.target,
84            },
85        });
86        
87        ScheduleRef::new(resource_id)
88    }
89}
90
91impl ScheduleBuilder<StartState> {
92    pub fn new(id: &str, target: Target, flexible_time_window: FlexibleTimeWindow) -> ScheduleBuilder<StartState> {
93        ScheduleBuilder {
94            phantom_data: Default::default(),
95            id: Id(id.to_string()),
96            flexible_time_window,
97            target,
98            start_date: None,
99            end_date: None,
100            group_name: None,
101            name: None,
102            state: None,
103            schedule_expression: None,
104        }
105    }
106    
107    pub fn one_time_schedule(self, expression: ScheduleAtExpression) -> ScheduleBuilder<OneTimeScheduleState> {
108        let one_time_schedule = format!("at({})", expression.0);
109        ScheduleBuilder {
110            phantom_data: Default::default(),
111            schedule_expression: Some(one_time_schedule),
112            id: self.id,
113            flexible_time_window: self.flexible_time_window,
114            group_name: self.group_name,
115            name: self.name,
116            state: self.state,
117            target: self.target,
118            start_date: None,
119            end_date: None,
120        }
121    }
122    
123    pub fn rate_schedule(self, expression: ScheduleRateExpression) -> ScheduleBuilder<RepeatedScheduleState> {
124        let rate = format!("rate({} {})", expression.0, expression.1);
125        ScheduleBuilder {
126            phantom_data: Default::default(),
127            schedule_expression: Some(rate),
128            id: self.id,
129            flexible_time_window: self.flexible_time_window,
130            group_name: self.group_name,
131            name: self.name,
132            state: self.state,
133            target: self.target,
134            start_date: self.start_date,
135            end_date: self.end_date,
136        }
137    }
138    
139    pub fn cron_schedule(self, expression: ScheduleCronExpression) -> ScheduleBuilder<RepeatedScheduleState> {
140        let schedule = format!("cron({})", expression.0);
141        ScheduleBuilder {
142            phantom_data: Default::default(),
143            schedule_expression: Some(schedule),
144            id: self.id,
145            flexible_time_window: self.flexible_time_window,
146            group_name: self.group_name,
147            name: self.name,
148            state: self.state,
149            target: self.target,
150            start_date: self.start_date,
151            end_date: self.end_date,
152        }
153    }
154}
155
156impl ScheduleBuilder<OneTimeScheduleState> {
157    pub fn build(self, stack_builder: &mut StackBuilder) -> ScheduleRef {
158        self.build_internal(stack_builder)
159    }
160}
161
162impl ScheduleBuilder<RepeatedScheduleState> {
163    // TODO better validation
164    pub fn start_date(self, start_date: String) -> Self {
165        Self {
166            start_date: Some(start_date),
167            ..self
168        }
169    }
170
171    // TODO better validation
172    pub fn end_date(self, end_date: String) -> Self {
173        Self {
174            end_date: Some(end_date),
175            ..self
176        }
177    }
178    
179    pub fn build(self, stack_builder: &mut StackBuilder) -> ScheduleRef {
180        self.build_internal(stack_builder)
181    }
182}
183
184type_state!(
185    TargetBuilderState,
186    TargetStartState,
187    JsonTargetState,
188    NormalTargetState,
189);
190
191pub struct TargetBuilder<T: TargetBuilderState> {
192    phantom_data: PhantomData<T>,
193    target_arn: Value,
194    role_arn: Value, // TODO instead this should accept a RoleRef. and if it is a remote role, it should return it's arn without the whole !Ref bit (which is not needed)
195    input: Option<String>,
196    retry_policy: Option<RetryPolicy>,
197}
198
199pub enum JsonTarget<'a> {
200    Lambda(&'a FunctionRef)
201    // AWS SF
202    // EventBridge
203}
204
205pub enum NormalTarget<'a> {
206    Sqs(&'a QueueRef),
207    Sns(&'a TopicRef),
208    Other(Value)
209}
210
211impl TargetBuilder<TargetStartState> {
212    /// Target that accepts any string input. This is all targets *except*
213    /// - Lambda
214    /// - Step Functions
215    /// - EventBridge
216    pub fn new_normal_target(target: NormalTarget, role_arn: Value) -> TargetBuilder<NormalTargetState> {
217        let arn = match target {
218            NormalTarget::Sqs(r) => r.get_arn(),
219            NormalTarget::Sns(r) => r.get_arn(),
220            NormalTarget::Other(r) => r,
221        };
222        TargetBuilder {
223            phantom_data: Default::default(),
224            target_arn: arn,
225            role_arn,
226            input: None,
227            retry_policy: None,
228        }
229    }
230
231    /// Target that requires the input to be valid JSON
232    /// - Lambda
233    /// - Step Functions
234    /// - EventBridge
235    pub fn new_json_target(target: JsonTarget, role_arn: Value) -> TargetBuilder<JsonTargetState> {
236        let target_arn = match target {
237            JsonTarget::Lambda(l) => l.get_arn(),
238        };
239        TargetBuilder {
240            phantom_data: Default::default(),
241            target_arn,
242            role_arn,
243            input: None,
244            retry_policy: None,
245        }
246    }
247}
248
249impl<T: TargetBuilderState> TargetBuilder<T> {
250    pub fn retry_policy(self, retry_policy: RetryPolicy) -> TargetBuilder<T> {
251        TargetBuilder {
252            retry_policy: Some(retry_policy),
253            phantom_data: Default::default(),
254            target_arn: self.target_arn,
255            role_arn: self.role_arn,
256            input: self.input,
257        }
258    }
259
260    pub fn build(self) -> Target {
261        Target {
262            arn: self.target_arn,
263            role_arn: self.role_arn,
264            input: self.input,
265            retry_policy: self.retry_policy,
266        }
267    }
268}
269
270impl TargetBuilder<NormalTargetState> {
271    pub fn input(self, input: String) -> Self {
272        Self {
273            input: Some(input),
274            ..self
275        }
276    }
277}
278
279impl TargetBuilder<JsonTargetState> {
280    pub fn input(self, input: Value) -> Self {
281        Self {
282            input: Some(input.to_string()),
283            ..self
284        }
285    }
286}
287
288pub enum Mode {
289    Off,
290    Flexible(MaxFlexibleTimeWindow)
291}
292
293pub struct FlexibleTimeWindowBuilder {
294    maximum_window_in_minutes: Option<u16>,
295    mode: String,
296}
297
298impl FlexibleTimeWindowBuilder {
299    pub fn new(mode: Mode) -> Self {
300        match mode {
301            Mode::Off => {
302                Self {
303                    maximum_window_in_minutes: None,
304                    mode: "OFF".to_string(),
305                }
306            }
307            Mode::Flexible(max) => {
308                Self {
309                    maximum_window_in_minutes: Some(max.0),
310                    mode: "FLEXIBLE".to_string(),
311                }
312            }
313        }
314    }
315    
316    pub fn build(self) -> FlexibleTimeWindow {
317        FlexibleTimeWindow {
318            maximum_window_in_minutes: self.maximum_window_in_minutes,
319            mode: self.mode,
320        }
321    }
322}
323
324pub struct RetryPolicyBuilder {
325    maximum_event_age_in_seconds: Option<u32>, 
326    maximum_retry_attempts: Option<u8>
327}
328
329impl RetryPolicyBuilder {
330    pub fn new() -> Self {
331        Self {
332            maximum_event_age_in_seconds: None,
333            maximum_retry_attempts: None,
334        }    
335    }
336    
337    pub fn maximum_event_age_in_seconds(self, maximum_event_age_in_seconds: RetryPolicyEventAge) -> Self {
338        Self {
339            maximum_event_age_in_seconds: Some(maximum_event_age_in_seconds.0),
340            ..self
341        }
342    }
343    
344    pub fn maximum_retry_attempts(self, maximum_retry_attempts: RetryPolicyRetries) -> Self {
345        Self {
346            maximum_retry_attempts: Some(maximum_retry_attempts.0),
347            ..self
348        }
349    }
350    
351    pub fn build(self) -> RetryPolicy {
352        RetryPolicy {
353            maximum_event_age_in_seconds: self.maximum_event_age_in_seconds,
354            maximum_retry_attempts: self.maximum_retry_attempts,
355        }
356    }
357}