1use std::sync::Arc;
9
10use async_trait::async_trait;
11use http::StatusCode;
12use serde_json::{json, Map, Value};
13use tokio::sync::Mutex as AsyncMutex;
14use uuid::Uuid;
15
16use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
17use fakecloud_persistence::SnapshotStore;
18
19use crate::persistence::save_snapshot;
20use crate::state::{DmsData, SharedDmsState};
21
22#[cfg(test)]
23mod tests;
24
25pub const DMS_ACTIONS: &[&str] = &[
27 "AddTagsToResource",
28 "ApplyPendingMaintenanceAction",
29 "BatchStartRecommendations",
30 "CancelMetadataModelConversion",
31 "CancelMetadataModelCreation",
32 "CancelReplicationTaskAssessmentRun",
33 "CreateDataMigration",
34 "CreateDataProvider",
35 "CreateEndpoint",
36 "CreateEventSubscription",
37 "CreateFleetAdvisorCollector",
38 "CreateInstanceProfile",
39 "CreateMigrationProject",
40 "CreateReplicationConfig",
41 "CreateReplicationInstance",
42 "CreateReplicationSubnetGroup",
43 "CreateReplicationTask",
44 "DeleteCertificate",
45 "DeleteConnection",
46 "DeleteDataMigration",
47 "DeleteDataProvider",
48 "DeleteEndpoint",
49 "DeleteEventSubscription",
50 "DeleteFleetAdvisorCollector",
51 "DeleteFleetAdvisorDatabases",
52 "DeleteInstanceProfile",
53 "DeleteMigrationProject",
54 "DeleteReplicationConfig",
55 "DeleteReplicationInstance",
56 "DeleteReplicationSubnetGroup",
57 "DeleteReplicationTask",
58 "DeleteReplicationTaskAssessmentRun",
59 "DescribeAccountAttributes",
60 "DescribeApplicableIndividualAssessments",
61 "DescribeCertificates",
62 "DescribeConnections",
63 "DescribeConversionConfiguration",
64 "DescribeDataMigrations",
65 "DescribeDataProviders",
66 "DescribeEndpointSettings",
67 "DescribeEndpointTypes",
68 "DescribeEndpoints",
69 "DescribeEngineVersions",
70 "DescribeEventCategories",
71 "DescribeEventSubscriptions",
72 "DescribeEvents",
73 "DescribeExtensionPackAssociations",
74 "DescribeFleetAdvisorCollectors",
75 "DescribeFleetAdvisorDatabases",
76 "DescribeFleetAdvisorLsaAnalysis",
77 "DescribeFleetAdvisorSchemaObjectSummary",
78 "DescribeFleetAdvisorSchemas",
79 "DescribeInstanceProfiles",
80 "DescribeMetadataModel",
81 "DescribeMetadataModelAssessments",
82 "DescribeMetadataModelChildren",
83 "DescribeMetadataModelConversions",
84 "DescribeMetadataModelCreations",
85 "DescribeMetadataModelExportsAsScript",
86 "DescribeMetadataModelExportsToTarget",
87 "DescribeMetadataModelImports",
88 "DescribeMigrationProjects",
89 "DescribeOrderableReplicationInstances",
90 "DescribePendingMaintenanceActions",
91 "DescribeRecommendationLimitations",
92 "DescribeRecommendations",
93 "DescribeRefreshSchemasStatus",
94 "DescribeReplicationConfigs",
95 "DescribeReplicationInstanceTaskLogs",
96 "DescribeReplicationInstances",
97 "DescribeReplicationSubnetGroups",
98 "DescribeReplicationTableStatistics",
99 "DescribeReplicationTaskAssessmentResults",
100 "DescribeReplicationTaskAssessmentRuns",
101 "DescribeReplicationTaskIndividualAssessments",
102 "DescribeReplicationTasks",
103 "DescribeReplications",
104 "DescribeSchemas",
105 "DescribeTableStatistics",
106 "ExportMetadataModelAssessment",
107 "GetTargetSelectionRules",
108 "ImportCertificate",
109 "ListTagsForResource",
110 "ModifyConversionConfiguration",
111 "ModifyDataMigration",
112 "ModifyDataProvider",
113 "ModifyEndpoint",
114 "ModifyEventSubscription",
115 "ModifyInstanceProfile",
116 "ModifyMigrationProject",
117 "ModifyReplicationConfig",
118 "ModifyReplicationInstance",
119 "ModifyReplicationSubnetGroup",
120 "ModifyReplicationTask",
121 "MoveReplicationTask",
122 "RebootReplicationInstance",
123 "RefreshSchemas",
124 "ReloadReplicationTables",
125 "ReloadTables",
126 "RemoveTagsFromResource",
127 "RunFleetAdvisorLsaAnalysis",
128 "StartDataMigration",
129 "StartExtensionPackAssociation",
130 "StartMetadataModelAssessment",
131 "StartMetadataModelConversion",
132 "StartMetadataModelCreation",
133 "StartMetadataModelExportAsScript",
134 "StartMetadataModelExportToTarget",
135 "StartMetadataModelImport",
136 "StartRecommendations",
137 "StartReplication",
138 "StartReplicationTask",
139 "StartReplicationTaskAssessment",
140 "StartReplicationTaskAssessmentRun",
141 "StopDataMigration",
142 "StopReplication",
143 "StopReplicationTask",
144 "TestConnection",
145 "UpdateSubscriptionsToEventBridge",
146];
147
148pub struct DmsService {
149 state: SharedDmsState,
150 snapshot_store: Option<Arc<dyn SnapshotStore>>,
151 snapshot_lock: Arc<AsyncMutex<()>>,
152}
153
154impl DmsService {
155 pub fn new(state: SharedDmsState) -> Self {
156 Self {
157 state,
158 snapshot_store: None,
159 snapshot_lock: Arc::new(AsyncMutex::new(())),
160 }
161 }
162
163 pub fn with_snapshot_store(mut self, store: Arc<dyn SnapshotStore>) -> Self {
164 self.snapshot_store = Some(store);
165 self
166 }
167
168 async fn save(&self) {
169 save_snapshot(
170 &self.state,
171 self.snapshot_store.clone(),
172 &self.snapshot_lock,
173 )
174 .await;
175 }
176}
177
178#[async_trait]
179impl AwsService for DmsService {
180 fn service_name(&self) -> &str {
181 "dms"
182 }
183
184 async fn handle(&self, request: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
185 let mutates = is_mutating(request.action.as_str());
186 let result = dispatch(self, &request);
187 if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
188 self.save().await;
189 }
190 result
191 }
192
193 fn supported_actions(&self) -> &[&str] {
194 DMS_ACTIONS
195 }
196}
197
198fn is_mutating(action: &str) -> bool {
199 !(action.starts_with("Describe") || action.starts_with("List") || action.starts_with("Get"))
200}
201
202fn dispatch(s: &DmsService, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
203 let b = parse(req)?;
204 let acct = Ctx {
205 account: req.account_id.clone(),
206 region: req.region.clone(),
207 };
208 match req.action.as_str() {
209 "CreateReplicationInstance" => s.create_replication_instance(&acct, &b),
211 "DescribeReplicationInstances" => s.describe_replication_instances(&acct, &b),
212 "ModifyReplicationInstance" => s.modify_replication_instance(&acct, &b),
213 "DeleteReplicationInstance" => s.delete_replication_instance(&acct, &b),
214 "RebootReplicationInstance" => s.reboot_replication_instance(&acct, &b),
215 "ApplyPendingMaintenanceAction" => s.apply_pending_maintenance_action(&acct, &b),
216 "DescribePendingMaintenanceActions" => s.describe_pending_maintenance_actions(&acct, &b),
217 "DescribeReplicationInstanceTaskLogs" => {
218 s.describe_replication_instance_task_logs(&acct, &b)
219 }
220 "DescribeOrderableReplicationInstances" => {
221 s.describe_orderable_replication_instances(&acct, &b)
222 }
223 "CreateEndpoint" => s.create_endpoint(&acct, &b),
225 "DescribeEndpoints" => s.describe_endpoints(&acct, &b),
226 "ModifyEndpoint" => s.modify_endpoint(&acct, &b),
227 "DeleteEndpoint" => s.delete_endpoint(&acct, &b),
228 "TestConnection" => s.test_connection(&acct, &b),
229 "DeleteConnection" => s.delete_connection(&acct, &b),
230 "DescribeConnections" => s.describe_connections(&acct, &b),
231 "RefreshSchemas" => s.refresh_schemas(&acct, &b),
232 "DescribeRefreshSchemasStatus" => s.describe_refresh_schemas_status(&acct, &b),
233 "DescribeSchemas" => s.describe_schemas(&acct, &b),
234 "DescribeEndpointSettings" => s.describe_endpoint_settings(&b),
235 "DescribeEndpointTypes" => s.describe_endpoint_types(&b),
236 "CreateReplicationTask" => s.create_replication_task(&acct, &b),
238 "DescribeReplicationTasks" => s.describe_replication_tasks(&acct, &b),
239 "ModifyReplicationTask" => s.modify_replication_task(&acct, &b),
240 "DeleteReplicationTask" => s.delete_replication_task(&acct, &b),
241 "StartReplicationTask" => s.start_replication_task(&acct, &b),
242 "StopReplicationTask" => s.stop_replication_task(&acct, &b),
243 "MoveReplicationTask" => s.move_replication_task(&acct, &b),
244 "StartReplicationTaskAssessment" => s.start_replication_task_assessment(&acct, &b),
245 "StartReplicationTaskAssessmentRun" => s.start_replication_task_assessment_run(&acct, &b),
246 "CancelReplicationTaskAssessmentRun" => s.cancel_replication_task_assessment_run(&acct, &b),
247 "DeleteReplicationTaskAssessmentRun" => s.delete_replication_task_assessment_run(&acct, &b),
248 "DescribeReplicationTaskAssessmentRuns" => {
249 s.describe_replication_task_assessment_runs(&acct, &b)
250 }
251 "DescribeReplicationTaskAssessmentResults" => {
252 s.describe_replication_task_assessment_results(&acct, &b)
253 }
254 "DescribeReplicationTaskIndividualAssessments" => {
255 s.describe_replication_task_individual_assessments(&b)
256 }
257 "DescribeApplicableIndividualAssessments" => {
258 s.describe_applicable_individual_assessments(&b)
259 }
260 "DescribeTableStatistics" => s.describe_table_statistics(&acct, &b),
261 "ReloadTables" => s.reload_tables(&acct, &b),
262 "CreateReplicationSubnetGroup" => s.create_subnet_group(&acct, &b),
264 "DescribeReplicationSubnetGroups" => s.describe_subnet_groups(&acct, &b),
265 "ModifyReplicationSubnetGroup" => s.modify_subnet_group(&acct, &b),
266 "DeleteReplicationSubnetGroup" => s.delete_subnet_group(&acct, &b),
267 "CreateEventSubscription" => s.create_event_subscription(&acct, &b),
269 "DescribeEventSubscriptions" => s.describe_event_subscriptions(&acct, &b),
270 "ModifyEventSubscription" => s.modify_event_subscription(&acct, &b),
271 "DeleteEventSubscription" => s.delete_event_subscription(&acct, &b),
272 "DescribeEventCategories" => s.describe_event_categories(&b),
273 "DescribeEvents" => s.describe_events(&b),
274 "UpdateSubscriptionsToEventBridge" => s.update_subscriptions_to_eventbridge(&b),
275 "ImportCertificate" => s.import_certificate(&acct, &b),
277 "DescribeCertificates" => s.describe_certificates(&acct, &b),
278 "DeleteCertificate" => s.delete_certificate(&acct, &b),
279 "CreateReplicationConfig" => s.create_replication_config(&acct, &b),
281 "DescribeReplicationConfigs" => s.describe_replication_configs(&acct, &b),
282 "ModifyReplicationConfig" => s.modify_replication_config(&acct, &b),
283 "DeleteReplicationConfig" => s.delete_replication_config(&acct, &b),
284 "StartReplication" => s.start_replication(&acct, &b),
285 "StopReplication" => s.stop_replication(&acct, &b),
286 "DescribeReplications" => s.describe_replications(&acct, &b),
287 "DescribeReplicationTableStatistics" => s.describe_replication_table_statistics(&acct, &b),
288 "ReloadReplicationTables" => s.reload_replication_tables(&acct, &b),
289 "CreateDataProvider" => s.create_data_provider(&acct, &b),
291 "DescribeDataProviders" => s.describe_data_providers(&acct, &b),
292 "ModifyDataProvider" => s.modify_data_provider(&acct, &b),
293 "DeleteDataProvider" => s.delete_data_provider(&acct, &b),
294 "CreateInstanceProfile" => s.create_instance_profile(&acct, &b),
296 "DescribeInstanceProfiles" => s.describe_instance_profiles(&acct, &b),
297 "ModifyInstanceProfile" => s.modify_instance_profile(&acct, &b),
298 "DeleteInstanceProfile" => s.delete_instance_profile(&acct, &b),
299 "CreateMigrationProject" => s.create_migration_project(&acct, &b),
301 "DescribeMigrationProjects" => s.describe_migration_projects(&acct, &b),
302 "ModifyMigrationProject" => s.modify_migration_project(&acct, &b),
303 "DeleteMigrationProject" => s.delete_migration_project(&acct, &b),
304 "DescribeConversionConfiguration" => s.describe_conversion_configuration(&acct, &b),
306 "ModifyConversionConfiguration" => s.modify_conversion_configuration(&acct, &b),
307 "DescribeMetadataModel" => s.describe_metadata_model(&acct, &b),
308 "DescribeMetadataModelAssessments" => s.describe_scr(&acct, &b),
309 "DescribeMetadataModelChildren" => s.describe_metadata_model_children(&acct, &b),
310 "DescribeMetadataModelConversions" => s.describe_scr(&acct, &b),
311 "DescribeMetadataModelCreations" => s.describe_scr(&acct, &b),
312 "DescribeMetadataModelExportsAsScript" => s.describe_scr(&acct, &b),
313 "DescribeMetadataModelExportsToTarget" => s.describe_scr(&acct, &b),
314 "DescribeMetadataModelImports" => s.describe_scr(&acct, &b),
315 "DescribeExtensionPackAssociations" => s.describe_scr(&acct, &b),
316 "StartExtensionPackAssociation" => s.start_scr(&acct, &b, "extension-pack-association"),
317 "StartMetadataModelAssessment" => s.start_scr(&acct, &b, "metadata-model-assessment"),
318 "StartMetadataModelConversion" => s.start_scr(&acct, &b, "metadata-model-conversion"),
319 "StartMetadataModelCreation" => s.start_scr(&acct, &b, "metadata-model-creation"),
320 "StartMetadataModelExportAsScript" => {
321 s.start_scr(&acct, &b, "metadata-model-export-script")
322 }
323 "StartMetadataModelExportToTarget" => {
324 s.start_scr(&acct, &b, "metadata-model-export-target")
325 }
326 "StartMetadataModelImport" => s.start_scr(&acct, &b, "metadata-model-import"),
327 "CancelMetadataModelConversion" => s.cancel_scr(&acct, &b),
328 "CancelMetadataModelCreation" => s.cancel_scr(&acct, &b),
329 "ExportMetadataModelAssessment" => s.export_metadata_model_assessment(&acct, &b),
330 "GetTargetSelectionRules" => s.get_target_selection_rules(&acct, &b),
331 "CreateDataMigration" => s.create_data_migration(&acct, &b),
333 "DescribeDataMigrations" => s.describe_data_migrations(&acct, &b),
334 "ModifyDataMigration" => s.modify_data_migration(&acct, &b),
335 "DeleteDataMigration" => s.delete_data_migration(&acct, &b),
336 "StartDataMigration" => s.start_data_migration(&acct, &b),
337 "StopDataMigration" => s.stop_data_migration(&acct, &b),
338 "CreateFleetAdvisorCollector" => s.create_fleet_advisor_collector(&acct, &b),
340 "DeleteFleetAdvisorCollector" => s.delete_fleet_advisor_collector(&acct, &b),
341 "DescribeFleetAdvisorCollectors" => s.describe_fleet_advisor_collectors(&acct, &b),
342 "DescribeFleetAdvisorDatabases" => s.describe_fleet_advisor_databases(&b),
343 "DeleteFleetAdvisorDatabases" => s.delete_fleet_advisor_databases(&b),
344 "DescribeFleetAdvisorLsaAnalysis" => s.describe_fleet_advisor_lsa_analysis(&b),
345 "DescribeFleetAdvisorSchemaObjectSummary" => {
346 s.describe_fleet_advisor_schema_object_summary(&b)
347 }
348 "DescribeFleetAdvisorSchemas" => s.describe_fleet_advisor_schemas(&b),
349 "RunFleetAdvisorLsaAnalysis" => s.run_fleet_advisor_lsa_analysis(),
350 "StartRecommendations" => s.start_recommendations(&acct, &b),
352 "BatchStartRecommendations" => s.batch_start_recommendations(&acct, &b),
353 "DescribeRecommendations" => s.describe_recommendations(&acct, &b),
354 "DescribeRecommendationLimitations" => s.describe_recommendation_limitations(&b),
355 "DescribeAccountAttributes" => s.describe_account_attributes(&acct),
357 "DescribeEngineVersions" => s.describe_engine_versions(&b),
358 "AddTagsToResource" => s.add_tags_to_resource(&acct, &b),
360 "RemoveTagsFromResource" => s.remove_tags_from_resource(&acct, &b),
361 "ListTagsForResource" => s.list_tags_for_resource(&acct, &b),
362 _ => Err(AwsServiceError::action_not_implemented(
363 s.service_name(),
364 &req.action,
365 )),
366 }
367}
368
369struct Ctx {
373 account: String,
374 region: String,
375}
376
377fn ok(v: Value) -> Result<AwsResponse, AwsServiceError> {
378 Ok(AwsResponse::json_value(StatusCode::OK, v))
379}
380
381fn parse(req: &AwsRequest) -> Result<Value, AwsServiceError> {
382 if req.body.is_empty() {
383 return Ok(json!({}));
384 }
385 serde_json::from_slice(&req.body)
386 .map_err(|e| validation(&format!("Request body is malformed: {e}")))
387}
388
389fn validation(msg: &str) -> AwsServiceError {
390 AwsServiceError::aws_error(
391 StatusCode::BAD_REQUEST,
392 "InvalidParameterValueException",
393 msg,
394 )
395}
396
397fn not_found(msg: &str) -> AwsServiceError {
398 AwsServiceError::aws_error(StatusCode::BAD_REQUEST, "ResourceNotFoundFault", msg)
399}
400
401fn already_exists(msg: &str) -> AwsServiceError {
402 AwsServiceError::aws_error(StatusCode::BAD_REQUEST, "ResourceAlreadyExistsFault", msg)
403}
404
405fn invalid_state(msg: &str) -> AwsServiceError {
406 AwsServiceError::aws_error(StatusCode::BAD_REQUEST, "InvalidResourceStateFault", msg)
407}
408
409fn req_str<'a>(b: &'a Value, f: &str) -> Result<&'a str, AwsServiceError> {
411 b.get(f)
412 .and_then(Value::as_str)
413 .filter(|s| !s.is_empty())
414 .ok_or_else(|| validation(&format!("{f} is required.")))
415}
416
417fn req_str_allow_empty<'a>(b: &'a Value, f: &str) -> Result<&'a str, AwsServiceError> {
420 b.get(f)
421 .and_then(Value::as_str)
422 .ok_or_else(|| validation(&format!("{f} is required.")))
423}
424
425fn opt_str<'a>(b: &'a Value, f: &str) -> Option<&'a str> {
426 b.get(f).and_then(Value::as_str)
427}
428
429fn now_ts() -> f64 {
430 chrono::Utc::now().timestamp() as f64
431}
432
433fn res_id() -> String {
435 let raw = Uuid::new_v4().simple().to_string().to_uppercase();
436 raw.chars()
437 .filter(|c| c.is_ascii_alphanumeric())
438 .take(26)
439 .collect()
440}
441
442fn arn(ctx: &Ctx, kind: &str, id: &str) -> String {
443 format!("arn:aws:dms:{}:{}:{}:{}", ctx.region, ctx.account, kind, id)
444}
445
446fn vpc_security_groups(b: &Value) -> Vec<Value> {
449 b.get("VpcSecurityGroupIds")
450 .and_then(Value::as_array)
451 .map(|a| {
452 a.iter()
453 .filter_map(|v| v.as_str())
454 .map(|s| json!({ "VpcSecurityGroupId": s, "Status": "active" }))
455 .collect()
456 })
457 .unwrap_or_default()
458}
459
460fn copy_fields(src: &Value, dst: &mut Map<String, Value>, fields: &[&str]) {
462 for f in fields {
463 if let Some(v) = src.get(*f) {
464 dst.insert((*f).to_string(), v.clone());
465 }
466 }
467}
468
469fn paginate(rows: Vec<Value>, b: &Value) -> (Vec<Value>, Option<String>) {
472 let start = b
473 .get("Marker")
474 .or_else(|| b.get("NextToken"))
475 .and_then(Value::as_str)
476 .and_then(|t| t.parse::<usize>().ok())
477 .unwrap_or(0);
478 let max = b
479 .get("MaxRecords")
480 .and_then(Value::as_u64)
481 .map(|m| m.clamp(1, 10_000) as usize)
482 .unwrap_or(100);
483 let end = start.saturating_add(max).min(rows.len());
487 let page = rows.get(start..end).unwrap_or(&[]).to_vec();
488 let next = if end < rows.len() {
489 Some(end.to_string())
490 } else {
491 None
492 };
493 (page, next)
494}
495
496fn list_marker(
498 key: &str,
499 mut rows: Vec<Value>,
500 b: &Value,
501 filterable: &[(&str, &[&str])],
502) -> Result<AwsResponse, AwsServiceError> {
503 apply_filters(&mut rows, b, filterable);
504 let (page, next) = paginate(rows, b);
505 let mut out = Map::new();
506 out.insert(key.to_string(), Value::Array(page));
507 out.insert("Marker".to_string(), json!(next.unwrap_or_default()));
510 ok(Value::Object(out))
511}
512
513fn list_next_token(
516 key: &str,
517 mut rows: Vec<Value>,
518 b: &Value,
519 filterable: &[(&str, &[&str])],
520) -> Result<AwsResponse, AwsServiceError> {
521 apply_filters(&mut rows, b, filterable);
522 let (page, next) = paginate(rows, b);
523 let mut out = Map::new();
524 out.insert(key.to_string(), Value::Array(page));
525 if let Some(t) = next {
526 out.insert("NextToken".to_string(), json!(t));
527 }
528 ok(Value::Object(out))
529}
530
531fn apply_filters(rows: &mut Vec<Value>, b: &Value, filterable: &[(&str, &[&str])]) {
536 let Some(filters) = b.get("Filters").and_then(Value::as_array) else {
537 return;
538 };
539 for filter in filters {
540 let Some(name) = filter.get("Name").and_then(Value::as_str) else {
541 continue;
542 };
543 let values: Vec<String> = filter
544 .get("Values")
545 .and_then(Value::as_array)
546 .map(|a| {
547 a.iter()
548 .filter_map(|v| v.as_str().map(String::from))
549 .collect()
550 })
551 .unwrap_or_default();
552 let Some((_, row_fields)) = filterable.iter().find(|(n, _)| *n == name) else {
553 continue;
554 };
555 rows.retain(|row| {
556 row_fields.iter().any(|rf| {
557 row.get(*rf)
558 .and_then(Value::as_str)
559 .map(|v| values.iter().any(|want| want == v))
560 .unwrap_or(false)
561 })
562 });
563 }
564}
565
566fn store_tags(data: &mut DmsData, resource_arn: &str, b: &Value) {
568 if let Some(tags) = b.get("Tags").and_then(Value::as_array) {
569 let entry = data.tags.entry(resource_arn.to_string()).or_default();
570 for t in tags {
571 if let (Some(k), Some(v)) = (
572 t.get("Key").and_then(Value::as_str),
573 t.get("Value").and_then(Value::as_str),
574 ) {
575 entry.insert(k.to_string(), v.to_string());
576 }
577 }
578 }
579}
580
581fn default_subnet_group(region: &str) -> Value {
584 json!({
585 "ReplicationSubnetGroupIdentifier": "default",
586 "ReplicationSubnetGroupDescription": "default",
587 "VpcId": "vpc-0a1b2c3d",
588 "SubnetGroupStatus": "Complete",
589 "SupportedNetworkTypes": ["IPV4"],
590 "Subnets": [
591 {
592 "SubnetIdentifier": "subnet-0a1b2c3d",
593 "SubnetStatus": "Active",
594 "SubnetAvailabilityZone": { "Name": format!("{region}a") }
595 },
596 {
597 "SubnetIdentifier": "subnet-1a2b3c4d",
598 "SubnetStatus": "Active",
599 "SubnetAvailabilityZone": { "Name": format!("{region}b") }
600 }
601 ]
602 })
603}
604
605const ENDPOINT_SETTINGS_FIELDS: &[&str] = &[
606 "DynamoDbSettings",
607 "S3Settings",
608 "DmsTransferSettings",
609 "MongoDbSettings",
610 "KinesisSettings",
611 "KafkaSettings",
612 "ElasticsearchSettings",
613 "NeptuneSettings",
614 "RedshiftSettings",
615 "PostgreSQLSettings",
616 "MySQLSettings",
617 "OracleSettings",
618 "SybaseSettings",
619 "MicrosoftSQLServerSettings",
620 "IBMDb2Settings",
621 "DocDbSettings",
622 "RedisSettings",
623 "GcpMySQLSettings",
624 "TimestreamSettings",
625];
626
627impl DmsService {
630 fn create_replication_instance(
631 &self,
632 ctx: &Ctx,
633 b: &Value,
634 ) -> Result<AwsResponse, AwsServiceError> {
635 let id = req_str_allow_empty(b, "ReplicationInstanceIdentifier")?.to_string();
636 let class = req_str_allow_empty(b, "ReplicationInstanceClass")?.to_string();
637 if class.chars().count() > 30 {
638 return Err(validation(
639 "ReplicationInstanceClass exceeds 30 characters.",
640 ));
641 }
642 let mut guard = self.state.write();
643 let data = guard.get_or_create(&ctx.account);
644 if !id.is_empty()
645 && data.replication_instances.values().any(|v| {
646 v.get("ReplicationInstanceIdentifier")
647 .and_then(Value::as_str)
648 == Some(&id)
649 })
650 {
651 return Err(already_exists(&format!(
652 "Replication instance {id} already exists."
653 )));
654 }
655 let instance_arn = arn(ctx, "rep", &res_id());
656 let vpc_sgs = vpc_security_groups(b);
657 let mut inst = Map::new();
658 inst.insert("ReplicationInstanceArn".into(), json!(instance_arn));
659 inst.insert("ReplicationInstanceIdentifier".into(), json!(id));
660 inst.insert("ReplicationInstanceClass".into(), json!(class));
661 inst.insert("ReplicationInstanceStatus".into(), json!("available"));
664 inst.insert(
665 "AllocatedStorage".into(),
666 b.get("AllocatedStorage").cloned().unwrap_or(json!(50)),
667 );
668 inst.insert("InstanceCreateTime".into(), json!(now_ts()));
669 inst.insert("VpcSecurityGroups".into(), json!(vpc_sgs));
670 inst.insert(
671 "AvailabilityZone".into(),
672 json!(opt_str(b, "AvailabilityZone").unwrap_or(&format!("{}a", ctx.region))),
673 );
674 inst.insert(
675 "ReplicationSubnetGroup".into(),
676 default_subnet_group(&ctx.region),
677 );
678 inst.insert(
679 "PreferredMaintenanceWindow".into(),
680 json!(opt_str(b, "PreferredMaintenanceWindow").unwrap_or("sun:06:00-sun:14:00")),
681 );
682 inst.insert("PendingModifiedValues".into(), json!({}));
683 inst.insert(
684 "MultiAZ".into(),
685 b.get("MultiAZ").cloned().unwrap_or(json!(false)),
686 );
687 inst.insert(
688 "EngineVersion".into(),
689 json!(opt_str(b, "EngineVersion")
690 .filter(|s| !s.is_empty())
691 .unwrap_or("3.5.4")),
692 );
693 inst.insert(
694 "AutoMinorVersionUpgrade".into(),
695 b.get("AutoMinorVersionUpgrade")
696 .cloned()
697 .unwrap_or(json!(true)),
698 );
699 inst.insert(
700 "KmsKeyId".into(),
701 json!(opt_str(b, "KmsKeyId")
702 .filter(|s| !s.is_empty())
703 .map(String::from)
704 .unwrap_or_else(|| arn(ctx, "key", &Uuid::new_v4().to_string()))),
705 );
706 inst.insert(
707 "PubliclyAccessible".into(),
708 b.get("PubliclyAccessible").cloned().unwrap_or(json!(true)),
709 );
710 inst.insert(
711 "ReplicationInstancePrivateIpAddresses".into(),
712 json!(["10.0.0.10"]),
713 );
714 if let Some(nt) = opt_str(b, "NetworkType") {
715 inst.insert("NetworkType".into(), json!(nt));
716 }
717 let inst = Value::Object(inst);
718 data.replication_instances
719 .insert(instance_arn.clone(), inst.clone());
720 store_tags(data, &instance_arn, b);
721 ok(json!({ "ReplicationInstance": inst }))
722 }
723
724 fn describe_replication_instances(
725 &self,
726 ctx: &Ctx,
727 b: &Value,
728 ) -> Result<AwsResponse, AwsServiceError> {
729 let guard = self.state.read();
730 let rows = guard
731 .get(&ctx.account)
732 .map(|d| d.replication_instances.values().cloned().collect())
733 .unwrap_or_default();
734 list_marker(
735 "ReplicationInstances",
736 rows,
737 b,
738 &[
739 ("replication-instance-arn", &["ReplicationInstanceArn"]),
740 (
741 "replication-instance-id",
742 &["ReplicationInstanceIdentifier"],
743 ),
744 ],
745 )
746 }
747
748 fn modify_replication_instance(
749 &self,
750 ctx: &Ctx,
751 b: &Value,
752 ) -> Result<AwsResponse, AwsServiceError> {
753 let arn_id = req_str(b, "ReplicationInstanceArn")?.to_string();
754 if let Some(class) = opt_str(b, "ReplicationInstanceClass") {
755 if class.chars().count() > 30 {
756 return Err(validation(
757 "ReplicationInstanceClass exceeds 30 characters.",
758 ));
759 }
760 }
761 let mut guard = self.state.write();
762 let data = guard.get_or_create(&ctx.account);
763 let Some(inst) = data.replication_instances.get_mut(&arn_id) else {
764 return Err(not_found(&format!(
765 "Replication instance {arn_id} not found."
766 )));
767 };
768 let obj = inst.as_object_mut().unwrap();
769 for (in_field, out_field) in [
770 ("ReplicationInstanceClass", "ReplicationInstanceClass"),
771 ("AllocatedStorage", "AllocatedStorage"),
772 ("PreferredMaintenanceWindow", "PreferredMaintenanceWindow"),
773 ("MultiAZ", "MultiAZ"),
774 ("EngineVersion", "EngineVersion"),
775 ("AutoMinorVersionUpgrade", "AutoMinorVersionUpgrade"),
776 (
777 "ReplicationInstanceIdentifier",
778 "ReplicationInstanceIdentifier",
779 ),
780 ("NetworkType", "NetworkType"),
781 ] {
782 if let Some(v) = b.get(in_field) {
783 obj.insert(out_field.to_string(), v.clone());
784 }
785 }
786 if b.get("VpcSecurityGroupIds").is_some() {
790 obj.insert("VpcSecurityGroups".into(), json!(vpc_security_groups(b)));
791 }
792 obj.insert("ReplicationInstanceStatus".into(), json!("available"));
793 let inst = inst.clone();
794 ok(json!({ "ReplicationInstance": inst }))
795 }
796
797 fn delete_replication_instance(
798 &self,
799 ctx: &Ctx,
800 b: &Value,
801 ) -> Result<AwsResponse, AwsServiceError> {
802 let arn_id = req_str(b, "ReplicationInstanceArn")?.to_string();
803 let mut guard = self.state.write();
804 let data = guard.get_or_create(&ctx.account);
805 let Some(mut inst) = data.replication_instances.remove(&arn_id) else {
806 return Err(not_found(&format!(
807 "Replication instance {arn_id} not found."
808 )));
809 };
810 data.tags.remove(&arn_id);
811 inst["ReplicationInstanceStatus"] = json!("deleting");
812 ok(json!({ "ReplicationInstance": inst }))
813 }
814
815 fn reboot_replication_instance(
816 &self,
817 ctx: &Ctx,
818 b: &Value,
819 ) -> Result<AwsResponse, AwsServiceError> {
820 let arn_id = req_str(b, "ReplicationInstanceArn")?.to_string();
821 let mut guard = self.state.write();
822 let data = guard.get_or_create(&ctx.account);
823 let Some(inst) = data.replication_instances.get_mut(&arn_id) else {
824 return Err(not_found(&format!(
825 "Replication instance {arn_id} not found."
826 )));
827 };
828 inst["ReplicationInstanceStatus"] = json!("available");
829 let inst = inst.clone();
830 ok(json!({ "ReplicationInstance": inst }))
831 }
832
833 fn apply_pending_maintenance_action(
834 &self,
835 ctx: &Ctx,
836 b: &Value,
837 ) -> Result<AwsResponse, AwsServiceError> {
838 let arn_id = req_str(b, "ReplicationInstanceArn")?.to_string();
839 let guard = self.state.read();
840 let exists = guard
841 .get(&ctx.account)
842 .map(|d| d.replication_instances.contains_key(&arn_id))
843 .unwrap_or(false);
844 if !exists {
845 return Err(not_found(&format!(
846 "Replication instance {arn_id} not found."
847 )));
848 }
849 ok(json!({
850 "ResourcePendingMaintenanceActions": {
851 "ResourceIdentifier": arn_id,
852 "PendingMaintenanceActionDetails": []
853 }
854 }))
855 }
856
857 fn describe_pending_maintenance_actions(
858 &self,
859 ctx: &Ctx,
860 b: &Value,
861 ) -> Result<AwsResponse, AwsServiceError> {
862 let _ = (ctx, b);
863 ok(json!({ "PendingMaintenanceActions": [] }))
864 }
865
866 fn describe_replication_instance_task_logs(
867 &self,
868 ctx: &Ctx,
869 b: &Value,
870 ) -> Result<AwsResponse, AwsServiceError> {
871 let arn_id = req_str(b, "ReplicationInstanceArn")?.to_string();
872 let guard = self.state.read();
873 let exists = guard
874 .get(&ctx.account)
875 .map(|d| d.replication_instances.contains_key(&arn_id))
876 .unwrap_or(false);
877 if !exists {
878 return Err(not_found(&format!(
879 "Replication instance {arn_id} not found."
880 )));
881 }
882 ok(json!({
883 "ReplicationInstanceArn": arn_id,
884 "ReplicationInstanceTaskLogs": []
885 }))
886 }
887
888 fn describe_orderable_replication_instances(
889 &self,
890 ctx: &Ctx,
891 b: &Value,
892 ) -> Result<AwsResponse, AwsServiceError> {
893 let azs: Vec<Value> = ["a", "b", "c"]
897 .iter()
898 .map(|z| json!(format!("{}{z}", ctx.region)))
899 .collect();
900 let rows: Vec<Value> = [
901 "dms.t3.micro",
902 "dms.t3.medium",
903 "dms.c5.large",
904 "dms.r5.large",
905 ]
906 .iter()
907 .map(|class| {
908 json!({
909 "EngineVersion": "3.5.4",
910 "ReplicationInstanceClass": class,
911 "StorageType": "gp2",
912 "MinAllocatedStorage": 5,
913 "MaxAllocatedStorage": 6144,
914 "DefaultAllocatedStorage": 50,
915 "IncludedAllocatedStorage": 50,
916 "AvailabilityZones": azs.clone(),
917 "ReleaseStatus": "available"
918 })
919 })
920 .collect();
921 list_marker("OrderableReplicationInstances", rows, b, &[])
922 }
923}
924
925impl DmsService {
928 fn create_endpoint(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
929 let id = req_str_allow_empty(b, "EndpointIdentifier")?.to_string();
930 let ep_type = req_str_allow_empty(b, "EndpointType")?.to_string();
931 if !ep_type.is_empty() && ep_type != "source" && ep_type != "target" {
932 return Err(validation("EndpointType must be one of [source, target]."));
933 }
934 let engine = req_str_allow_empty(b, "EngineName")?.to_string();
935 let mut guard = self.state.write();
936 let data = guard.get_or_create(&ctx.account);
937 if !id.is_empty()
938 && data
939 .endpoints
940 .values()
941 .any(|v| v.get("EndpointIdentifier").and_then(Value::as_str) == Some(&id))
942 {
943 return Err(already_exists(&format!("Endpoint {id} already exists.")));
944 }
945 let endpoint_arn = arn(ctx, "endpoint", &res_id());
946 let mut ep = Map::new();
947 ep.insert("EndpointArn".into(), json!(endpoint_arn));
948 ep.insert("EndpointIdentifier".into(), json!(id));
949 ep.insert("EndpointType".into(), json!(ep_type));
950 ep.insert("EngineName".into(), json!(engine));
951 ep.insert("EngineDisplayName".into(), json!(engine));
952 ep.insert("Status".into(), json!("active"));
953 copy_fields(
954 b,
955 &mut ep,
956 &[
957 "Username",
958 "ServerName",
959 "Port",
960 "DatabaseName",
961 "ExtraConnectionAttributes",
962 "KmsKeyId",
963 "CertificateArn",
964 "SslMode",
965 "ServiceAccessRoleArn",
966 "ExternalTableDefinition",
967 ],
968 );
969 copy_fields(b, &mut ep, ENDPOINT_SETTINGS_FIELDS);
970 let ep = Value::Object(ep);
971 data.endpoints.insert(endpoint_arn.clone(), ep.clone());
972 store_tags(data, &endpoint_arn, b);
973 ok(json!({ "Endpoint": ep }))
974 }
975
976 fn describe_endpoints(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
977 let guard = self.state.read();
978 let rows = guard
979 .get(&ctx.account)
980 .map(|d| d.endpoints.values().cloned().collect())
981 .unwrap_or_default();
982 list_marker(
983 "Endpoints",
984 rows,
985 b,
986 &[
987 ("endpoint-arn", &["EndpointArn"]),
988 ("endpoint-id", &["EndpointIdentifier"]),
989 ("endpoint-type", &["EndpointType"]),
990 ("engine-name", &["EngineName"]),
991 ],
992 )
993 }
994
995 fn modify_endpoint(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
996 let arn_id = req_str_allow_empty(b, "EndpointArn")?.to_string();
997 if let Some(t) = opt_str(b, "EndpointType") {
998 if !t.is_empty() && t != "source" && t != "target" {
999 return Err(validation("EndpointType must be one of [source, target]."));
1000 }
1001 }
1002 let mut guard = self.state.write();
1003 let data = guard.get_or_create(&ctx.account);
1004 let Some(ep) = data.endpoints.get_mut(&arn_id) else {
1005 return Err(not_found(&format!("Endpoint {arn_id} not found.")));
1006 };
1007 let obj = ep.as_object_mut().unwrap();
1008 for f in [
1009 "EndpointIdentifier",
1010 "EngineName",
1011 "Username",
1012 "ServerName",
1013 "Port",
1014 "DatabaseName",
1015 "ExtraConnectionAttributes",
1016 "CertificateArn",
1017 "SslMode",
1018 "ServiceAccessRoleArn",
1019 "ExternalTableDefinition",
1020 ] {
1021 if let Some(v) = b.get(f) {
1022 obj.insert(f.to_string(), v.clone());
1023 }
1024 }
1025 if let Some(t) = opt_str(b, "EndpointType") {
1026 if !t.is_empty() {
1027 obj.insert("EndpointType".into(), json!(t));
1028 }
1029 }
1030 for f in ENDPOINT_SETTINGS_FIELDS {
1031 if let Some(v) = b.get(*f) {
1032 obj.insert((*f).to_string(), v.clone());
1033 }
1034 }
1035 let ep = ep.clone();
1036 ok(json!({ "Endpoint": ep }))
1037 }
1038
1039 fn delete_endpoint(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1040 let arn_id = req_str(b, "EndpointArn")?.to_string();
1041 let mut guard = self.state.write();
1042 let data = guard.get_or_create(&ctx.account);
1043 let Some(mut ep) = data.endpoints.remove(&arn_id) else {
1044 return Err(not_found(&format!("Endpoint {arn_id} not found.")));
1045 };
1046 data.tags.remove(&arn_id);
1047 ep["Status"] = json!("deleting");
1048 ok(json!({ "Endpoint": ep }))
1049 }
1050
1051 fn test_connection(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1052 let inst_arn = req_str(b, "ReplicationInstanceArn")?.to_string();
1053 let ep_arn = req_str(b, "EndpointArn")?.to_string();
1054 let mut guard = self.state.write();
1055 let data = guard.get_or_create(&ctx.account);
1056 if !data.replication_instances.contains_key(&inst_arn) {
1057 return Err(not_found(&format!(
1058 "Replication instance {inst_arn} not found."
1059 )));
1060 }
1061 let Some(ep) = data.endpoints.get(&ep_arn) else {
1062 return Err(not_found(&format!("Endpoint {ep_arn} not found.")));
1063 };
1064 let conn = json!({
1065 "ReplicationInstanceArn": inst_arn,
1066 "EndpointArn": ep_arn,
1067 "Status": "successful",
1068 "EndpointIdentifier": ep.get("EndpointIdentifier").cloned().unwrap_or(json!("")),
1069 "ReplicationInstanceIdentifier": data
1070 .replication_instances
1071 .get(&inst_arn)
1072 .and_then(|i| i.get("ReplicationInstanceIdentifier"))
1073 .cloned()
1074 .unwrap_or(json!(""))
1075 });
1076 data.connections
1077 .insert(format!("{ep_arn}|{inst_arn}"), conn.clone());
1078 ok(json!({ "Connection": conn }))
1079 }
1080
1081 fn delete_connection(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1082 let ep_arn = req_str(b, "EndpointArn")?.to_string();
1083 let inst_arn = req_str(b, "ReplicationInstanceArn")?.to_string();
1084 let mut guard = self.state.write();
1085 let data = guard.get_or_create(&ctx.account);
1086 let Some(mut conn) = data.connections.remove(&format!("{ep_arn}|{inst_arn}")) else {
1087 return Err(not_found("Connection not found."));
1088 };
1089 conn["Status"] = json!("deleted");
1090 ok(json!({ "Connection": conn }))
1091 }
1092
1093 fn describe_connections(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1094 let guard = self.state.read();
1095 let rows = guard
1096 .get(&ctx.account)
1097 .map(|d| d.connections.values().cloned().collect())
1098 .unwrap_or_default();
1099 list_marker(
1100 "Connections",
1101 rows,
1102 b,
1103 &[
1104 ("endpoint-arn", &["EndpointArn"]),
1105 ("replication-instance-arn", &["ReplicationInstanceArn"]),
1106 ],
1107 )
1108 }
1109
1110 fn refresh_schemas(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1111 let ep_arn = req_str_allow_empty(b, "EndpointArn")?.to_string();
1112 let inst_arn = req_str_allow_empty(b, "ReplicationInstanceArn")?.to_string();
1113 let mut guard = self.state.write();
1114 let data = guard.get_or_create(&ctx.account);
1115 if !data.endpoints.contains_key(&ep_arn) {
1116 return Err(not_found(&format!("Endpoint {ep_arn} not found.")));
1117 }
1118 if !data.replication_instances.contains_key(&inst_arn) {
1119 return Err(not_found(&format!(
1120 "Replication instance {inst_arn} not found."
1121 )));
1122 }
1123 let status = json!({
1124 "EndpointArn": ep_arn,
1125 "ReplicationInstanceArn": inst_arn,
1126 "Status": "refreshing",
1127 "LastRefreshDate": now_ts()
1128 });
1129 data.refresh_schemas_status
1130 .insert(ep_arn.clone(), status.clone());
1131 ok(json!({ "RefreshSchemasStatus": status }))
1132 }
1133
1134 fn describe_refresh_schemas_status(
1135 &self,
1136 ctx: &Ctx,
1137 b: &Value,
1138 ) -> Result<AwsResponse, AwsServiceError> {
1139 let ep_arn = req_str_allow_empty(b, "EndpointArn")?.to_string();
1140 let guard = self.state.read();
1141 let data = guard.get(&ctx.account);
1142 if let Some(status) = data.and_then(|d| d.refresh_schemas_status.get(&ep_arn)) {
1143 return ok(json!({ "RefreshSchemasStatus": status.clone() }));
1144 }
1145 if data
1146 .map(|d| d.endpoints.contains_key(&ep_arn))
1147 .unwrap_or(false)
1148 {
1149 return ok(json!({
1150 "RefreshSchemasStatus": {
1151 "EndpointArn": ep_arn,
1152 "Status": "successful",
1153 "LastRefreshDate": now_ts()
1154 }
1155 }));
1156 }
1157 Err(not_found(&format!("Endpoint {ep_arn} not found.")))
1158 }
1159
1160 fn describe_schemas(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1161 let ep_arn = req_str_allow_empty(b, "EndpointArn")?.to_string();
1162 let guard = self.state.read();
1163 if !guard
1164 .get(&ctx.account)
1165 .map(|d| d.endpoints.contains_key(&ep_arn))
1166 .unwrap_or(false)
1167 {
1168 return Err(not_found(&format!("Endpoint {ep_arn} not found.")));
1169 }
1170 ok(json!({ "Schemas": [] }))
1171 }
1172
1173 fn describe_endpoint_settings(&self, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1174 let engine = req_str(b, "EngineName")?;
1175 let rows = vec![
1176 json!({ "Name": "ServerName", "Type": "string", "Applicability": engine }),
1177 json!({ "Name": "Port", "Type": "integer", "IntValueMin": 1, "IntValueMax": 65535 }),
1178 json!({ "Name": "DatabaseName", "Type": "string" }),
1179 ];
1180 list_marker("EndpointSettings", rows, b, &[])
1181 }
1182
1183 fn describe_endpoint_types(&self, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1184 let rows: Vec<Value> = [
1185 ("mysql", true),
1186 ("postgres", true),
1187 ("oracle", true),
1188 ("sqlserver", true),
1189 ("s3", false),
1190 ("dynamodb", false),
1191 ("kinesis", false),
1192 ("mariadb", true),
1193 ("aurora", true),
1194 ("aurora-postgresql", true),
1195 ]
1196 .iter()
1197 .flat_map(|(engine, cdc)| {
1198 ["source", "target"].iter().map(move |et| {
1199 json!({
1200 "EngineName": engine,
1201 "SupportsCDC": cdc,
1202 "EndpointType": et,
1203 "EngineDisplayName": engine,
1204 "ReplicationInstanceEngineMinimumVersion": "3.4.7"
1205 })
1206 })
1207 })
1208 .collect();
1209 list_marker(
1210 "SupportedEndpointTypes",
1211 rows,
1212 b,
1213 &[
1214 ("engine-name", &["EngineName"]),
1215 ("endpoint-type", &["EndpointType"]),
1216 ],
1217 )
1218 }
1219}
1220
1221impl DmsService {
1224 fn create_replication_task(
1225 &self,
1226 ctx: &Ctx,
1227 b: &Value,
1228 ) -> Result<AwsResponse, AwsServiceError> {
1229 let id = req_str_allow_empty(b, "ReplicationTaskIdentifier")?.to_string();
1230 let src = req_str_allow_empty(b, "SourceEndpointArn")?.to_string();
1231 let tgt = req_str_allow_empty(b, "TargetEndpointArn")?.to_string();
1232 let inst = req_str_allow_empty(b, "ReplicationInstanceArn")?.to_string();
1233 let mig_type = req_str_allow_empty(b, "MigrationType")?.to_string();
1234 check_migration_type(&mig_type)?;
1235 let table_mappings = req_str_allow_empty(b, "TableMappings")?.to_string();
1236 let mut guard = self.state.write();
1237 let data = guard.get_or_create(&ctx.account);
1238 if !data.replication_instances.contains_key(&inst) {
1240 return Err(not_found(&format!(
1241 "Replication instance {inst} not found."
1242 )));
1243 }
1244 if !data.endpoints.contains_key(&src) {
1245 return Err(not_found(&format!("Source endpoint {src} not found.")));
1246 }
1247 if !data.endpoints.contains_key(&tgt) {
1248 return Err(not_found(&format!("Target endpoint {tgt} not found.")));
1249 }
1250 if !id.is_empty()
1251 && data
1252 .replication_tasks
1253 .values()
1254 .any(|v| v.get("ReplicationTaskIdentifier").and_then(Value::as_str) == Some(&id))
1255 {
1256 return Err(already_exists(&format!(
1257 "Replication task {id} already exists."
1258 )));
1259 }
1260 let task_arn = arn(ctx, "task", &res_id());
1261 let settings = opt_str(b, "ReplicationTaskSettings")
1262 .filter(|s| !s.is_empty())
1263 .map(String::from)
1264 .unwrap_or_else(default_task_settings);
1265 let mut task = Map::new();
1266 task.insert("ReplicationTaskArn".into(), json!(task_arn));
1267 task.insert("ReplicationTaskIdentifier".into(), json!(id));
1268 task.insert("SourceEndpointArn".into(), json!(src));
1269 task.insert("TargetEndpointArn".into(), json!(tgt));
1270 task.insert("ReplicationInstanceArn".into(), json!(inst));
1271 task.insert("MigrationType".into(), json!(mig_type));
1272 task.insert("TableMappings".into(), json!(table_mappings));
1273 task.insert("ReplicationTaskSettings".into(), json!(settings));
1274 task.insert("Status".into(), json!("ready"));
1275 task.insert("ReplicationTaskCreationDate".into(), json!(now_ts()));
1276 copy_fields(
1277 b,
1278 &mut task,
1279 &["CdcStartPosition", "CdcStopPosition", "TaskData"],
1280 );
1281 task.insert(
1282 "ReplicationTaskStats".into(),
1283 json!({
1284 "FullLoadProgressPercent": 0,
1285 "TablesLoaded": 0,
1286 "TablesLoading": 0,
1287 "TablesQueued": 0,
1288 "TablesErrored": 0
1289 }),
1290 );
1291 let task = Value::Object(task);
1292 data.replication_tasks
1293 .insert(task_arn.clone(), task.clone());
1294 store_tags(data, &task_arn, b);
1295 ok(json!({ "ReplicationTask": task }))
1296 }
1297
1298 fn describe_replication_tasks(
1299 &self,
1300 ctx: &Ctx,
1301 b: &Value,
1302 ) -> Result<AwsResponse, AwsServiceError> {
1303 let guard = self.state.read();
1304 let rows = guard
1305 .get(&ctx.account)
1306 .map(|d| d.replication_tasks.values().cloned().collect())
1307 .unwrap_or_default();
1308 list_marker(
1309 "ReplicationTasks",
1310 rows,
1311 b,
1312 &[
1313 ("replication-task-arn", &["ReplicationTaskArn"]),
1314 ("replication-task-id", &["ReplicationTaskIdentifier"]),
1315 ("migration-type", &["MigrationType"]),
1316 ("endpoint-arn", &["SourceEndpointArn", "TargetEndpointArn"]),
1317 ("replication-instance-arn", &["ReplicationInstanceArn"]),
1318 ],
1319 )
1320 }
1321
1322 fn modify_replication_task(
1323 &self,
1324 ctx: &Ctx,
1325 b: &Value,
1326 ) -> Result<AwsResponse, AwsServiceError> {
1327 let arn_id = req_str(b, "ReplicationTaskArn")?.to_string();
1328 if let Some(mt) = opt_str(b, "MigrationType") {
1329 check_migration_type(mt)?;
1330 }
1331 let mut guard = self.state.write();
1332 let data = guard.get_or_create(&ctx.account);
1333 let Some(task) = data.replication_tasks.get_mut(&arn_id) else {
1334 return Err(not_found(&format!("Replication task {arn_id} not found.")));
1335 };
1336 let obj = task.as_object_mut().unwrap();
1337 for f in [
1338 "ReplicationTaskIdentifier",
1339 "MigrationType",
1340 "TableMappings",
1341 "ReplicationTaskSettings",
1342 "CdcStartPosition",
1343 "CdcStopPosition",
1344 "TaskData",
1345 ] {
1346 if let Some(v) = b.get(f) {
1347 obj.insert(f.to_string(), v.clone());
1348 }
1349 }
1350 let task = task.clone();
1351 ok(json!({ "ReplicationTask": task }))
1352 }
1353
1354 fn delete_replication_task(
1355 &self,
1356 ctx: &Ctx,
1357 b: &Value,
1358 ) -> Result<AwsResponse, AwsServiceError> {
1359 let arn_id = req_str(b, "ReplicationTaskArn")?.to_string();
1360 let mut guard = self.state.write();
1361 let data = guard.get_or_create(&ctx.account);
1362 let Some(mut task) = data.replication_tasks.remove(&arn_id) else {
1363 return Err(not_found(&format!("Replication task {arn_id} not found.")));
1364 };
1365 data.tags.remove(&arn_id);
1366 task["Status"] = json!("deleting");
1367 ok(json!({ "ReplicationTask": task }))
1368 }
1369
1370 fn set_task_status(
1371 &self,
1372 ctx: &Ctx,
1373 b: &Value,
1374 status: &str,
1375 ) -> Result<AwsResponse, AwsServiceError> {
1376 let arn_id = req_str(b, "ReplicationTaskArn")?.to_string();
1377 let mut guard = self.state.write();
1378 let data = guard.get_or_create(&ctx.account);
1379 let Some(task) = data.replication_tasks.get_mut(&arn_id) else {
1380 return Err(not_found(&format!("Replication task {arn_id} not found.")));
1381 };
1382 task["Status"] = json!(status);
1383 if status == "running" {
1384 task["ReplicationTaskStartDate"] = json!(now_ts());
1385 }
1386 let task = task.clone();
1387 ok(json!({ "ReplicationTask": task }))
1388 }
1389
1390 fn start_replication_task(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1391 if let Some(t) = opt_str(b, "StartReplicationTaskType") {
1392 const ALLOWED: &[&str] = &["start-replication", "resume-processing", "reload-target"];
1393 if !t.is_empty() && !ALLOWED.contains(&t) {
1394 return Err(validation(
1395 "StartReplicationTaskType must be one of [start-replication, resume-processing, reload-target].",
1396 ));
1397 }
1398 }
1399 self.set_task_status(ctx, b, "running")
1400 }
1401
1402 fn stop_replication_task(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1403 self.set_task_status(ctx, b, "stopped")
1404 }
1405
1406 fn start_replication_task_assessment(
1407 &self,
1408 ctx: &Ctx,
1409 b: &Value,
1410 ) -> Result<AwsResponse, AwsServiceError> {
1411 self.set_task_status(ctx, b, "testing")
1412 }
1413
1414 fn move_replication_task(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1415 let arn_id = req_str(b, "ReplicationTaskArn")?.to_string();
1416 let target_inst = req_str(b, "TargetReplicationInstanceArn")?.to_string();
1417 let mut guard = self.state.write();
1418 let data = guard.get_or_create(&ctx.account);
1419 if !data.replication_instances.contains_key(&target_inst) {
1420 return Err(not_found(&format!(
1421 "Target replication instance {target_inst} not found."
1422 )));
1423 }
1424 let Some(task) = data.replication_tasks.get_mut(&arn_id) else {
1425 return Err(not_found(&format!("Replication task {arn_id} not found.")));
1426 };
1427 task["ReplicationInstanceArn"] = json!(target_inst);
1428 task["Status"] = json!("moving");
1429 let task = task.clone();
1430 ok(json!({ "ReplicationTask": task }))
1431 }
1432
1433 fn start_replication_task_assessment_run(
1434 &self,
1435 ctx: &Ctx,
1436 b: &Value,
1437 ) -> Result<AwsResponse, AwsServiceError> {
1438 let task_arn = req_str(b, "ReplicationTaskArn")?.to_string();
1439 req_str(b, "ServiceAccessRoleArn")?;
1440 req_str(b, "ResultLocationBucket")?;
1441 let run_name = req_str(b, "AssessmentRunName")?.to_string();
1442 let mut guard = self.state.write();
1443 let data = guard.get_or_create(&ctx.account);
1444 if !data.replication_tasks.contains_key(&task_arn) {
1445 return Err(not_found(&format!(
1446 "Replication task {task_arn} not found."
1447 )));
1448 }
1449 if data
1450 .assessment_runs
1451 .values()
1452 .any(|r| r.get("AssessmentRunName").and_then(Value::as_str) == Some(&run_name))
1453 {
1454 return Err(already_exists(&format!(
1455 "Assessment run {run_name} already exists."
1456 )));
1457 }
1458 let run_arn = arn(ctx, "assessment-run", &res_id());
1459 let mut run = Map::new();
1460 run.insert("ReplicationTaskAssessmentRunArn".into(), json!(run_arn));
1461 run.insert("ReplicationTaskArn".into(), json!(task_arn));
1462 run.insert("Status".into(), json!("running"));
1463 run.insert(
1464 "ReplicationTaskAssessmentRunCreationDate".into(),
1465 json!(now_ts()),
1466 );
1467 run.insert("AssessmentRunName".into(), json!(run_name));
1468 run.insert("IsLatestTaskAssessmentRun".into(), json!(true));
1469 copy_fields(
1470 b,
1471 &mut run,
1472 &[
1473 "ServiceAccessRoleArn",
1474 "ResultLocationBucket",
1475 "ResultLocationFolder",
1476 "ResultEncryptionMode",
1477 "ResultKmsKeyArn",
1478 ],
1479 );
1480 let run = Value::Object(run);
1481 data.assessment_runs.insert(run_arn.clone(), run.clone());
1482 store_tags(data, &run_arn, b);
1483 ok(json!({ "ReplicationTaskAssessmentRun": run }))
1484 }
1485
1486 fn cancel_replication_task_assessment_run(
1487 &self,
1488 ctx: &Ctx,
1489 b: &Value,
1490 ) -> Result<AwsResponse, AwsServiceError> {
1491 let arn_id = req_str(b, "ReplicationTaskAssessmentRunArn")?.to_string();
1492 let mut guard = self.state.write();
1493 let data = guard.get_or_create(&ctx.account);
1494 let Some(run) = data.assessment_runs.get_mut(&arn_id) else {
1495 return Err(not_found(&format!("Assessment run {arn_id} not found.")));
1496 };
1497 run["Status"] = json!("cancelling");
1498 let run = run.clone();
1499 ok(json!({ "ReplicationTaskAssessmentRun": run }))
1500 }
1501
1502 fn delete_replication_task_assessment_run(
1503 &self,
1504 ctx: &Ctx,
1505 b: &Value,
1506 ) -> Result<AwsResponse, AwsServiceError> {
1507 let arn_id = req_str(b, "ReplicationTaskAssessmentRunArn")?.to_string();
1508 let mut guard = self.state.write();
1509 let data = guard.get_or_create(&ctx.account);
1510 let Some(run) = data.assessment_runs.remove(&arn_id) else {
1511 return Err(not_found(&format!("Assessment run {arn_id} not found.")));
1512 };
1513 ok(json!({ "ReplicationTaskAssessmentRun": run }))
1514 }
1515
1516 fn describe_replication_task_assessment_runs(
1517 &self,
1518 ctx: &Ctx,
1519 b: &Value,
1520 ) -> Result<AwsResponse, AwsServiceError> {
1521 let guard = self.state.read();
1522 let rows = guard
1523 .get(&ctx.account)
1524 .map(|d| d.assessment_runs.values().cloned().collect())
1525 .unwrap_or_default();
1526 list_marker(
1527 "ReplicationTaskAssessmentRuns",
1528 rows,
1529 b,
1530 &[("replication-task-arn", &["ReplicationTaskArn"])],
1531 )
1532 }
1533
1534 fn describe_replication_task_assessment_results(
1535 &self,
1536 ctx: &Ctx,
1537 b: &Value,
1538 ) -> Result<AwsResponse, AwsServiceError> {
1539 let _ = (ctx, b);
1540 ok(json!({ "BucketName": "", "ReplicationTaskAssessmentResults": [] }))
1541 }
1542
1543 fn describe_replication_task_individual_assessments(
1544 &self,
1545 b: &Value,
1546 ) -> Result<AwsResponse, AwsServiceError> {
1547 list_marker("ReplicationTaskIndividualAssessments", vec![], b, &[])
1548 }
1549
1550 fn describe_applicable_individual_assessments(
1551 &self,
1552 b: &Value,
1553 ) -> Result<AwsResponse, AwsServiceError> {
1554 if let Some(mt) = opt_str(b, "MigrationType") {
1555 check_migration_type(mt)?;
1556 }
1557 let rows: Vec<Value> = [
1558 "unsupported-data-types-in-source",
1559 "table-with-lob-but-no-primary-key",
1560 ]
1561 .iter()
1562 .map(|n| json!(n))
1563 .collect();
1564 list_marker("IndividualAssessmentNames", rows, b, &[])
1565 }
1566
1567 fn describe_table_statistics(
1568 &self,
1569 ctx: &Ctx,
1570 b: &Value,
1571 ) -> Result<AwsResponse, AwsServiceError> {
1572 let task_arn = req_str_allow_empty(b, "ReplicationTaskArn")?.to_string();
1573 let guard = self.state.read();
1574 if !guard
1575 .get(&ctx.account)
1576 .map(|d| d.replication_tasks.contains_key(&task_arn))
1577 .unwrap_or(false)
1578 {
1579 return Err(not_found(&format!(
1580 "Replication task {task_arn} not found."
1581 )));
1582 }
1583 let mut out = Map::new();
1584 out.insert("ReplicationTaskArn".into(), json!(task_arn));
1585 out.insert("TableStatistics".into(), json!([]));
1586 ok(Value::Object(out))
1587 }
1588
1589 fn reload_tables(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1590 let task_arn = req_str(b, "ReplicationTaskArn")?.to_string();
1591 if b.get("TablesToReload").and_then(Value::as_array).is_none() {
1592 return Err(validation("TablesToReload is required."));
1593 }
1594 let guard = self.state.read();
1595 if !guard
1596 .get(&ctx.account)
1597 .map(|d| d.replication_tasks.contains_key(&task_arn))
1598 .unwrap_or(false)
1599 {
1600 return Err(not_found(&format!(
1601 "Replication task {task_arn} not found."
1602 )));
1603 }
1604 ok(json!({ "ReplicationTaskArn": task_arn }))
1605 }
1606}
1607
1608fn check_migration_type(t: &str) -> Result<(), AwsServiceError> {
1609 const ALLOWED: &[&str] = &["full-load", "cdc", "full-load-and-cdc"];
1610 if !t.is_empty() && !ALLOWED.contains(&t) {
1611 return Err(validation(
1612 "MigrationType must be one of [full-load, cdc, full-load-and-cdc].",
1613 ));
1614 }
1615 Ok(())
1616}
1617
1618fn default_task_settings() -> String {
1619 json!({
1620 "TargetMetadata": { "TargetSchema": "", "SupportLobs": true, "FullLobMode": false, "LobChunkSize": 64 },
1621 "FullLoadSettings": { "TargetTablePrepMode": "DROP_AND_CREATE", "MaxFullLoadSubTasks": 8, "CommitRate": 10000 },
1622 "Logging": { "EnableLogging": false }
1623 })
1624 .to_string()
1625}
1626
1627impl DmsService {
1630 fn create_subnet_group(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1631 let id = req_str_allow_empty(b, "ReplicationSubnetGroupIdentifier")?.to_string();
1632 let desc = req_str_allow_empty(b, "ReplicationSubnetGroupDescription")?.to_string();
1633 let subnet_ids: Vec<String> = b
1634 .get("SubnetIds")
1635 .and_then(Value::as_array)
1636 .map(|a| {
1637 a.iter()
1638 .filter_map(|v| v.as_str().map(String::from))
1639 .collect()
1640 })
1641 .ok_or_else(|| validation("SubnetIds is required."))?;
1642 let mut guard = self.state.write();
1643 let data = guard.get_or_create(&ctx.account);
1644 if !id.is_empty() && data.subnet_groups.contains_key(&id) {
1645 return Err(already_exists(&format!(
1646 "Subnet group {id} already exists."
1647 )));
1648 }
1649 let subnets: Vec<Value> = subnet_ids
1650 .iter()
1651 .enumerate()
1652 .map(|(i, sid)| {
1653 json!({
1654 "SubnetIdentifier": sid,
1655 "SubnetStatus": "Active",
1656 "SubnetAvailabilityZone": { "Name": format!("{}{}", ctx.region, (b'a' + (i % 3) as u8) as char) }
1657 })
1658 })
1659 .collect();
1660 let group = json!({
1661 "ReplicationSubnetGroupIdentifier": id,
1662 "ReplicationSubnetGroupDescription": desc,
1663 "VpcId": "vpc-0a1b2c3d",
1664 "SubnetGroupStatus": "Complete",
1665 "SupportedNetworkTypes": ["IPV4"],
1666 "Subnets": subnets
1667 });
1668 data.subnet_groups.insert(id.clone(), group.clone());
1669 let group_arn = arn(ctx, "subgrp", &id);
1670 store_tags(data, &group_arn, b);
1671 ok(json!({ "ReplicationSubnetGroup": group }))
1672 }
1673
1674 fn describe_subnet_groups(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1675 let guard = self.state.read();
1676 let rows = guard
1677 .get(&ctx.account)
1678 .map(|d| d.subnet_groups.values().cloned().collect())
1679 .unwrap_or_default();
1680 list_marker(
1681 "ReplicationSubnetGroups",
1682 rows,
1683 b,
1684 &[(
1685 "replication-subnet-group-id",
1686 &["ReplicationSubnetGroupIdentifier"],
1687 )],
1688 )
1689 }
1690
1691 fn modify_subnet_group(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1692 let id = req_str_allow_empty(b, "ReplicationSubnetGroupIdentifier")?.to_string();
1693 if b.get("SubnetIds").and_then(Value::as_array).is_none() {
1694 return Err(validation("SubnetIds is required."));
1695 }
1696 let mut guard = self.state.write();
1697 let data = guard.get_or_create(&ctx.account);
1698 let Some(group) = data.subnet_groups.get_mut(&id) else {
1699 return Err(not_found(&format!("Subnet group {id} not found.")));
1700 };
1701 let obj = group.as_object_mut().unwrap();
1702 if let Some(desc) = opt_str(b, "ReplicationSubnetGroupDescription") {
1703 obj.insert("ReplicationSubnetGroupDescription".into(), json!(desc));
1704 }
1705 if let Some(ids) = b.get("SubnetIds").and_then(Value::as_array) {
1706 let subnets: Vec<Value> = ids
1707 .iter()
1708 .filter_map(|v| v.as_str())
1709 .enumerate()
1710 .map(|(i, sid)| {
1711 json!({
1712 "SubnetIdentifier": sid,
1713 "SubnetStatus": "Active",
1714 "SubnetAvailabilityZone": { "Name": format!("{}{}", ctx.region, (b'a' + (i % 3) as u8) as char) }
1715 })
1716 })
1717 .collect();
1718 obj.insert("Subnets".into(), json!(subnets));
1719 }
1720 let group = group.clone();
1721 ok(json!({ "ReplicationSubnetGroup": group }))
1722 }
1723
1724 fn delete_subnet_group(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1725 let id = req_str(b, "ReplicationSubnetGroupIdentifier")?.to_string();
1726 let mut guard = self.state.write();
1727 let data = guard.get_or_create(&ctx.account);
1728 if data.subnet_groups.remove(&id).is_none() {
1729 return Err(not_found(&format!("Subnet group {id} not found.")));
1730 }
1731 data.tags.remove(&arn(ctx, "subgrp", &id));
1732 ok(json!({}))
1733 }
1734}
1735
1736impl DmsService {
1739 fn create_event_subscription(
1740 &self,
1741 ctx: &Ctx,
1742 b: &Value,
1743 ) -> Result<AwsResponse, AwsServiceError> {
1744 let name = req_str_allow_empty(b, "SubscriptionName")?.to_string();
1745 let topic = req_str_allow_empty(b, "SnsTopicArn")?.to_string();
1746 let mut guard = self.state.write();
1747 let data = guard.get_or_create(&ctx.account);
1748 if !name.is_empty() && data.event_subscriptions.contains_key(&name) {
1749 return Err(already_exists(&format!(
1750 "Event subscription {name} already exists."
1751 )));
1752 }
1753 let mut sub = Map::new();
1754 sub.insert("CustomerAwsId".into(), json!(ctx.account));
1755 sub.insert("CustSubscriptionId".into(), json!(name));
1756 sub.insert("SnsTopicArn".into(), json!(topic));
1757 sub.insert("Status".into(), json!("active"));
1758 sub.insert(
1759 "SubscriptionCreationTime".into(),
1760 json!(chrono::Utc::now().to_rfc3339()),
1761 );
1762 sub.insert(
1763 "Enabled".into(),
1764 b.get("Enabled").cloned().unwrap_or(json!(true)),
1765 );
1766 if let Some(st) = opt_str(b, "SourceType") {
1767 sub.insert("SourceType".into(), json!(st));
1768 }
1769 if let Some(cats) = b.get("EventCategories") {
1770 sub.insert("EventCategoriesList".into(), cats.clone());
1771 }
1772 if let Some(ids) = b.get("SourceIds") {
1773 sub.insert("SourceIdsList".into(), ids.clone());
1774 }
1775 let sub = Value::Object(sub);
1776 data.event_subscriptions.insert(name.clone(), sub.clone());
1777 store_tags(data, &arn(ctx, "es", &name), b);
1778 ok(json!({ "EventSubscription": sub }))
1779 }
1780
1781 fn describe_event_subscriptions(
1782 &self,
1783 ctx: &Ctx,
1784 b: &Value,
1785 ) -> Result<AwsResponse, AwsServiceError> {
1786 let name = opt_str(b, "SubscriptionName")
1787 .filter(|s| !s.is_empty())
1788 .map(String::from);
1789 let guard = self.state.read();
1790 let subs = guard.get(&ctx.account).map(|d| &d.event_subscriptions);
1791 if let Some(n) = &name {
1795 if !subs.map(|s| s.contains_key(n)).unwrap_or(false) {
1796 return Err(not_found(&format!("Event subscription {n} not found.")));
1797 }
1798 }
1799 let rows: Vec<Value> = subs
1800 .map(|s| {
1801 s.iter()
1802 .filter(|(k, _)| name.as_ref().map(|n| *k == n).unwrap_or(true))
1803 .map(|(_, v)| v.clone())
1804 .collect()
1805 })
1806 .unwrap_or_default();
1807 list_marker(
1808 "EventSubscriptionsList",
1809 rows,
1810 b,
1811 &[
1812 ("event-subscription-arn", &["EventSubscriptionArn"]),
1813 ("event-subscription-id", &["CustSubscriptionId"]),
1814 ],
1815 )
1816 }
1817
1818 fn modify_event_subscription(
1819 &self,
1820 ctx: &Ctx,
1821 b: &Value,
1822 ) -> Result<AwsResponse, AwsServiceError> {
1823 let name = req_str(b, "SubscriptionName")?.to_string();
1824 let mut guard = self.state.write();
1825 let data = guard.get_or_create(&ctx.account);
1826 let Some(sub) = data.event_subscriptions.get_mut(&name) else {
1827 return Err(not_found(&format!("Event subscription {name} not found.")));
1828 };
1829 let obj = sub.as_object_mut().unwrap();
1830 if let Some(v) = b.get("SnsTopicArn") {
1831 obj.insert("SnsTopicArn".into(), v.clone());
1832 }
1833 if let Some(v) = b.get("SourceType") {
1834 obj.insert("SourceType".into(), v.clone());
1835 }
1836 if let Some(v) = b.get("EventCategories") {
1837 obj.insert("EventCategoriesList".into(), v.clone());
1838 }
1839 if let Some(v) = b.get("Enabled") {
1840 obj.insert("Enabled".into(), v.clone());
1841 }
1842 let sub = sub.clone();
1843 ok(json!({ "EventSubscription": sub }))
1844 }
1845
1846 fn delete_event_subscription(
1847 &self,
1848 ctx: &Ctx,
1849 b: &Value,
1850 ) -> Result<AwsResponse, AwsServiceError> {
1851 let name = req_str(b, "SubscriptionName")?.to_string();
1852 let mut guard = self.state.write();
1853 let data = guard.get_or_create(&ctx.account);
1854 let Some(sub) = data.event_subscriptions.remove(&name) else {
1855 return Err(not_found(&format!("Event subscription {name} not found.")));
1856 };
1857 data.tags.remove(&arn(ctx, "es", &name));
1858 ok(json!({ "EventSubscription": sub }))
1859 }
1860
1861 fn describe_event_categories(&self, _b: &Value) -> Result<AwsResponse, AwsServiceError> {
1862 ok(json!({
1863 "EventCategoryGroupList": [
1864 {
1865 "SourceType": "replication-instance",
1866 "EventCategories": ["configuration change", "creation", "deletion", "failover", "failure", "maintenance"]
1867 },
1868 {
1869 "SourceType": "replication-task",
1870 "EventCategories": ["configuration change", "creation", "deletion", "failure", "state change"]
1871 }
1872 ]
1873 }))
1874 }
1875
1876 fn describe_events(&self, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1877 if let Some(st) = opt_str(b, "SourceType") {
1878 if !st.is_empty() && st != "replication-instance" {
1879 return Err(validation("SourceType must be replication-instance."));
1880 }
1881 }
1882 list_marker("Events", vec![], b, &[])
1883 }
1884
1885 fn update_subscriptions_to_eventbridge(
1886 &self,
1887 _b: &Value,
1888 ) -> Result<AwsResponse, AwsServiceError> {
1889 ok(json!({ "Result": "Successfully migrated subscriptions to EventBridge." }))
1890 }
1891}
1892
1893impl DmsService {
1896 fn import_certificate(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1897 let id = req_str_allow_empty(b, "CertificateIdentifier")?.to_string();
1898 let mut guard = self.state.write();
1899 let data = guard.get_or_create(&ctx.account);
1900 if !id.is_empty()
1901 && data
1902 .certificates
1903 .values()
1904 .any(|v| v.get("CertificateIdentifier").and_then(Value::as_str) == Some(&id))
1905 {
1906 return Err(already_exists(&format!("Certificate {id} already exists.")));
1907 }
1908 let cert_arn = arn(ctx, "cert", &res_id());
1909 let mut cert = Map::new();
1910 cert.insert("CertificateArn".into(), json!(cert_arn));
1911 cert.insert("CertificateIdentifier".into(), json!(id));
1912 cert.insert("CertificateCreationDate".into(), json!(now_ts()));
1913 cert.insert("CertificateOwner".into(), json!(ctx.account));
1914 cert.insert("SigningAlgorithm".into(), json!("SHA256withRSA"));
1915 cert.insert("KeyLength".into(), json!(2048));
1916 cert.insert("ValidFromDate".into(), json!(now_ts()));
1917 cert.insert(
1918 "ValidToDate".into(),
1919 json!(now_ts() + 365.0 * 24.0 * 3600.0),
1920 );
1921 if let Some(pem) = opt_str(b, "CertificatePem") {
1922 cert.insert("CertificatePem".into(), json!(pem));
1923 }
1924 if let Some(kms) = opt_str(b, "KmsKeyId") {
1925 cert.insert("KmsKeyId".into(), json!(kms));
1926 }
1927 let cert = Value::Object(cert);
1928 data.certificates.insert(cert_arn.clone(), cert.clone());
1929 store_tags(data, &cert_arn, b);
1930 ok(json!({ "Certificate": cert }))
1931 }
1932
1933 fn describe_certificates(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1934 let guard = self.state.read();
1935 let rows = guard
1936 .get(&ctx.account)
1937 .map(|d| d.certificates.values().cloned().collect())
1938 .unwrap_or_default();
1939 list_marker(
1940 "Certificates",
1941 rows,
1942 b,
1943 &[("certificate-id", &["CertificateIdentifier"])],
1944 )
1945 }
1946
1947 fn delete_certificate(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1948 let arn_id = req_str(b, "CertificateArn")?.to_string();
1949 let mut guard = self.state.write();
1950 let data = guard.get_or_create(&ctx.account);
1951 let Some(cert) = data.certificates.remove(&arn_id) else {
1952 return Err(not_found(&format!("Certificate {arn_id} not found.")));
1953 };
1954 data.tags.remove(&arn_id);
1955 ok(json!({ "Certificate": cert }))
1956 }
1957}
1958
1959impl DmsService {
1962 fn create_replication_config(
1963 &self,
1964 ctx: &Ctx,
1965 b: &Value,
1966 ) -> Result<AwsResponse, AwsServiceError> {
1967 let id = req_str_allow_empty(b, "ReplicationConfigIdentifier")?.to_string();
1968 let src = req_str_allow_empty(b, "SourceEndpointArn")?.to_string();
1969 let tgt = req_str_allow_empty(b, "TargetEndpointArn")?.to_string();
1970 let rep_type = req_str_allow_empty(b, "ReplicationType")?.to_string();
1971 check_replication_type(&rep_type)?;
1972 let table_mappings = req_str_allow_empty(b, "TableMappings")?.to_string();
1973 let compute = b
1974 .get("ComputeConfig")
1975 .cloned()
1976 .ok_or_else(|| validation("ComputeConfig is required."))?;
1977 let mut guard = self.state.write();
1978 let data = guard.get_or_create(&ctx.account);
1979 if !id.is_empty()
1980 && data
1981 .replication_configs
1982 .values()
1983 .any(|v| v.get("ReplicationConfigIdentifier").and_then(Value::as_str) == Some(&id))
1984 {
1985 return Err(already_exists(&format!(
1986 "Replication config {id} already exists."
1987 )));
1988 }
1989 let config_arn = arn(ctx, "replication-config", &res_id());
1990 let mut cfg = Map::new();
1991 cfg.insert("ReplicationConfigArn".into(), json!(config_arn));
1992 cfg.insert("ReplicationConfigIdentifier".into(), json!(id));
1993 cfg.insert("SourceEndpointArn".into(), json!(src));
1994 cfg.insert("TargetEndpointArn".into(), json!(tgt));
1995 cfg.insert("ReplicationType".into(), json!(rep_type));
1996 cfg.insert("ComputeConfig".into(), compute);
1997 cfg.insert("TableMappings".into(), json!(table_mappings));
1998 cfg.insert("ReplicationConfigCreateTime".into(), json!(now_ts()));
1999 cfg.insert("ReplicationConfigUpdateTime".into(), json!(now_ts()));
2000 copy_fields(
2001 b,
2002 &mut cfg,
2003 &["ReplicationSettings", "SupplementalSettings"],
2004 );
2005 let cfg = Value::Object(cfg);
2006 data.replication_configs
2007 .insert(config_arn.clone(), cfg.clone());
2008 store_tags(data, &config_arn, b);
2009 ok(json!({ "ReplicationConfig": cfg }))
2010 }
2011
2012 fn describe_replication_configs(
2013 &self,
2014 ctx: &Ctx,
2015 b: &Value,
2016 ) -> Result<AwsResponse, AwsServiceError> {
2017 let guard = self.state.read();
2018 let rows = guard
2019 .get(&ctx.account)
2020 .map(|d| d.replication_configs.values().cloned().collect())
2021 .unwrap_or_default();
2022 list_marker(
2023 "ReplicationConfigs",
2024 rows,
2025 b,
2026 &[
2027 ("replication-config-arn", &["ReplicationConfigArn"]),
2028 ("replication-config-id", &["ReplicationConfigIdentifier"]),
2029 ],
2030 )
2031 }
2032
2033 fn modify_replication_config(
2034 &self,
2035 ctx: &Ctx,
2036 b: &Value,
2037 ) -> Result<AwsResponse, AwsServiceError> {
2038 let arn_id = req_str(b, "ReplicationConfigArn")?.to_string();
2039 if let Some(rt) = opt_str(b, "ReplicationType") {
2040 check_replication_type(rt)?;
2041 }
2042 let mut guard = self.state.write();
2043 let data = guard.get_or_create(&ctx.account);
2044 let Some(cfg) = data.replication_configs.get_mut(&arn_id) else {
2045 return Err(not_found(&format!(
2046 "Replication config {arn_id} not found."
2047 )));
2048 };
2049 let obj = cfg.as_object_mut().unwrap();
2050 for f in [
2051 "ReplicationConfigIdentifier",
2052 "ReplicationType",
2053 "TableMappings",
2054 "ReplicationSettings",
2055 "SupplementalSettings",
2056 "ComputeConfig",
2057 "SourceEndpointArn",
2058 "TargetEndpointArn",
2059 ] {
2060 if let Some(v) = b.get(f) {
2061 obj.insert(f.to_string(), v.clone());
2062 }
2063 }
2064 obj.insert("ReplicationConfigUpdateTime".into(), json!(now_ts()));
2065 let cfg = cfg.clone();
2066 ok(json!({ "ReplicationConfig": cfg }))
2067 }
2068
2069 fn delete_replication_config(
2070 &self,
2071 ctx: &Ctx,
2072 b: &Value,
2073 ) -> Result<AwsResponse, AwsServiceError> {
2074 let arn_id = req_str(b, "ReplicationConfigArn")?.to_string();
2075 let mut guard = self.state.write();
2076 let data = guard.get_or_create(&ctx.account);
2077 let Some(cfg) = data.replication_configs.remove(&arn_id) else {
2078 return Err(not_found(&format!(
2079 "Replication config {arn_id} not found."
2080 )));
2081 };
2082 data.replications.remove(&arn_id);
2083 data.tags.remove(&arn_id);
2084 ok(json!({ "ReplicationConfig": cfg }))
2085 }
2086
2087 fn build_replication(&self, cfg: &Value, status: &str) -> Value {
2088 json!({
2089 "ReplicationConfigIdentifier": cfg.get("ReplicationConfigIdentifier").cloned().unwrap_or(json!("")),
2090 "ReplicationConfigArn": cfg.get("ReplicationConfigArn").cloned().unwrap_or(json!("")),
2091 "SourceEndpointArn": cfg.get("SourceEndpointArn").cloned().unwrap_or(json!("")),
2092 "TargetEndpointArn": cfg.get("TargetEndpointArn").cloned().unwrap_or(json!("")),
2093 "ReplicationType": cfg.get("ReplicationType").cloned().unwrap_or(json!("")),
2094 "Status": status,
2095 "ReplicationCreateTime": now_ts(),
2096 "ReplicationUpdateTime": now_ts(),
2097 "ProvisionData": { "ProvisionState": "provisioned", "ProvisionedCapacityUnits": 2 },
2098 "ReplicationStats": { "FullLoadProgressPercent": 0, "TablesLoaded": 0, "TablesLoading": 0, "TablesQueued": 0, "TablesErrored": 0 }
2099 })
2100 }
2101
2102 fn start_replication(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
2103 let arn_id = req_str(b, "ReplicationConfigArn")?.to_string();
2104 req_str(b, "StartReplicationType")?;
2105 let mut guard = self.state.write();
2106 let data = guard.get_or_create(&ctx.account);
2107 let Some(cfg) = data.replication_configs.get(&arn_id).cloned() else {
2108 return Err(not_found(&format!(
2109 "Replication config {arn_id} not found."
2110 )));
2111 };
2112 if data
2113 .replications
2114 .get(&arn_id)
2115 .and_then(|r| r.get("Status"))
2116 .and_then(Value::as_str)
2117 == Some("running")
2118 {
2119 return Err(invalid_state(&format!(
2120 "Replication for {arn_id} is already running."
2121 )));
2122 }
2123 let mut rep = self.build_replication(&cfg, "running");
2124 rep["StartReplicationType"] = b
2125 .get("StartReplicationType")
2126 .cloned()
2127 .unwrap_or(json!("start-replication"));
2128 for f in ["CdcStartPosition", "CdcStopPosition"] {
2129 if let Some(v) = b.get(f) {
2130 rep[f] = v.clone();
2131 }
2132 }
2133 data.replications.insert(arn_id, rep.clone());
2134 ok(json!({ "Replication": rep }))
2135 }
2136
2137 fn stop_replication(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
2138 let arn_id = req_str(b, "ReplicationConfigArn")?.to_string();
2139 let mut guard = self.state.write();
2140 let data = guard.get_or_create(&ctx.account);
2141 let Some(rep) = data.replications.get_mut(&arn_id) else {
2142 return Err(not_found(&format!("Replication for {arn_id} not found.")));
2143 };
2144 rep["Status"] = json!("stopped");
2145 rep["ReplicationLastStopTime"] = json!(now_ts());
2146 let rep = rep.clone();
2147 ok(json!({ "Replication": rep }))
2148 }
2149
2150 fn describe_replications(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
2151 let guard = self.state.read();
2152 let rows = guard
2153 .get(&ctx.account)
2154 .map(|d| d.replications.values().cloned().collect())
2155 .unwrap_or_default();
2156 list_marker(
2157 "Replications",
2158 rows,
2159 b,
2160 &[("replication-config-arn", &["ReplicationConfigArn"])],
2161 )
2162 }
2163
2164 fn describe_replication_table_statistics(
2165 &self,
2166 ctx: &Ctx,
2167 b: &Value,
2168 ) -> Result<AwsResponse, AwsServiceError> {
2169 let arn_id = req_str(b, "ReplicationConfigArn")?.to_string();
2170 let guard = self.state.read();
2171 if !guard
2172 .get(&ctx.account)
2173 .map(|d| d.replication_configs.contains_key(&arn_id))
2174 .unwrap_or(false)
2175 {
2176 return Err(not_found(&format!(
2177 "Replication config {arn_id} not found."
2178 )));
2179 }
2180 ok(json!({
2181 "ReplicationConfigArn": arn_id,
2182 "ReplicationTableStatistics": []
2183 }))
2184 }
2185
2186 fn reload_replication_tables(
2187 &self,
2188 ctx: &Ctx,
2189 b: &Value,
2190 ) -> Result<AwsResponse, AwsServiceError> {
2191 let arn_id = req_str(b, "ReplicationConfigArn")?.to_string();
2192 if b.get("TablesToReload").and_then(Value::as_array).is_none() {
2193 return Err(validation("TablesToReload is required."));
2194 }
2195 let guard = self.state.read();
2196 if !guard
2197 .get(&ctx.account)
2198 .map(|d| d.replication_configs.contains_key(&arn_id))
2199 .unwrap_or(false)
2200 {
2201 return Err(not_found(&format!(
2202 "Replication config {arn_id} not found."
2203 )));
2204 }
2205 ok(json!({ "ReplicationConfigArn": arn_id }))
2206 }
2207}
2208
2209fn check_replication_type(t: &str) -> Result<(), AwsServiceError> {
2210 const ALLOWED: &[&str] = &["full-load", "cdc", "full-load-and-cdc"];
2211 if !t.is_empty() && !ALLOWED.contains(&t) {
2212 return Err(validation(
2213 "ReplicationType must be one of [full-load, cdc, full-load-and-cdc].",
2214 ));
2215 }
2216 Ok(())
2217}
2218
2219impl DmsService {
2222 fn create_data_provider(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
2223 let engine = req_str_allow_empty(b, "Engine")?.to_string();
2224 let settings = b
2225 .get("Settings")
2226 .cloned()
2227 .ok_or_else(|| validation("Settings is required."))?;
2228 let name = opt_str(b, "DataProviderName")
2229 .filter(|s| !s.is_empty())
2230 .map(String::from)
2231 .unwrap_or_else(|| format!("data-provider-{}", &res_id()[..8].to_lowercase()));
2232 let mut guard = self.state.write();
2233 let data = guard.get_or_create(&ctx.account);
2234 if data
2235 .data_providers
2236 .values()
2237 .any(|v| v.get("DataProviderName").and_then(Value::as_str) == Some(&name))
2238 {
2239 return Err(already_exists(&format!(
2240 "Data provider {name} already exists."
2241 )));
2242 }
2243 let provider_arn = arn(ctx, "data-provider", &name);
2244 let mut dp = Map::new();
2245 dp.insert("DataProviderArn".into(), json!(provider_arn));
2246 dp.insert("DataProviderName".into(), json!(name));
2247 dp.insert("Engine".into(), json!(engine));
2248 dp.insert("Settings".into(), settings);
2249 dp.insert("DataProviderCreationTime".into(), json!(now_ts()));
2250 if let Some(d) = opt_str(b, "Description") {
2251 dp.insert("Description".into(), json!(d));
2252 }
2253 if let Some(v) = b.get("Virtual") {
2254 dp.insert("Virtual".into(), v.clone());
2255 }
2256 let dp = Value::Object(dp);
2257 data.data_providers.insert(provider_arn.clone(), dp.clone());
2258 store_tags(data, &provider_arn, b);
2259 ok(json!({ "DataProvider": dp }))
2260 }
2261
2262 fn describe_data_providers(
2263 &self,
2264 ctx: &Ctx,
2265 b: &Value,
2266 ) -> Result<AwsResponse, AwsServiceError> {
2267 let guard = self.state.read();
2268 let rows = guard
2269 .get(&ctx.account)
2270 .map(|d| d.data_providers.values().cloned().collect())
2271 .unwrap_or_default();
2272 list_marker(
2273 "DataProviders",
2274 rows,
2275 b,
2276 &[(
2277 "data-provider-identifier",
2278 &["DataProviderArn", "DataProviderName"],
2279 )],
2280 )
2281 }
2282
2283 fn resolve_data_provider<'a>(data: &'a mut DmsData, ident: &str) -> Option<&'a mut Value> {
2284 if data.data_providers.contains_key(ident) {
2285 return data.data_providers.get_mut(ident);
2286 }
2287 let key = data
2288 .data_providers
2289 .iter()
2290 .find(|(_, v)| v.get("DataProviderName").and_then(Value::as_str) == Some(ident))
2291 .map(|(k, _)| k.clone())?;
2292 data.data_providers.get_mut(&key)
2293 }
2294
2295 fn modify_data_provider(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
2296 let ident = req_str(b, "DataProviderIdentifier")?.to_string();
2297 let mut guard = self.state.write();
2298 let data = guard.get_or_create(&ctx.account);
2299 let Some(dp) = Self::resolve_data_provider(data, &ident) else {
2300 return Err(not_found(&format!("Data provider {ident} not found.")));
2301 };
2302 let obj = dp.as_object_mut().unwrap();
2303 for (in_f, out_f) in [
2304 ("DataProviderName", "DataProviderName"),
2305 ("Description", "Description"),
2306 ("Engine", "Engine"),
2307 ("Virtual", "Virtual"),
2308 ("Settings", "Settings"),
2309 ] {
2310 if let Some(v) = b.get(in_f) {
2311 obj.insert(out_f.to_string(), v.clone());
2312 }
2313 }
2314 let dp = dp.clone();
2315 ok(json!({ "DataProvider": dp }))
2316 }
2317
2318 fn delete_data_provider(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
2319 let ident = req_str(b, "DataProviderIdentifier")?.to_string();
2320 let mut guard = self.state.write();
2321 let data = guard.get_or_create(&ctx.account);
2322 let key = if data.data_providers.contains_key(&ident) {
2323 Some(ident.clone())
2324 } else {
2325 data.data_providers
2326 .iter()
2327 .find(|(_, v)| {
2328 v.get("DataProviderName").and_then(Value::as_str) == Some(ident.as_str())
2329 })
2330 .map(|(k, _)| k.clone())
2331 };
2332 let Some(key) = key else {
2333 return Err(not_found(&format!("Data provider {ident} not found.")));
2334 };
2335 let dp = data.data_providers.remove(&key).unwrap();
2336 data.tags.remove(&key);
2337 ok(json!({ "DataProvider": dp }))
2338 }
2339}
2340
2341impl DmsService {
2344 fn build_instance_profile(&self, ctx: &Ctx, b: &Value, profile_arn: &str, name: &str) -> Value {
2345 let mut ip = Map::new();
2346 ip.insert("InstanceProfileArn".into(), json!(profile_arn));
2347 ip.insert("InstanceProfileName".into(), json!(name));
2348 ip.insert("InstanceProfileCreationTime".into(), json!(now_ts()));
2349 ip.insert(
2350 "VpcSecurityGroups".into(),
2351 b.get("VpcSecurityGroups").cloned().unwrap_or(json!([])),
2352 );
2353 copy_fields(
2354 b,
2355 &mut ip,
2356 &[
2357 "AvailabilityZone",
2358 "KmsKeyArn",
2359 "PubliclyAccessible",
2360 "NetworkType",
2361 "Description",
2362 "SubnetGroupIdentifier",
2363 ],
2364 );
2365 let _ = ctx;
2366 Value::Object(ip)
2367 }
2368
2369 fn create_instance_profile(
2370 &self,
2371 ctx: &Ctx,
2372 b: &Value,
2373 ) -> Result<AwsResponse, AwsServiceError> {
2374 let name = opt_str(b, "InstanceProfileName")
2375 .filter(|s| !s.is_empty())
2376 .map(String::from)
2377 .unwrap_or_else(|| format!("instance-profile-{}", &res_id()[..8].to_lowercase()));
2378 let mut guard = self.state.write();
2379 let data = guard.get_or_create(&ctx.account);
2380 if data
2381 .instance_profiles
2382 .values()
2383 .any(|v| v.get("InstanceProfileName").and_then(Value::as_str) == Some(&name))
2384 {
2385 return Err(already_exists(&format!(
2386 "Instance profile {name} already exists."
2387 )));
2388 }
2389 let profile_arn = arn(ctx, "instance-profile", &name);
2390 let ip = self.build_instance_profile(ctx, b, &profile_arn, &name);
2391 data.instance_profiles
2392 .insert(profile_arn.clone(), ip.clone());
2393 store_tags(data, &profile_arn, b);
2394 ok(json!({ "InstanceProfile": ip }))
2395 }
2396
2397 fn describe_instance_profiles(
2398 &self,
2399 ctx: &Ctx,
2400 b: &Value,
2401 ) -> Result<AwsResponse, AwsServiceError> {
2402 let guard = self.state.read();
2403 let rows = guard
2404 .get(&ctx.account)
2405 .map(|d| d.instance_profiles.values().cloned().collect())
2406 .unwrap_or_default();
2407 list_marker(
2408 "InstanceProfiles",
2409 rows,
2410 b,
2411 &[(
2412 "instance-profile-identifier",
2413 &["InstanceProfileArn", "InstanceProfileName"],
2414 )],
2415 )
2416 }
2417
2418 fn resolve_instance_profile_key(data: &DmsData, ident: &str) -> Option<String> {
2419 if data.instance_profiles.contains_key(ident) {
2420 return Some(ident.to_string());
2421 }
2422 data.instance_profiles
2423 .iter()
2424 .find(|(_, v)| v.get("InstanceProfileName").and_then(Value::as_str) == Some(ident))
2425 .map(|(k, _)| k.clone())
2426 }
2427
2428 fn modify_instance_profile(
2429 &self,
2430 ctx: &Ctx,
2431 b: &Value,
2432 ) -> Result<AwsResponse, AwsServiceError> {
2433 let ident = req_str_allow_empty(b, "InstanceProfileIdentifier")?.to_string();
2434 let mut guard = self.state.write();
2435 let data = guard.get_or_create(&ctx.account);
2436 let Some(key) = Self::resolve_instance_profile_key(data, &ident) else {
2437 return Err(not_found(&format!("Instance profile {ident} not found.")));
2438 };
2439 let ip = data.instance_profiles.get_mut(&key).unwrap();
2440 let obj = ip.as_object_mut().unwrap();
2441 for f in [
2442 "AvailabilityZone",
2443 "KmsKeyArn",
2444 "PubliclyAccessible",
2445 "NetworkType",
2446 "InstanceProfileName",
2447 "Description",
2448 "SubnetGroupIdentifier",
2449 "VpcSecurityGroups",
2450 ] {
2451 if let Some(v) = b.get(f) {
2452 obj.insert(f.to_string(), v.clone());
2453 }
2454 }
2455 let ip = ip.clone();
2456 ok(json!({ "InstanceProfile": ip }))
2457 }
2458
2459 fn delete_instance_profile(
2460 &self,
2461 ctx: &Ctx,
2462 b: &Value,
2463 ) -> Result<AwsResponse, AwsServiceError> {
2464 let ident = req_str(b, "InstanceProfileIdentifier")?.to_string();
2465 let mut guard = self.state.write();
2466 let data = guard.get_or_create(&ctx.account);
2467 let Some(key) = Self::resolve_instance_profile_key(data, &ident) else {
2468 return Err(not_found(&format!("Instance profile {ident} not found.")));
2469 };
2470 let ip = data.instance_profiles.remove(&key).unwrap();
2471 data.tags.remove(&key);
2472 ok(json!({ "InstanceProfile": ip }))
2473 }
2474}
2475
2476fn build_provider_descriptors(input: Option<&Value>) -> Vec<Value> {
2479 input
2480 .and_then(Value::as_array)
2481 .map(|arr| {
2482 arr.iter()
2483 .map(|d| {
2484 let ident = d
2485 .get("DataProviderIdentifier")
2486 .and_then(Value::as_str)
2487 .unwrap_or("");
2488 let name = ident.rsplit(':').next().unwrap_or(ident);
2489 let mut m = Map::new();
2490 m.insert("DataProviderArn".into(), json!(ident));
2491 m.insert("DataProviderName".into(), json!(name));
2492 if let Some(v) = d.get("SecretsManagerSecretId") {
2493 m.insert("SecretsManagerSecretId".into(), v.clone());
2494 }
2495 if let Some(v) = d.get("SecretsManagerAccessRoleArn") {
2496 m.insert("SecretsManagerAccessRoleArn".into(), v.clone());
2497 }
2498 Value::Object(m)
2499 })
2500 .collect()
2501 })
2502 .unwrap_or_default()
2503}
2504
2505impl DmsService {
2506 fn create_migration_project(
2507 &self,
2508 ctx: &Ctx,
2509 b: &Value,
2510 ) -> Result<AwsResponse, AwsServiceError> {
2511 if b.get("SourceDataProviderDescriptors")
2512 .and_then(Value::as_array)
2513 .is_none()
2514 {
2515 return Err(validation("SourceDataProviderDescriptors is required."));
2516 }
2517 if b.get("TargetDataProviderDescriptors")
2518 .and_then(Value::as_array)
2519 .is_none()
2520 {
2521 return Err(validation("TargetDataProviderDescriptors is required."));
2522 }
2523 let profile_ident = req_str_allow_empty(b, "InstanceProfileIdentifier")?.to_string();
2524 let name = opt_str(b, "MigrationProjectName")
2525 .filter(|s| !s.is_empty())
2526 .map(String::from)
2527 .unwrap_or_else(|| format!("migration-project-{}", &res_id()[..8].to_lowercase()));
2528 let mut guard = self.state.write();
2529 let data = guard.get_or_create(&ctx.account);
2530 if data
2531 .migration_projects
2532 .values()
2533 .any(|v| v.get("MigrationProjectName").and_then(Value::as_str) == Some(&name))
2534 {
2535 return Err(already_exists(&format!(
2536 "Migration project {name} already exists."
2537 )));
2538 }
2539 let profile_name = profile_ident
2540 .rsplit(':')
2541 .next()
2542 .unwrap_or(&profile_ident)
2543 .to_string();
2544 let profile_arn = if profile_ident.starts_with("arn:") {
2545 profile_ident.clone()
2546 } else {
2547 arn(ctx, "instance-profile", &profile_ident)
2548 };
2549 let project_arn = arn(ctx, "migration-project", &name);
2550 let mut mp = Map::new();
2551 mp.insert("MigrationProjectArn".into(), json!(project_arn));
2552 mp.insert("MigrationProjectName".into(), json!(name));
2553 mp.insert("MigrationProjectCreationTime".into(), json!(now_ts()));
2554 mp.insert("InstanceProfileArn".into(), json!(profile_arn));
2555 mp.insert("InstanceProfileName".into(), json!(profile_name));
2556 mp.insert(
2557 "SourceDataProviderDescriptors".into(),
2558 json!(build_provider_descriptors(
2559 b.get("SourceDataProviderDescriptors")
2560 )),
2561 );
2562 mp.insert(
2563 "TargetDataProviderDescriptors".into(),
2564 json!(build_provider_descriptors(
2565 b.get("TargetDataProviderDescriptors")
2566 )),
2567 );
2568 copy_fields(
2569 b,
2570 &mut mp,
2571 &[
2572 "TransformationRules",
2573 "Description",
2574 "SchemaConversionApplicationAttributes",
2575 ],
2576 );
2577 let mp = Value::Object(mp);
2578 data.migration_projects
2579 .insert(project_arn.clone(), mp.clone());
2580 store_tags(data, &project_arn, b);
2581 ok(json!({ "MigrationProject": mp }))
2582 }
2583
2584 fn describe_migration_projects(
2585 &self,
2586 ctx: &Ctx,
2587 b: &Value,
2588 ) -> Result<AwsResponse, AwsServiceError> {
2589 let guard = self.state.read();
2590 let rows = guard
2591 .get(&ctx.account)
2592 .map(|d| d.migration_projects.values().cloned().collect())
2593 .unwrap_or_default();
2594 list_marker(
2595 "MigrationProjects",
2596 rows,
2597 b,
2598 &[(
2599 "migration-project-identifier",
2600 &["MigrationProjectArn", "MigrationProjectName"],
2601 )],
2602 )
2603 }
2604
2605 fn resolve_migration_project_key(data: &DmsData, ident: &str) -> Option<String> {
2606 if data.migration_projects.contains_key(ident) {
2607 return Some(ident.to_string());
2608 }
2609 data.migration_projects
2610 .iter()
2611 .find(|(_, v)| v.get("MigrationProjectName").and_then(Value::as_str) == Some(ident))
2612 .map(|(k, _)| k.clone())
2613 }
2614
2615 fn modify_migration_project(
2616 &self,
2617 ctx: &Ctx,
2618 b: &Value,
2619 ) -> Result<AwsResponse, AwsServiceError> {
2620 let ident = req_str(b, "MigrationProjectIdentifier")?.to_string();
2621 let mut guard = self.state.write();
2622 let data = guard.get_or_create(&ctx.account);
2623 let Some(key) = Self::resolve_migration_project_key(data, &ident) else {
2624 return Err(not_found(&format!("Migration project {ident} not found.")));
2625 };
2626 let mp = data.migration_projects.get_mut(&key).unwrap();
2627 let obj = mp.as_object_mut().unwrap();
2628 for f in [
2629 "MigrationProjectName",
2630 "TransformationRules",
2631 "Description",
2632 "SchemaConversionApplicationAttributes",
2633 ] {
2634 if let Some(v) = b.get(f) {
2635 obj.insert(f.to_string(), v.clone());
2636 }
2637 }
2638 if let Some(v) = b.get("SourceDataProviderDescriptors") {
2639 obj.insert(
2640 "SourceDataProviderDescriptors".into(),
2641 json!(build_provider_descriptors(Some(v))),
2642 );
2643 }
2644 if let Some(v) = b.get("TargetDataProviderDescriptors") {
2645 obj.insert(
2646 "TargetDataProviderDescriptors".into(),
2647 json!(build_provider_descriptors(Some(v))),
2648 );
2649 }
2650 if let Some(pi) = opt_str(b, "InstanceProfileIdentifier") {
2651 let pname = pi.rsplit(':').next().unwrap_or(pi).to_string();
2652 let parn = if pi.starts_with("arn:") {
2653 pi.to_string()
2654 } else {
2655 arn(ctx, "instance-profile", pi)
2656 };
2657 obj.insert("InstanceProfileArn".into(), json!(parn));
2658 obj.insert("InstanceProfileName".into(), json!(pname));
2659 }
2660 let mp = mp.clone();
2661 ok(json!({ "MigrationProject": mp }))
2662 }
2663
2664 fn delete_migration_project(
2665 &self,
2666 ctx: &Ctx,
2667 b: &Value,
2668 ) -> Result<AwsResponse, AwsServiceError> {
2669 let ident = req_str(b, "MigrationProjectIdentifier")?.to_string();
2670 let mut guard = self.state.write();
2671 let data = guard.get_or_create(&ctx.account);
2672 let Some(key) = Self::resolve_migration_project_key(data, &ident) else {
2673 return Err(not_found(&format!("Migration project {ident} not found.")));
2674 };
2675 let mp = data.migration_projects.remove(&key).unwrap();
2676 data.conversion_configs.remove(&key);
2677 data.tags.remove(&key);
2678 ok(json!({ "MigrationProject": mp }))
2679 }
2680}
2681
2682impl DmsService {
2685 fn require_project(data: &DmsData, b: &Value) -> Result<String, AwsServiceError> {
2686 let ident = req_str_allow_empty(b, "MigrationProjectIdentifier")?;
2691 Self::resolve_migration_project_key(data, ident)
2692 .ok_or_else(|| not_found(&format!("Migration project {ident} not found.")))
2693 }
2694
2695 fn describe_conversion_configuration(
2696 &self,
2697 ctx: &Ctx,
2698 b: &Value,
2699 ) -> Result<AwsResponse, AwsServiceError> {
2700 let guard = self.state.read();
2701 let data = guard
2702 .get(&ctx.account)
2703 .ok_or_else(|| not_found("Migration project not found."))?;
2704 let key = Self::require_project(data, b)?;
2705 let cfg = data
2706 .conversion_configs
2707 .get(&key)
2708 .cloned()
2709 .unwrap_or_else(|| "{}".to_string());
2710 ok(json!({ "MigrationProjectIdentifier": key, "ConversionConfiguration": cfg }))
2711 }
2712
2713 fn modify_conversion_configuration(
2714 &self,
2715 ctx: &Ctx,
2716 b: &Value,
2717 ) -> Result<AwsResponse, AwsServiceError> {
2718 let cfg = req_str(b, "ConversionConfiguration")?.to_string();
2719 let mut guard = self.state.write();
2720 let data = guard.get_or_create(&ctx.account);
2721 let key = Self::require_project(data, b)?;
2722 data.conversion_configs.insert(key.clone(), cfg);
2723 ok(json!({ "MigrationProjectIdentifier": key }))
2724 }
2725
2726 fn describe_metadata_model(
2727 &self,
2728 ctx: &Ctx,
2729 b: &Value,
2730 ) -> Result<AwsResponse, AwsServiceError> {
2731 req_str(b, "SelectionRules")?;
2732 if let Some(o) = opt_str(b, "Origin") {
2733 check_origin(o)?;
2734 }
2735 let guard = self.state.read();
2736 let data = guard
2737 .get(&ctx.account)
2738 .ok_or_else(|| not_found("Migration project not found."))?;
2739 Self::require_project(data, b)?;
2740 ok(json!({
2741 "MetadataModelName": "root",
2742 "MetadataModelType": "SchemaObject",
2743 "TargetMetadataModels": [],
2744 "Definition": "{}"
2745 }))
2746 }
2747
2748 fn describe_metadata_model_children(
2749 &self,
2750 ctx: &Ctx,
2751 b: &Value,
2752 ) -> Result<AwsResponse, AwsServiceError> {
2753 req_str(b, "SelectionRules")?;
2754 if let Some(o) = opt_str(b, "Origin") {
2755 check_origin(o)?;
2756 }
2757 let guard = self.state.read();
2758 let data = guard
2759 .get(&ctx.account)
2760 .ok_or_else(|| not_found("Migration project not found."))?;
2761 Self::require_project(data, b)?;
2762 list_marker("MetadataModelChildren", vec![], b, &[])
2763 }
2764
2765 fn describe_scr(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
2768 let ident = req_str_allow_empty(b, "MigrationProjectIdentifier")?;
2775 if ident.chars().count() > 255 {
2776 return Err(validation(
2777 "MigrationProjectIdentifier exceeds 255 characters.",
2778 ));
2779 }
2780 let guard = self.state.read();
2781 let key = guard.get(&ctx.account).and_then(|data| {
2782 opt_str(b, "MigrationProjectIdentifier")
2783 .and_then(|ident| Self::resolve_migration_project_key(data, ident))
2784 });
2785 let rows: Vec<Value> = match (guard.get(&ctx.account), &key) {
2786 (Some(data), Some(key)) => data
2787 .schema_conversion_requests
2788 .values()
2789 .filter(|r| {
2790 r.get("MigrationProjectArn").and_then(Value::as_str) == Some(key.as_str())
2791 })
2792 .cloned()
2793 .collect(),
2794 _ => Vec::new(),
2795 };
2796 list_marker(
2797 "Requests",
2798 rows,
2799 b,
2800 &[("request-id", &["RequestIdentifier"])],
2801 )
2802 }
2803
2804 fn start_scr(&self, ctx: &Ctx, b: &Value, _kind: &str) -> Result<AwsResponse, AwsServiceError> {
2807 if let Some(o) = opt_str(b, "Origin") {
2808 check_origin(o)?;
2809 }
2810 let mut guard = self.state.write();
2811 let data = guard.get_or_create(&ctx.account);
2812 let key = Self::require_project(data, b)?;
2813 let request_id = Uuid::new_v4().to_string();
2814 let req = json!({
2815 "Status": "SUCCESS",
2816 "RequestIdentifier": request_id,
2817 "MigrationProjectArn": key
2818 });
2819 data.schema_conversion_requests
2820 .insert(request_id.clone(), req);
2821 ok(json!({ "RequestIdentifier": request_id }))
2822 }
2823
2824 fn cancel_scr(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
2825 let request_id = req_str(b, "RequestIdentifier")?.to_string();
2826 let mut guard = self.state.write();
2827 let data = guard.get_or_create(&ctx.account);
2828 Self::require_project(data, b)?;
2829 let req = data
2830 .schema_conversion_requests
2831 .get(&request_id)
2832 .cloned()
2833 .unwrap_or_else(|| json!({ "Status": "CANCELLED", "RequestIdentifier": request_id }));
2834 if let Some(r) = data.schema_conversion_requests.get_mut(&request_id) {
2835 r["Status"] = json!("CANCELLED");
2836 }
2837 ok(json!({ "Request": req }))
2838 }
2839
2840 fn export_metadata_model_assessment(
2841 &self,
2842 ctx: &Ctx,
2843 b: &Value,
2844 ) -> Result<AwsResponse, AwsServiceError> {
2845 req_str(b, "SelectionRules")?;
2846 let guard = self.state.read();
2847 let data = guard
2848 .get(&ctx.account)
2849 .ok_or_else(|| not_found("Migration project not found."))?;
2850 Self::require_project(data, b)?;
2851 let entry = |suffix: &str| {
2852 json!({
2853 "S3ObjectKey": format!("assessment-report.{suffix}"),
2854 "ObjectURL": format!("https://s3.amazonaws.com/dms-assessments/assessment-report.{suffix}")
2855 })
2856 };
2857 ok(json!({ "PdfReport": entry("pdf"), "CsvReport": entry("csv") }))
2858 }
2859
2860 fn get_target_selection_rules(
2861 &self,
2862 ctx: &Ctx,
2863 b: &Value,
2864 ) -> Result<AwsResponse, AwsServiceError> {
2865 let rules = req_str(b, "SelectionRules")?.to_string();
2866 let guard = self.state.read();
2867 let data = guard
2868 .get(&ctx.account)
2869 .ok_or_else(|| not_found("Migration project not found."))?;
2870 Self::require_project(data, b)?;
2871 ok(json!({ "TargetSelectionRules": rules }))
2872 }
2873}
2874
2875fn check_origin(o: &str) -> Result<(), AwsServiceError> {
2876 if !o.is_empty() && o != "SOURCE" && o != "TARGET" {
2877 return Err(validation("Origin must be one of [SOURCE, TARGET]."));
2878 }
2879 Ok(())
2880}
2881
2882impl DmsService {
2885 fn create_data_migration(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
2886 let project_ident = req_str_allow_empty(b, "MigrationProjectIdentifier")?.to_string();
2887 let mig_type = req_str_allow_empty(b, "DataMigrationType")?.to_string();
2888 check_data_migration_type(&mig_type)?;
2889 req_str_allow_empty(b, "ServiceAccessRoleArn")?;
2890 let name = opt_str(b, "DataMigrationName")
2891 .filter(|s| !s.is_empty())
2892 .map(String::from)
2893 .unwrap_or_else(|| format!("data-migration-{}", &res_id()[..8].to_lowercase()));
2894 let mut guard = self.state.write();
2895 let data = guard.get_or_create(&ctx.account);
2896 if data
2897 .data_migrations
2898 .values()
2899 .any(|v| v.get("DataMigrationName").and_then(Value::as_str) == Some(&name))
2900 {
2901 return Err(already_exists(&format!(
2902 "Data migration {name} already exists."
2903 )));
2904 }
2905 let project_arn = if project_ident.starts_with("arn:") {
2906 project_ident.clone()
2907 } else {
2908 arn(ctx, "migration-project", &project_ident)
2909 };
2910 let dm_arn = arn(ctx, "data-migration", &name);
2911 let mut dm = Map::new();
2912 dm.insert("DataMigrationArn".into(), json!(dm_arn));
2913 dm.insert("DataMigrationName".into(), json!(name));
2914 dm.insert("MigrationProjectArn".into(), json!(project_arn));
2915 dm.insert("DataMigrationType".into(), json!(mig_type));
2916 dm.insert("DataMigrationCreateTime".into(), json!(now_ts()));
2917 dm.insert("DataMigrationStatus".into(), json!("READY"));
2918 copy_fields(
2919 b,
2920 &mut dm,
2921 &[
2922 "ServiceAccessRoleArn",
2923 "SourceDataSettings",
2924 "TargetDataSettings",
2925 ],
2926 );
2927 let dm = Value::Object(dm);
2928 data.data_migrations.insert(dm_arn.clone(), dm.clone());
2929 store_tags(data, &dm_arn, b);
2930 ok(json!({ "DataMigration": dm }))
2931 }
2932
2933 fn describe_data_migrations(
2934 &self,
2935 ctx: &Ctx,
2936 b: &Value,
2937 ) -> Result<AwsResponse, AwsServiceError> {
2938 if let Some(m) = opt_str(b, "Marker") {
2939 if m.chars().count() > 1024 {
2940 return Err(validation("Marker exceeds 1024 characters."));
2941 }
2942 }
2943 let guard = self.state.read();
2944 let rows = guard
2945 .get(&ctx.account)
2946 .map(|d| d.data_migrations.values().cloned().collect())
2947 .unwrap_or_default();
2948 list_marker(
2949 "DataMigrations",
2950 rows,
2951 b,
2952 &[("data-migration-name", &["DataMigrationName"])],
2953 )
2954 }
2955
2956 fn resolve_data_migration_key(data: &DmsData, ident: &str) -> Option<String> {
2957 if data.data_migrations.contains_key(ident) {
2958 return Some(ident.to_string());
2959 }
2960 data.data_migrations
2961 .iter()
2962 .find(|(_, v)| v.get("DataMigrationName").and_then(Value::as_str) == Some(ident))
2963 .map(|(k, _)| k.clone())
2964 }
2965
2966 fn modify_data_migration(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
2967 let ident = req_str(b, "DataMigrationIdentifier")?.to_string();
2968 if let Some(mt) = opt_str(b, "DataMigrationType") {
2969 check_data_migration_type(mt)?;
2970 }
2971 let mut guard = self.state.write();
2972 let data = guard.get_or_create(&ctx.account);
2973 let Some(key) = Self::resolve_data_migration_key(data, &ident) else {
2974 return Err(not_found(&format!("Data migration {ident} not found.")));
2975 };
2976 let dm = data.data_migrations.get_mut(&key).unwrap();
2977 let obj = dm.as_object_mut().unwrap();
2978 for f in [
2979 "DataMigrationName",
2980 "ServiceAccessRoleArn",
2981 "DataMigrationType",
2982 "SourceDataSettings",
2983 "TargetDataSettings",
2984 ] {
2985 if let Some(v) = b.get(f) {
2986 obj.insert(f.to_string(), v.clone());
2987 }
2988 }
2989 let dm = dm.clone();
2990 ok(json!({ "DataMigration": dm }))
2991 }
2992
2993 fn delete_data_migration(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
2994 let ident = req_str(b, "DataMigrationIdentifier")?.to_string();
2995 let mut guard = self.state.write();
2996 let data = guard.get_or_create(&ctx.account);
2997 let Some(key) = Self::resolve_data_migration_key(data, &ident) else {
2998 return Err(not_found(&format!("Data migration {ident} not found.")));
2999 };
3000 let dm = data.data_migrations.remove(&key).unwrap();
3001 data.tags.remove(&key);
3002 ok(json!({ "DataMigration": dm }))
3003 }
3004
3005 fn set_data_migration_status(
3006 &self,
3007 ctx: &Ctx,
3008 b: &Value,
3009 status: &str,
3010 ) -> Result<AwsResponse, AwsServiceError> {
3011 let ident = req_str(b, "DataMigrationIdentifier")?.to_string();
3012 let mut guard = self.state.write();
3013 let data = guard.get_or_create(&ctx.account);
3014 let Some(key) = Self::resolve_data_migration_key(data, &ident) else {
3015 return Err(not_found(&format!("Data migration {ident} not found.")));
3016 };
3017 let dm = data.data_migrations.get_mut(&key).unwrap();
3018 dm["DataMigrationStatus"] = json!(status);
3019 if status == "RUNNING" {
3020 dm["DataMigrationStartTime"] = json!(now_ts());
3021 }
3022 let dm = dm.clone();
3023 ok(json!({ "DataMigration": dm }))
3024 }
3025
3026 fn start_data_migration(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
3027 if let Some(t) = opt_str(b, "StartType") {
3028 const ALLOWED: &[&str] = &["reload-target", "resume-processing", "start-replication"];
3029 if !t.is_empty() && !ALLOWED.contains(&t) {
3030 return Err(validation(
3031 "StartType must be one of [reload-target, resume-processing, start-replication].",
3032 ));
3033 }
3034 }
3035 self.set_data_migration_status(ctx, b, "RUNNING")
3036 }
3037
3038 fn stop_data_migration(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
3039 self.set_data_migration_status(ctx, b, "STOPPED")
3040 }
3041}
3042
3043fn check_data_migration_type(t: &str) -> Result<(), AwsServiceError> {
3044 const ALLOWED: &[&str] = &["full-load", "cdc", "full-load-and-cdc"];
3045 if !t.is_empty() && !ALLOWED.contains(&t) {
3046 return Err(validation(
3047 "DataMigrationType must be one of [full-load, cdc, full-load-and-cdc].",
3048 ));
3049 }
3050 Ok(())
3051}
3052
3053impl DmsService {
3056 fn create_fleet_advisor_collector(
3057 &self,
3058 ctx: &Ctx,
3059 b: &Value,
3060 ) -> Result<AwsResponse, AwsServiceError> {
3061 let name = req_str_allow_empty(b, "CollectorName")?.to_string();
3062 let role = req_str_allow_empty(b, "ServiceAccessRoleArn")?.to_string();
3063 let bucket = req_str_allow_empty(b, "S3BucketName")?.to_string();
3064 let collector_id = Uuid::new_v4().to_string();
3065 let mut guard = self.state.write();
3066 let data = guard.get_or_create(&ctx.account);
3067 let mut collector = Map::new();
3068 collector.insert("CollectorReferencedId".into(), json!(collector_id));
3069 collector.insert("CollectorName".into(), json!(name));
3070 collector.insert("ServiceAccessRoleArn".into(), json!(role));
3071 collector.insert("S3BucketName".into(), json!(bucket));
3072 if let Some(d) = opt_str(b, "Description") {
3073 collector.insert("Description".into(), json!(d));
3074 }
3075 data.fleet_advisor_collectors
3076 .insert(collector_id.clone(), Value::Object(collector.clone()));
3077 ok(Value::Object(collector))
3079 }
3080
3081 fn delete_fleet_advisor_collector(
3082 &self,
3083 ctx: &Ctx,
3084 b: &Value,
3085 ) -> Result<AwsResponse, AwsServiceError> {
3086 let id = req_str(b, "CollectorReferencedId")?.to_string();
3087 let mut guard = self.state.write();
3088 let data = guard.get_or_create(&ctx.account);
3089 if data.fleet_advisor_collectors.remove(&id).is_none() {
3090 return Err(AwsServiceError::aws_error(
3091 StatusCode::NOT_FOUND,
3092 "CollectorNotFoundFault",
3093 format!("Collector {id} not found."),
3094 ));
3095 }
3096 ok(json!({}))
3097 }
3098
3099 fn describe_fleet_advisor_collectors(
3100 &self,
3101 ctx: &Ctx,
3102 b: &Value,
3103 ) -> Result<AwsResponse, AwsServiceError> {
3104 let guard = self.state.read();
3105 let rows = guard
3106 .get(&ctx.account)
3107 .map(|d| d.fleet_advisor_collectors.values().cloned().collect())
3108 .unwrap_or_default();
3109 list_next_token(
3110 "Collectors",
3111 rows,
3112 b,
3113 &[
3114 ("collector-referenced-id", &["CollectorReferencedId"]),
3115 ("collector-name", &["CollectorName"]),
3116 ],
3117 )
3118 }
3119
3120 fn describe_fleet_advisor_databases(&self, b: &Value) -> Result<AwsResponse, AwsServiceError> {
3121 list_next_token("Databases", vec![], b, &[])
3122 }
3123
3124 fn delete_fleet_advisor_databases(&self, b: &Value) -> Result<AwsResponse, AwsServiceError> {
3125 let ids = b
3126 .get("DatabaseIds")
3127 .cloned()
3128 .ok_or_else(|| validation("DatabaseIds is required."))?;
3129 ok(json!({ "DatabaseIds": ids }))
3130 }
3131
3132 fn describe_fleet_advisor_lsa_analysis(
3133 &self,
3134 b: &Value,
3135 ) -> Result<AwsResponse, AwsServiceError> {
3136 list_next_token("Analysis", vec![], b, &[])
3137 }
3138
3139 fn describe_fleet_advisor_schema_object_summary(
3140 &self,
3141 b: &Value,
3142 ) -> Result<AwsResponse, AwsServiceError> {
3143 list_next_token("FleetAdvisorSchemaObjects", vec![], b, &[])
3144 }
3145
3146 fn describe_fleet_advisor_schemas(&self, b: &Value) -> Result<AwsResponse, AwsServiceError> {
3147 list_next_token("FleetAdvisorSchemas", vec![], b, &[])
3148 }
3149
3150 fn run_fleet_advisor_lsa_analysis(&self) -> Result<AwsResponse, AwsServiceError> {
3151 ok(json!({ "LsaAnalysisId": Uuid::new_v4().to_string(), "Status": "RUNNING" }))
3152 }
3153}
3154
3155impl DmsService {
3158 fn start_recommendations(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
3159 let db_id = req_str(b, "DatabaseId")?.to_string();
3160 if b.get("Settings").is_none() {
3161 return Err(validation("Settings is required."));
3162 }
3163 let mut guard = self.state.write();
3164 let data = guard.get_or_create(&ctx.account);
3165 data.recommendations.insert(
3166 db_id.clone(),
3167 json!({
3168 "DatabaseId": db_id,
3169 "Status": "in-progress",
3170 "Settings": b.get("Settings").cloned().unwrap_or(json!({}))
3171 }),
3172 );
3173 ok(json!({}))
3174 }
3175
3176 fn batch_start_recommendations(
3177 &self,
3178 ctx: &Ctx,
3179 b: &Value,
3180 ) -> Result<AwsResponse, AwsServiceError> {
3181 let mut guard = self.state.write();
3182 let data = guard.get_or_create(&ctx.account);
3183 if let Some(entries) = b.get("Data").and_then(Value::as_array) {
3184 for e in entries {
3185 if let Some(db_id) = e.get("DatabaseId").and_then(Value::as_str) {
3186 data.recommendations.insert(
3187 db_id.to_string(),
3188 json!({ "DatabaseId": db_id, "Status": "in-progress" }),
3189 );
3190 }
3191 }
3192 }
3193 ok(json!({ "ErrorEntries": [] }))
3194 }
3195
3196 fn describe_recommendations(
3197 &self,
3198 ctx: &Ctx,
3199 b: &Value,
3200 ) -> Result<AwsResponse, AwsServiceError> {
3201 let guard = self.state.read();
3202 let rows = guard
3203 .get(&ctx.account)
3204 .map(|d| d.recommendations.values().cloned().collect())
3205 .unwrap_or_default();
3206 list_next_token(
3207 "Recommendations",
3208 rows,
3209 b,
3210 &[("database-id", &["DatabaseId"]), ("status", &["Status"])],
3211 )
3212 }
3213
3214 fn describe_recommendation_limitations(
3215 &self,
3216 b: &Value,
3217 ) -> Result<AwsResponse, AwsServiceError> {
3218 list_next_token("Limitations", vec![], b, &[])
3219 }
3220}
3221
3222impl DmsService {
3225 fn describe_account_attributes(&self, ctx: &Ctx) -> Result<AwsResponse, AwsServiceError> {
3226 let guard = self.state.read();
3227 let data = guard.get(&ctx.account);
3228 let count = |f: fn(&DmsData) -> usize| data.map(f).unwrap_or(0) as i64;
3229 let quotas = json!([
3230 { "AccountQuotaName": "ReplicationInstances", "Used": count(|d| d.replication_instances.len()), "Max": 20 },
3231 { "AccountQuotaName": "ReplicationSubnetGroups", "Used": count(|d| d.subnet_groups.len()), "Max": 20 },
3232 { "AccountQuotaName": "Endpoints", "Used": count(|d| d.endpoints.len()), "Max": 1000 },
3233 { "AccountQuotaName": "ReplicationTasks", "Used": count(|d| d.replication_tasks.len()), "Max": 200 },
3234 { "AccountQuotaName": "AllocatedStorage", "Used": 0, "Max": 10000 },
3235 { "AccountQuotaName": "EventSubscriptions", "Used": count(|d| d.event_subscriptions.len()), "Max": 20 }
3236 ]);
3237 ok(json!({
3238 "AccountQuotas": quotas,
3239 "UniqueAccountIdentifier": format!("dms-{}", ctx.account)
3240 }))
3241 }
3242
3243 fn describe_engine_versions(&self, b: &Value) -> Result<AwsResponse, AwsServiceError> {
3244 let rows: Vec<Value> = ["3.4.7", "3.5.1", "3.5.2", "3.5.3", "3.5.4"]
3245 .iter()
3246 .map(|v| {
3247 json!({
3248 "Version": v,
3249 "Lifecycle": "available",
3250 "ReleaseStatus": "prod",
3251 "AvailableUpgrades": []
3252 })
3253 })
3254 .collect();
3255 list_marker("EngineVersions", rows, b, &[])
3256 }
3257}
3258
3259impl DmsService {
3262 fn add_tags_to_resource(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
3263 let resource_arn = req_str(b, "ResourceArn")?.to_string();
3264 if b.get("Tags").and_then(Value::as_array).is_none() {
3265 return Err(validation("Tags is required."));
3266 }
3267 let mut guard = self.state.write();
3268 let data = guard.get_or_create(&ctx.account);
3269 store_tags(data, &resource_arn, b);
3270 ok(json!({}))
3271 }
3272
3273 fn remove_tags_from_resource(
3274 &self,
3275 ctx: &Ctx,
3276 b: &Value,
3277 ) -> Result<AwsResponse, AwsServiceError> {
3278 let resource_arn = req_str(b, "ResourceArn")?.to_string();
3279 let keys: Vec<String> = b
3280 .get("TagKeys")
3281 .and_then(Value::as_array)
3282 .map(|a| {
3283 a.iter()
3284 .filter_map(|v| v.as_str().map(String::from))
3285 .collect()
3286 })
3287 .ok_or_else(|| validation("TagKeys is required."))?;
3288 let mut guard = self.state.write();
3289 let data = guard.get_or_create(&ctx.account);
3290 if let Some(entry) = data.tags.get_mut(&resource_arn) {
3291 for k in &keys {
3292 entry.remove(k);
3293 }
3294 }
3295 ok(json!({}))
3296 }
3297
3298 fn list_tags_for_resource(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
3299 let mut arns: Vec<String> = Vec::new();
3300 if let Some(a) = opt_str(b, "ResourceArn") {
3301 arns.push(a.to_string());
3302 }
3303 if let Some(list) = b.get("ResourceArnList").and_then(Value::as_array) {
3304 arns.extend(list.iter().filter_map(|v| v.as_str().map(String::from)));
3305 }
3306 let guard = self.state.read();
3307 let data = guard.get(&ctx.account);
3308 let mut tag_list = Vec::new();
3309 for a in &arns {
3310 if let Some(entry) = data.and_then(|d| d.tags.get(a)) {
3311 for (k, v) in entry {
3312 tag_list.push(json!({ "Key": k, "Value": v, "ResourceArn": a }));
3313 }
3314 }
3315 }
3316 ok(json!({ "TagList": tag_list }))
3317 }
3318}