1use crate::dynamodb::{AttributeDefinition, KeySchema, Table, TableProperties, TableType};
2use crate::dynamodb::{OnDemandThroughput, ProvisionedThroughput, TableRef};
3use crate::shared::{DeletionPolicy, Id, UpdateDeletePolicyDTO, UpdateReplacePolicy};
4use crate::stack::{Resource, StackBuilder};
5use crate::wrappers::{NonZeroNumber, StringWithOnlyAlphaNumericsAndUnderscores};
6use std::marker::PhantomData;
7use crate::type_state;
8
9#[derive(Debug, Clone, PartialEq)]
10pub enum BillingMode {
11 PayPerRequest,
12 Provisioned,
13}
14
15impl From<BillingMode> for String {
16 fn from(value: BillingMode) -> Self {
17 match value {
18 BillingMode::PayPerRequest => "PAY_PER_REQUEST".to_string(),
19 BillingMode::Provisioned => "PROVISIONED".to_string(),
20 }
21 }
22}
23
24#[derive(Debug, Clone)]
25pub enum AttributeType {
26 String,
27 Number,
28 Binary,
29}
30
31impl From<AttributeType> for String {
32 fn from(value: AttributeType) -> Self {
33 match value {
34 AttributeType::String => "S".to_string(),
35 AttributeType::Number => "N".to_string(),
36 AttributeType::Binary => "B".to_string(),
37 }
38 }
39}
40
41pub struct Key {
42 key: String,
43 key_type: AttributeType,
44}
45
46impl Key {
47 pub fn new(key: StringWithOnlyAlphaNumericsAndUnderscores, key_type: AttributeType) -> Self {
48 Self { key: key.0, key_type }
49 }
50}
51
52type_state!(
53 TableBuilderState,
54 StartState,
55 ProvisionedStateStart,
56 ProvisionedStateReadSet,
57 ProvisionedStateWriteSet,
58 PayPerRequestState,
59);
60
61pub struct TableBuilder<T: TableBuilderState> {
92 state: PhantomData<T>,
93 id: Id,
94 table_name: Option<String>,
95 partition_key: Option<Key>,
96 sort_key: Option<Key>,
97 billing_mode: Option<BillingMode>,
98 read_capacity: Option<u32>,
99 write_capacity: Option<u32>,
100 max_read_capacity: Option<u32>,
101 max_write_capacity: Option<u32>,
102 deletion_policy: Option<String>,
103 update_replace_policy: Option<String>,
104}
105
106impl TableBuilder<StartState> {
107 pub fn new(id: &str, key: Key) -> Self {
113 TableBuilder {
114 state: Default::default(),
115 id: Id(id.to_string()),
116 table_name: None,
117 partition_key: Some(key),
118 sort_key: None,
119 billing_mode: None,
120 read_capacity: None,
121 write_capacity: None,
122 max_read_capacity: None,
123 max_write_capacity: None,
124 deletion_policy: None,
125 update_replace_policy: None,
126 }
127 }
128}
129
130impl<T: TableBuilderState> TableBuilder<T> {
131 pub fn sort_key(self, key: Key) -> Self {
133 Self {
134 sort_key: Some(key),
135 ..self
136 }
137 }
138
139 pub fn table_name(self, name: StringWithOnlyAlphaNumericsAndUnderscores) -> Self {
141 Self {
142 table_name: Some(name.0),
143 ..self
144 }
145 }
146
147 pub fn update_replace_and_deletion_policy(self, update_replace_policy: UpdateReplacePolicy, deletion_policy: DeletionPolicy) -> Self {
148 Self {
149 deletion_policy: Some(deletion_policy.into()),
150 update_replace_policy: Some(update_replace_policy.into()),
151 ..self
152 }
153 }
154
155 pub fn pay_per_request_billing(self) -> TableBuilder<PayPerRequestState> {
159 TableBuilder {
160 billing_mode: Some(BillingMode::PayPerRequest),
161 state: Default::default(),
162 id: self.id,
163 table_name: self.table_name,
164 partition_key: self.partition_key,
165 sort_key: self.sort_key,
166 max_read_capacity: self.max_read_capacity,
167 max_write_capacity: self.max_write_capacity,
168 deletion_policy: self.deletion_policy,
169 update_replace_policy: self.update_replace_policy,
170 read_capacity: None,
171 write_capacity: None,
172 }
173 }
174
175 pub fn provisioned_billing(self) -> TableBuilder<ProvisionedStateStart> {
179 TableBuilder {
180 billing_mode: Some(BillingMode::Provisioned),
181 state: Default::default(),
182 id: self.id,
183 table_name: self.table_name,
184 partition_key: self.partition_key,
185 sort_key: self.sort_key,
186 read_capacity: self.read_capacity,
187 write_capacity: self.write_capacity,
188 deletion_policy: self.deletion_policy,
189 update_replace_policy: self.update_replace_policy,
190 max_read_capacity: None,
191 max_write_capacity: None,
192 }
193 }
194
195 fn build_internal(self, stack_builder: &mut StackBuilder) -> TableRef {
196 let Key { key, key_type } = self.partition_key.unwrap();
197 let mut key_schema = vec![KeySchema {
198 attribute_name: key.clone(),
199 key_type: "HASH".to_string(),
200 }];
201 let mut key_attributes = vec![AttributeDefinition {
202 attribute_name: key,
203 attribute_type: key_type.into(),
204 }];
205
206 if let Some(Key { key, key_type }) = self.sort_key {
207 let sort_key = KeySchema {
208 attribute_name: key.clone(),
209 key_type: "RANGE".to_string(),
210 };
211 let sort_key_attributes = AttributeDefinition {
212 attribute_name: key,
213 attribute_type: key_type.into(),
214 };
215 key_schema.push(sort_key);
216 key_attributes.push(sort_key_attributes);
217 }
218
219 let billing_mode = self
220 .billing_mode
221 .expect("billing mode should be set, as this is enforced by the builder");
222
223 let provisioned_throughput = if billing_mode == BillingMode::Provisioned {
224 Some(ProvisionedThroughput {
225 read_capacity: self
226 .read_capacity
227 .expect("for provisioned billing mode, read capacity should be set"),
228 write_capacity: self
229 .write_capacity
230 .expect("for provisioned billing mode, write capacity should be set"),
231 })
232 } else {
233 None
234 };
235
236 let on_demand_throughput = if billing_mode == BillingMode::PayPerRequest {
237 Some(OnDemandThroughput {
238 max_read_capacity: self.max_read_capacity,
239 max_write_capacity: self.max_write_capacity,
240 })
241 } else {
242 None
243 };
244
245 let properties = TableProperties {
246 key_schema,
247 attribute_definitions: key_attributes,
248 billing_mode: billing_mode.into(),
249 provisioned_throughput,
250 on_demand_throughput,
251 };
252
253 let resource_id = Resource::generate_id("DynamoDBTable");
254 stack_builder.add_resource(Table {
255 id: self.id,
256 resource_id: resource_id.clone(),
257 r#type: TableType::TableType,
258 properties,
259 update_delete_policy_dto: UpdateDeletePolicyDTO { deletion_policy: self.deletion_policy, update_replace_policy: self.update_replace_policy },
260 });
261
262 TableRef::internal_new(resource_id)
263 }
264}
265
266impl TableBuilder<PayPerRequestState> {
267 pub fn max_read_capacity(self, capacity: NonZeroNumber) -> Self {
269 Self {
270 max_read_capacity: Some(capacity.0),
271 ..self
272 }
273 }
274
275 pub fn max_write_capacity(self, capacity: NonZeroNumber) -> Self {
277 Self {
278 max_write_capacity: Some(capacity.0),
279 ..self
280 }
281 }
282
283 pub fn build(self, stack_builder: &mut StackBuilder) -> TableRef {
284 self.build_internal(stack_builder)
285 }
286}
287
288impl TableBuilder<ProvisionedStateStart> {
289 pub fn read_capacity(self, capacity: NonZeroNumber) -> TableBuilder<ProvisionedStateReadSet> {
291 TableBuilder {
292 read_capacity: Some(capacity.0),
293 state: Default::default(),
294 id: self.id,
295 table_name: self.table_name,
296 partition_key: self.partition_key,
297 sort_key: self.sort_key,
298 billing_mode: self.billing_mode,
299 write_capacity: self.write_capacity,
300 max_read_capacity: self.read_capacity,
301 max_write_capacity: self.max_write_capacity,
302 deletion_policy: self.deletion_policy,
303 update_replace_policy: self.update_replace_policy,
304 }
305 }
306}
307
308impl TableBuilder<ProvisionedStateReadSet> {
309 pub fn write_capacity(self, capacity: NonZeroNumber) -> TableBuilder<ProvisionedStateWriteSet> {
311 TableBuilder {
312 write_capacity: Some(capacity.0),
313 state: Default::default(),
314 id: self.id,
315 table_name: self.table_name,
316 partition_key: self.partition_key,
317 sort_key: self.sort_key,
318 billing_mode: self.billing_mode,
319 read_capacity: self.read_capacity,
320 max_read_capacity: self.max_read_capacity,
321 max_write_capacity: self.max_write_capacity,
322 deletion_policy: self.deletion_policy,
323 update_replace_policy: self.update_replace_policy,
324 }
325 }
326}
327
328impl TableBuilder<ProvisionedStateWriteSet> {
329 pub fn build(self, stack_builder: &mut StackBuilder) -> TableRef {
330 self.build_internal(stack_builder)
331 }
332}