Skip to main content

dynoxide/actions/
delete_table.rs

1use crate::actions::{TableDescription, build_table_description};
2use crate::errors::{DynoxideError, Result};
3use crate::storage_backend::StorageBackend;
4use crate::types::{GlobalSecondaryIndex, LocalSecondaryIndex};
5use serde::{Deserialize, Serialize};
6
7/// Internal deserialization struct for detecting missing TableName.
8#[derive(Debug, Default, Deserialize)]
9struct DeleteTableRequestRaw {
10    #[serde(rename = "TableName", default)]
11    table_name: Option<String>,
12}
13
14#[derive(Debug, Default)]
15pub struct DeleteTableRequest {
16    pub table_name: String,
17}
18
19impl<'de> serde::Deserialize<'de> for DeleteTableRequest {
20    fn deserialize<D: serde::Deserializer<'de>>(
21        deserializer: D,
22    ) -> std::result::Result<Self, D::Error> {
23        let raw = DeleteTableRequestRaw::deserialize(deserializer)?;
24
25        if raw.table_name.is_none() {
26            return Err(serde::de::Error::custom(
27                "VALIDATION:The parameter 'TableName' is required but was not present in the request",
28            ));
29        }
30        let table_name = raw.table_name.unwrap();
31
32        // Length check (before pattern, matching DynamoDB ordering)
33        if table_name.len() < 3 || table_name.len() > 255 {
34            return Err(serde::de::Error::custom(
35                "VALIDATION:TableName must be at least 3 characters long and at most 255 characters long",
36            ));
37        }
38
39        // Pattern check (only reached if length is valid)
40        if !table_name
41            .chars()
42            .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.')
43        {
44            return Err(serde::de::Error::custom(format!(
45                "VALIDATION:1 validation error detected: \
46                 Value '{}' at 'tableName' failed to satisfy constraint: \
47                 Member must satisfy regular expression pattern: [a-zA-Z0-9_.-]+",
48                table_name
49            )));
50        }
51
52        Ok(DeleteTableRequest { table_name })
53    }
54}
55
56#[derive(Debug, Default, Serialize)]
57pub struct DeleteTableResponse {
58    #[serde(rename = "TableDescription")]
59    pub table_description: TableDescription,
60}
61
62pub async fn execute<S: StorageBackend>(
63    storage: &S,
64    request: DeleteTableRequest,
65) -> Result<DeleteTableResponse> {
66    // Validate table name format before checking existence (DynamoDB validates input first)
67    crate::validation::validate_table_name(&request.table_name)?;
68
69    // Get metadata before deletion (for the response)
70    let meta = storage
71        .get_table_metadata(&request.table_name)
72        .await?
73        .ok_or_else(|| {
74            DynoxideError::ResourceNotFoundException(format!(
75                "Requested resource not found: Table: {} not found",
76                request.table_name
77            ))
78        })?;
79
80    // Check deletion protection
81    if meta.deletion_protection_enabled {
82        return Err(DynoxideError::ValidationException(
83            "Resource cannot be deleted as it is currently protected against deletion. \
84             Disable deletion protection first."
85                .to_string(),
86        ));
87    }
88
89    // Drop GSI tables first
90    if let Some(ref gsi_json) = meta.gsi_definitions {
91        if let Ok(gsis) = serde_json::from_str::<Vec<GlobalSecondaryIndex>>(gsi_json) {
92            for gsi in &gsis {
93                storage
94                    .drop_gsi_table(&request.table_name, &gsi.index_name)
95                    .await?;
96            }
97        }
98    }
99
100    // Drop LSI tables
101    if let Some(ref lsi_json) = meta.lsi_definitions {
102        if let Ok(lsis) = serde_json::from_str::<Vec<LocalSecondaryIndex>>(lsi_json) {
103            for lsi in &lsis {
104                storage
105                    .drop_lsi_table(&request.table_name, &lsi.index_name)
106                    .await?;
107            }
108        }
109    }
110
111    // Drop data table
112    storage.drop_data_table(&request.table_name).await?;
113
114    // Delete metadata
115    storage.delete_table_metadata(&request.table_name).await?;
116
117    // Build response with DELETING status
118    let mut desc = build_table_description(&meta, Some(0), Some(0));
119    desc.table_status = "DELETING".to_string();
120
121    Ok(DeleteTableResponse {
122        table_description: desc,
123    })
124}