1use std::collections::HashMap;
2
3use chrono::Utc;
4use thiserror::Error;
5
6use crate::types::*;
7
8#[derive(Debug, Default)]
9pub struct KeyspacesState {
10 pub keyspaces: HashMap<String, Keyspace>,
12 pub tables: HashMap<(String, String), Table>,
14 pub types: HashMap<(String, String), UserDefinedType>,
16}
17
18#[derive(Debug, Error)]
20pub enum KeyspacesError {
21 #[error("Resource not found: {resource_type} {name}")]
22 NotFound {
23 resource_type: &'static str,
24 name: String,
25 },
26 #[error("Resource already exists: {resource_type} {name}")]
27 AlreadyExists {
28 resource_type: &'static str,
29 name: String,
30 },
31 #[error("{message}")]
32 Validation { message: String },
33 #[error("Resource {name} has a conflict: {detail}")]
34 Conflict { name: String, detail: String },
35}
36
37impl KeyspacesState {
38 pub fn create_keyspace(
41 &mut self,
42 name: &str,
43 replication_strategy: &str,
44 replication_regions: Vec<String>,
45 tags: HashMap<String, String>,
46 account_id: &str,
47 region: &str,
48 ) -> Result<String, KeyspacesError> {
49 if self.keyspaces.contains_key(name) {
50 return Err(KeyspacesError::AlreadyExists {
51 resource_type: "Keyspace",
52 name: name.to_string(),
53 });
54 }
55 let arn = format!("arn:aws:cassandra:{region}:{account_id}:/keyspace/{name}/");
56 let ks = Keyspace {
57 name: name.to_string(),
58 arn: arn.clone(),
59 replication_strategy: replication_strategy.to_string(),
60 replication_regions,
61 tags,
62 creation_timestamp: Utc::now(),
63 status: "ACTIVE".to_string(),
64 };
65 self.keyspaces.insert(name.to_string(), ks);
66 Ok(arn)
67 }
68
69 pub fn get_keyspace(&self, name: &str) -> Result<&Keyspace, KeyspacesError> {
70 self.keyspaces
71 .get(name)
72 .ok_or_else(|| KeyspacesError::NotFound {
73 resource_type: "Keyspace",
74 name: name.to_string(),
75 })
76 }
77
78 pub fn delete_keyspace(&mut self, name: &str) -> Result<(), KeyspacesError> {
79 if self.keyspaces.remove(name).is_none() {
80 return Err(KeyspacesError::NotFound {
81 resource_type: "Keyspace",
82 name: name.to_string(),
83 });
84 }
85 self.tables.retain(|(ks, _), _| ks != name);
87 self.types.retain(|(ks, _), _| ks != name);
89 Ok(())
90 }
91
92 pub fn update_keyspace(
93 &mut self,
94 name: &str,
95 replication_strategy: &str,
96 replication_regions: Vec<String>,
97 ) -> Result<String, KeyspacesError> {
98 let ks = self
99 .keyspaces
100 .get_mut(name)
101 .ok_or_else(|| KeyspacesError::NotFound {
102 resource_type: "Keyspace",
103 name: name.to_string(),
104 })?;
105 ks.replication_strategy = replication_strategy.to_string();
106 ks.replication_regions = replication_regions;
107 Ok(ks.arn.clone())
108 }
109
110 pub fn list_keyspaces(&self) -> Vec<&Keyspace> {
111 let mut ks: Vec<_> = self.keyspaces.values().collect();
112 ks.sort_by_key(|k| &k.name);
113 ks
114 }
115
116 #[allow(clippy::too_many_arguments)]
119 pub fn create_table(
120 &mut self,
121 keyspace_name: &str,
122 table_name: &str,
123 schema: SchemaDefinition,
124 capacity_mode: &str,
125 read_capacity_units: Option<i64>,
126 write_capacity_units: Option<i64>,
127 encryption_type: &str,
128 kms_key_identifier: Option<String>,
129 point_in_time_recovery: bool,
130 ttl_status: &str,
131 default_time_to_live: Option<i32>,
132 comment: &str,
133 client_side_timestamps: bool,
134 tags: HashMap<String, String>,
135 account_id: &str,
136 region: &str,
137 ) -> Result<String, KeyspacesError> {
138 if !self.keyspaces.contains_key(keyspace_name) {
140 return Err(KeyspacesError::NotFound {
141 resource_type: "Keyspace",
142 name: keyspace_name.to_string(),
143 });
144 }
145 let key = (keyspace_name.to_string(), table_name.to_string());
146 if self.tables.contains_key(&key) {
147 return Err(KeyspacesError::AlreadyExists {
148 resource_type: "Table",
149 name: format!("{keyspace_name}/{table_name}"),
150 });
151 }
152 let arn = format!(
153 "arn:aws:cassandra:{region}:{account_id}:/keyspace/{keyspace_name}/table/{table_name}"
154 );
155 let table = Table {
156 keyspace_name: keyspace_name.to_string(),
157 table_name: table_name.to_string(),
158 arn: arn.clone(),
159 schema_definition: schema,
160 capacity_mode: capacity_mode.to_string(),
161 read_capacity_units,
162 write_capacity_units,
163 encryption_type: encryption_type.to_string(),
164 kms_key_identifier,
165 point_in_time_recovery_enabled: point_in_time_recovery,
166 ttl_status: ttl_status.to_string(),
167 default_time_to_live,
168 comment: comment.to_string(),
169 client_side_timestamps_enabled: client_side_timestamps,
170 tags,
171 creation_timestamp: Utc::now(),
172 status: "ACTIVE".to_string(),
173 };
174 self.tables.insert(key, table);
175 Ok(arn)
176 }
177
178 pub fn get_table(
179 &self,
180 keyspace_name: &str,
181 table_name: &str,
182 ) -> Result<&Table, KeyspacesError> {
183 let key = (keyspace_name.to_string(), table_name.to_string());
184 self.tables
185 .get(&key)
186 .ok_or_else(|| KeyspacesError::NotFound {
187 resource_type: "Table",
188 name: format!("{keyspace_name}/{table_name}"),
189 })
190 }
191
192 pub fn delete_table(
193 &mut self,
194 keyspace_name: &str,
195 table_name: &str,
196 ) -> Result<(), KeyspacesError> {
197 let key = (keyspace_name.to_string(), table_name.to_string());
198 if self.tables.remove(&key).is_none() {
199 return Err(KeyspacesError::NotFound {
200 resource_type: "Table",
201 name: format!("{keyspace_name}/{table_name}"),
202 });
203 }
204 Ok(())
205 }
206
207 #[allow(clippy::too_many_arguments)]
208 pub fn update_table(
209 &mut self,
210 keyspace_name: &str,
211 table_name: &str,
212 capacity_mode: Option<&str>,
213 read_capacity_units: Option<i64>,
214 write_capacity_units: Option<i64>,
215 encryption_type: Option<&str>,
216 kms_key_identifier: Option<String>,
217 point_in_time_recovery: Option<bool>,
218 ttl_status: Option<&str>,
219 default_time_to_live: Option<i32>,
220 client_side_timestamps: Option<bool>,
221 ) -> Result<String, KeyspacesError> {
222 let key = (keyspace_name.to_string(), table_name.to_string());
223 let table = self
224 .tables
225 .get_mut(&key)
226 .ok_or_else(|| KeyspacesError::NotFound {
227 resource_type: "Table",
228 name: format!("{keyspace_name}/{table_name}"),
229 })?;
230 if let Some(cm) = capacity_mode {
231 table.capacity_mode = cm.to_string();
232 }
233 if let Some(rcu) = read_capacity_units {
234 table.read_capacity_units = Some(rcu);
235 }
236 if let Some(wcu) = write_capacity_units {
237 table.write_capacity_units = Some(wcu);
238 }
239 if let Some(et) = encryption_type {
240 table.encryption_type = et.to_string();
241 }
242 if kms_key_identifier.is_some() {
243 table.kms_key_identifier = kms_key_identifier;
244 }
245 if let Some(pitr) = point_in_time_recovery {
246 table.point_in_time_recovery_enabled = pitr;
247 }
248 if let Some(ts) = ttl_status {
249 table.ttl_status = ts.to_string();
250 }
251 if let Some(dttl) = default_time_to_live {
252 table.default_time_to_live = Some(dttl);
253 }
254 if let Some(cst) = client_side_timestamps {
255 table.client_side_timestamps_enabled = cst;
256 }
257 Ok(table.arn.clone())
258 }
259
260 pub fn list_tables(&self, keyspace_name: &str) -> Result<Vec<&Table>, KeyspacesError> {
261 if !self.keyspaces.contains_key(keyspace_name) {
262 return Err(KeyspacesError::NotFound {
263 resource_type: "Keyspace",
264 name: keyspace_name.to_string(),
265 });
266 }
267 let mut tables: Vec<_> = self
268 .tables
269 .values()
270 .filter(|t| t.keyspace_name == keyspace_name)
271 .collect();
272 tables.sort_by_key(|t| &t.table_name);
273 Ok(tables)
274 }
275
276 pub fn restore_table(
277 &mut self,
278 source_keyspace_name: &str,
279 source_table_name: &str,
280 target_keyspace_name: &str,
281 target_table_name: &str,
282 account_id: &str,
283 region: &str,
284 ) -> Result<String, KeyspacesError> {
285 let source_key = (
287 source_keyspace_name.to_string(),
288 source_table_name.to_string(),
289 );
290 let source = self
291 .tables
292 .get(&source_key)
293 .ok_or_else(|| KeyspacesError::NotFound {
294 resource_type: "Table",
295 name: format!("{source_keyspace_name}/{source_table_name}"),
296 })?
297 .clone();
298
299 if !self.keyspaces.contains_key(target_keyspace_name) {
301 return Err(KeyspacesError::NotFound {
302 resource_type: "Keyspace",
303 name: target_keyspace_name.to_string(),
304 });
305 }
306
307 let target_key = (
308 target_keyspace_name.to_string(),
309 target_table_name.to_string(),
310 );
311 if self.tables.contains_key(&target_key) {
312 return Err(KeyspacesError::AlreadyExists {
313 resource_type: "Table",
314 name: format!("{target_keyspace_name}/{target_table_name}"),
315 });
316 }
317
318 let arn = format!(
319 "arn:aws:cassandra:{region}:{account_id}:/keyspace/{target_keyspace_name}/table/{target_table_name}"
320 );
321 let table = Table {
322 keyspace_name: target_keyspace_name.to_string(),
323 table_name: target_table_name.to_string(),
324 arn: arn.clone(),
325 schema_definition: source.schema_definition,
326 capacity_mode: source.capacity_mode,
327 read_capacity_units: source.read_capacity_units,
328 write_capacity_units: source.write_capacity_units,
329 encryption_type: source.encryption_type,
330 kms_key_identifier: source.kms_key_identifier,
331 point_in_time_recovery_enabled: source.point_in_time_recovery_enabled,
332 ttl_status: source.ttl_status,
333 default_time_to_live: source.default_time_to_live,
334 comment: source.comment,
335 client_side_timestamps_enabled: source.client_side_timestamps_enabled,
336 tags: source.tags,
337 creation_timestamp: Utc::now(),
338 status: "ACTIVE".to_string(),
339 };
340 self.tables.insert(target_key, table);
341 Ok(arn)
342 }
343
344 pub fn create_type(
347 &mut self,
348 keyspace_name: &str,
349 type_name: &str,
350 field_definitions: Vec<FieldDefinition>,
351 ) -> Result<String, KeyspacesError> {
352 if !self.keyspaces.contains_key(keyspace_name) {
353 return Err(KeyspacesError::NotFound {
354 resource_type: "Keyspace",
355 name: keyspace_name.to_string(),
356 });
357 }
358 let key = (keyspace_name.to_string(), type_name.to_string());
359 if self.types.contains_key(&key) {
360 return Err(KeyspacesError::AlreadyExists {
361 resource_type: "Type",
362 name: format!("{keyspace_name}/{type_name}"),
363 });
364 }
365 let result = format!("{keyspace_name}.{type_name}");
368 let udt = UserDefinedType {
369 keyspace_name: keyspace_name.to_string(),
370 type_name: type_name.to_string(),
371 field_definitions,
372 creation_timestamp: Utc::now(),
373 status: "ACTIVE".to_string(),
374 };
375 self.types.insert(key, udt);
376 Ok(result)
377 }
378
379 pub fn get_type(
380 &self,
381 keyspace_name: &str,
382 type_name: &str,
383 ) -> Result<&UserDefinedType, KeyspacesError> {
384 let key = (keyspace_name.to_string(), type_name.to_string());
385 self.types
386 .get(&key)
387 .ok_or_else(|| KeyspacesError::NotFound {
388 resource_type: "Type",
389 name: format!("{keyspace_name}/{type_name}"),
390 })
391 }
392
393 pub fn delete_type(
394 &mut self,
395 keyspace_name: &str,
396 type_name: &str,
397 ) -> Result<(), KeyspacesError> {
398 let key = (keyspace_name.to_string(), type_name.to_string());
399 if self.types.remove(&key).is_none() {
400 return Err(KeyspacesError::NotFound {
401 resource_type: "Type",
402 name: format!("{keyspace_name}/{type_name}"),
403 });
404 }
405 Ok(())
406 }
407
408 pub fn list_types(&self, keyspace_name: &str) -> Result<Vec<&UserDefinedType>, KeyspacesError> {
409 if !self.keyspaces.contains_key(keyspace_name) {
410 return Err(KeyspacesError::NotFound {
411 resource_type: "Keyspace",
412 name: keyspace_name.to_string(),
413 });
414 }
415 let mut types: Vec<_> = self
416 .types
417 .values()
418 .filter(|t| t.keyspace_name == keyspace_name)
419 .collect();
420 types.sort_by_key(|t| &t.type_name);
421 Ok(types)
422 }
423
424 pub fn get_tags_for_resource(
428 &self,
429 arn: &str,
430 ) -> Result<HashMap<String, String>, KeyspacesError> {
431 for ks in self.keyspaces.values() {
433 if ks.arn == arn {
434 return Ok(ks.tags.clone());
435 }
436 }
437 for table in self.tables.values() {
438 if table.arn == arn {
439 return Ok(table.tags.clone());
440 }
441 }
442 Err(KeyspacesError::NotFound {
443 resource_type: "Resource",
444 name: arn.to_string(),
445 })
446 }
447
448 pub fn tag_resource(
449 &mut self,
450 arn: &str,
451 tags: HashMap<String, String>,
452 ) -> Result<(), KeyspacesError> {
453 for ks in self.keyspaces.values_mut() {
455 if ks.arn == arn {
456 ks.tags.extend(tags);
457 return Ok(());
458 }
459 }
460 for table in self.tables.values_mut() {
461 if table.arn == arn {
462 table.tags.extend(tags);
463 return Ok(());
464 }
465 }
466 Err(KeyspacesError::NotFound {
467 resource_type: "Resource",
468 name: arn.to_string(),
469 })
470 }
471
472 pub fn untag_resource(&mut self, arn: &str, tag_keys: &[String]) -> Result<(), KeyspacesError> {
473 for ks in self.keyspaces.values_mut() {
475 if ks.arn == arn {
476 for key in tag_keys {
477 ks.tags.remove(key);
478 }
479 return Ok(());
480 }
481 }
482 for table in self.tables.values_mut() {
483 if table.arn == arn {
484 for key in tag_keys {
485 table.tags.remove(key);
486 }
487 return Ok(());
488 }
489 }
490 Err(KeyspacesError::NotFound {
491 resource_type: "Resource",
492 name: arn.to_string(),
493 })
494 }
495}