1use crate::dynamodb::{AttributeDefinition, KeySchema, Table, TableProperties};
2use crate::dynamodb::{OnDemandThroughput, ProvisionedThroughput, TableRef};
3use crate::shared::Id;
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> {
90 state: PhantomData<T>,
91 id: Id,
92 table_name: Option<String>,
93 partition_key: Option<Key>,
94 sort_key: Option<Key>,
95 billing_mode: Option<BillingMode>,
96 read_capacity: Option<u32>,
97 write_capacity: Option<u32>,
98 max_read_capacity: Option<u32>,
99 max_write_capacity: Option<u32>,
100}
101
102impl TableBuilder<StartState> {
103 pub fn new(id: &str, key: Key) -> Self {
109 TableBuilder {
110 state: Default::default(),
111 id: Id(id.to_string()),
112 table_name: None,
113 partition_key: Some(key),
114 sort_key: None,
115 billing_mode: None,
116 read_capacity: None,
117 write_capacity: None,
118 max_read_capacity: None,
119 max_write_capacity: None,
120 }
121 }
122}
123
124impl<T: TableBuilderState> TableBuilder<T> {
125 pub fn sort_key(self, key: Key) -> Self {
126 Self {
127 sort_key: Some(key),
128 ..self
129 }
130 }
131
132 pub fn table_name(self, name: StringWithOnlyAlphaNumericsAndUnderscores) -> Self {
133 Self {
134 table_name: Some(name.0),
135 ..self
136 }
137 }
138
139 pub fn pay_per_request_billing(self) -> TableBuilder<PayPerRequestState> {
143 TableBuilder {
144 billing_mode: Some(BillingMode::PayPerRequest),
145 state: Default::default(),
146 id: self.id,
147 table_name: self.table_name,
148 partition_key: self.partition_key,
149 sort_key: self.sort_key,
150 max_read_capacity: self.max_read_capacity,
151 max_write_capacity: self.max_write_capacity,
152 read_capacity: None,
153 write_capacity: None,
154 }
155 }
156
157 pub fn provisioned_billing(self) -> TableBuilder<ProvisionedStateStart> {
161 TableBuilder {
162 billing_mode: Some(BillingMode::Provisioned),
163 state: Default::default(),
164 id: self.id,
165 table_name: self.table_name,
166 partition_key: self.partition_key,
167 sort_key: self.sort_key,
168 read_capacity: self.read_capacity,
169 write_capacity: self.write_capacity,
170 max_read_capacity: None,
171 max_write_capacity: None,
172 }
173 }
174
175 fn build_internal(self, stack_builder: &mut StackBuilder) -> TableRef {
176 let Key { key, key_type } = self.partition_key.unwrap();
177 let mut key_schema = vec![KeySchema {
178 attribute_name: key.clone(),
179 key_type: "HASH".to_string(),
180 }];
181 let mut key_attributes = vec![AttributeDefinition {
182 attribute_name: key,
183 attribute_type: key_type.into(),
184 }];
185
186 if let Some(Key { key, key_type }) = self.sort_key {
187 let sort_key = KeySchema {
188 attribute_name: key.clone(),
189 key_type: "RANGE".to_string(),
190 };
191 let sort_key_attributes = AttributeDefinition {
192 attribute_name: key,
193 attribute_type: key_type.into(),
194 };
195 key_schema.push(sort_key);
196 key_attributes.push(sort_key_attributes);
197 }
198
199 let billing_mode = self
200 .billing_mode
201 .expect("billing mode should be set, as this is enforced by the builder");
202
203 let provisioned_throughput = if billing_mode == BillingMode::Provisioned {
204 Some(ProvisionedThroughput {
205 read_capacity: self
206 .read_capacity
207 .expect("for provisioned billing mode, read capacity should be set"),
208 write_capacity: self
209 .write_capacity
210 .expect("for provisioned billing mode, write capacity should be set"),
211 })
212 } else {
213 None
214 };
215
216 let on_demand_throughput = if billing_mode == BillingMode::PayPerRequest {
217 Some(OnDemandThroughput {
218 max_read_capacity: self.max_read_capacity,
219 max_write_capacity: self.max_write_capacity,
220 })
221 } else {
222 None
223 };
224
225 let properties = TableProperties {
226 key_schema,
227 attribute_definitions: key_attributes,
228 billing_mode: billing_mode.into(),
229 provisioned_throughput,
230 on_demand_throughput,
231 };
232
233 let resource_id = Resource::generate_id("DynamoDBTable");
234 stack_builder.add_resource(Table {
235 id: self.id,
236 resource_id: resource_id.clone(),
237 r#type: "AWS::DynamoDB::Table".to_string(),
238 properties,
239 });
240
241 TableRef::new(resource_id)
242 }
243}
244
245impl TableBuilder<PayPerRequestState> {
246 pub fn max_read_capacity(self, capacity: NonZeroNumber) -> Self {
247 Self {
248 max_read_capacity: Some(capacity.0),
249 ..self
250 }
251 }
252
253 pub fn max_write_capacity(self, capacity: NonZeroNumber) -> Self {
254 Self {
255 max_write_capacity: Some(capacity.0),
256 ..self
257 }
258 }
259
260 pub fn build(self, stack_builder: &mut StackBuilder) -> TableRef {
261 self.build_internal(stack_builder)
262 }
263}
264
265impl TableBuilder<ProvisionedStateStart> {
266 pub fn read_capacity(self, capacity: NonZeroNumber) -> TableBuilder<ProvisionedStateReadSet> {
267 TableBuilder {
268 read_capacity: Some(capacity.0),
269 state: Default::default(),
270 id: self.id,
271 table_name: self.table_name,
272 partition_key: self.partition_key,
273 sort_key: self.sort_key,
274 billing_mode: self.billing_mode,
275 write_capacity: self.write_capacity,
276 max_read_capacity: self.read_capacity,
277 max_write_capacity: self.max_write_capacity,
278 }
279 }
280}
281
282impl TableBuilder<ProvisionedStateReadSet> {
283 pub fn write_capacity(self, capacity: NonZeroNumber) -> TableBuilder<ProvisionedStateWriteSet> {
284 TableBuilder {
285 write_capacity: Some(capacity.0),
286 state: Default::default(),
287 id: self.id,
288 table_name: self.table_name,
289 partition_key: self.partition_key,
290 sort_key: self.sort_key,
291 billing_mode: self.billing_mode,
292 read_capacity: self.read_capacity,
293 max_read_capacity: self.max_read_capacity,
294 max_write_capacity: self.max_write_capacity,
295 }
296 }
297}
298
299impl TableBuilder<ProvisionedStateWriteSet> {
300 pub fn build(self, stack_builder: &mut StackBuilder) -> TableRef {
301 self.build_internal(stack_builder)
302 }
303}