1use crate::error::Error;
2use crate::types::*;
3
4pub struct FakeCloud {
6 base_url: String,
7 client: reqwest::Client,
8}
9
10impl FakeCloud {
11 pub fn new(base_url: &str) -> Self {
13 Self {
14 base_url: base_url.trim_end_matches('/').to_string(),
15 client: reqwest::Client::new(),
16 }
17 }
18
19 pub async fn health(&self) -> Result<HealthResponse, Error> {
23 let resp = self
24 .client
25 .get(format!("{}/_fakecloud/health", self.base_url))
26 .send()
27 .await?;
28 Self::parse(resp).await
29 }
30
31 pub async fn reset(&self) -> Result<ResetResponse, Error> {
33 let resp = self
34 .client
35 .post(format!("{}/_reset", self.base_url))
36 .send()
37 .await?;
38 Self::parse(resp).await
39 }
40
41 pub async fn create_admin(
46 &self,
47 account_id: &str,
48 user_name: &str,
49 ) -> Result<CreateAdminResponse, Error> {
50 let resp = self
51 .client
52 .post(format!("{}/_fakecloud/iam/create-admin", self.base_url))
53 .json(&CreateAdminRequest {
54 account_id: account_id.to_string(),
55 user_name: user_name.to_string(),
56 })
57 .send()
58 .await?;
59 Self::parse(resp).await
60 }
61
62 pub async fn reset_service(&self, service: &str) -> Result<ResetServiceResponse, Error> {
64 let resp = self
65 .client
66 .post(format!("{}/_fakecloud/reset/{}", self.base_url, service))
67 .send()
68 .await?;
69 Self::parse(resp).await
70 }
71
72 pub async fn reset_service_for_account(
74 &self,
75 service: &str,
76 account_id: &str,
77 ) -> Result<ResetServiceResponse, Error> {
78 let resp = self
79 .client
80 .post(format!(
81 "{}/_fakecloud/reset/{}/{}",
82 self.base_url, service, account_id
83 ))
84 .send()
85 .await?;
86 Self::parse(resp).await
87 }
88
89 pub fn lambda(&self) -> LambdaClient<'_> {
92 LambdaClient { fc: self }
93 }
94
95 pub fn ses(&self) -> SesClient<'_> {
96 SesClient { fc: self }
97 }
98
99 pub fn sns(&self) -> SnsClient<'_> {
100 SnsClient { fc: self }
101 }
102
103 pub fn sqs(&self) -> SqsClient<'_> {
104 SqsClient { fc: self }
105 }
106
107 pub fn events(&self) -> EventsClient<'_> {
108 EventsClient { fc: self }
109 }
110
111 pub fn s3(&self) -> S3Client<'_> {
112 S3Client { fc: self }
113 }
114
115 pub fn dynamodb(&self) -> DynamoDbClient<'_> {
116 DynamoDbClient { fc: self }
117 }
118
119 pub fn secretsmanager(&self) -> SecretsManagerClient<'_> {
120 SecretsManagerClient { fc: self }
121 }
122
123 pub fn cognito(&self) -> CognitoClient<'_> {
124 CognitoClient { fc: self }
125 }
126
127 pub fn rds(&self) -> RdsClient<'_> {
128 RdsClient { fc: self }
129 }
130
131 pub fn elasticache(&self) -> ElastiCacheClient<'_> {
132 ElastiCacheClient { fc: self }
133 }
134
135 pub fn apigatewayv2(&self) -> ApiGatewayV2Client<'_> {
136 ApiGatewayV2Client { fc: self }
137 }
138
139 pub fn stepfunctions(&self) -> StepFunctionsClient<'_> {
140 StepFunctionsClient { fc: self }
141 }
142
143 pub fn bedrock(&self) -> BedrockClient<'_> {
144 BedrockClient { fc: self }
145 }
146
147 pub fn ecs(&self) -> EcsClient<'_> {
148 EcsClient { fc: self }
149 }
150
151 async fn parse<T: serde::de::DeserializeOwned>(resp: reqwest::Response) -> Result<T, Error> {
154 let status = resp.status().as_u16();
155 if !resp.status().is_success() {
156 let body = resp.text().await.unwrap_or_default();
157 return Err(Error::Api { status, body });
158 }
159 Ok(resp.json::<T>().await?)
160 }
161}
162
163pub struct RdsClient<'a> {
166 fc: &'a FakeCloud,
167}
168
169impl RdsClient<'_> {
170 pub async fn get_instances(&self) -> Result<RdsInstancesResponse, Error> {
172 let resp = self
173 .fc
174 .client
175 .get(format!("{}/_fakecloud/rds/instances", self.fc.base_url))
176 .send()
177 .await?;
178 FakeCloud::parse(resp).await
179 }
180}
181
182pub struct ElastiCacheClient<'a> {
185 fc: &'a FakeCloud,
186}
187
188impl ElastiCacheClient<'_> {
189 pub async fn get_clusters(&self) -> Result<ElastiCacheClustersResponse, Error> {
191 let resp = self
192 .fc
193 .client
194 .get(format!(
195 "{}/_fakecloud/elasticache/clusters",
196 self.fc.base_url
197 ))
198 .send()
199 .await?;
200 FakeCloud::parse(resp).await
201 }
202
203 pub async fn get_replication_groups(
205 &self,
206 ) -> Result<ElastiCacheReplicationGroupsResponse, Error> {
207 let resp = self
208 .fc
209 .client
210 .get(format!(
211 "{}/_fakecloud/elasticache/replication-groups",
212 self.fc.base_url
213 ))
214 .send()
215 .await?;
216 FakeCloud::parse(resp).await
217 }
218
219 pub async fn get_serverless_caches(
221 &self,
222 ) -> Result<ElastiCacheServerlessCachesResponse, Error> {
223 let resp = self
224 .fc
225 .client
226 .get(format!(
227 "{}/_fakecloud/elasticache/serverless-caches",
228 self.fc.base_url
229 ))
230 .send()
231 .await?;
232 FakeCloud::parse(resp).await
233 }
234}
235
236pub struct LambdaClient<'a> {
239 fc: &'a FakeCloud,
240}
241
242impl LambdaClient<'_> {
243 pub async fn get_invocations(&self) -> Result<LambdaInvocationsResponse, Error> {
245 let resp = self
246 .fc
247 .client
248 .get(format!(
249 "{}/_fakecloud/lambda/invocations",
250 self.fc.base_url
251 ))
252 .send()
253 .await?;
254 FakeCloud::parse(resp).await
255 }
256
257 pub async fn get_warm_containers(&self) -> Result<WarmContainersResponse, Error> {
259 let resp = self
260 .fc
261 .client
262 .get(format!(
263 "{}/_fakecloud/lambda/warm-containers",
264 self.fc.base_url
265 ))
266 .send()
267 .await?;
268 FakeCloud::parse(resp).await
269 }
270
271 pub async fn evict_container(
273 &self,
274 function_name: &str,
275 ) -> Result<EvictContainerResponse, Error> {
276 let resp = self
277 .fc
278 .client
279 .post(format!(
280 "{}/_fakecloud/lambda/{}/evict-container",
281 self.fc.base_url, function_name
282 ))
283 .send()
284 .await?;
285 FakeCloud::parse(resp).await
286 }
287}
288
289pub struct SesClient<'a> {
292 fc: &'a FakeCloud,
293}
294
295impl SesClient<'_> {
296 pub async fn get_emails(&self) -> Result<SesEmailsResponse, Error> {
298 let resp = self
299 .fc
300 .client
301 .get(format!("{}/_fakecloud/ses/emails", self.fc.base_url))
302 .send()
303 .await?;
304 FakeCloud::parse(resp).await
305 }
306
307 pub async fn simulate_inbound(
309 &self,
310 req: &InboundEmailRequest,
311 ) -> Result<InboundEmailResponse, Error> {
312 let resp = self
313 .fc
314 .client
315 .post(format!("{}/_fakecloud/ses/inbound", self.fc.base_url))
316 .json(req)
317 .send()
318 .await?;
319 FakeCloud::parse(resp).await
320 }
321}
322
323pub struct SnsClient<'a> {
326 fc: &'a FakeCloud,
327}
328
329impl SnsClient<'_> {
330 pub async fn get_messages(&self) -> Result<SnsMessagesResponse, Error> {
332 let resp = self
333 .fc
334 .client
335 .get(format!("{}/_fakecloud/sns/messages", self.fc.base_url))
336 .send()
337 .await?;
338 FakeCloud::parse(resp).await
339 }
340
341 pub async fn get_pending_confirmations(&self) -> Result<PendingConfirmationsResponse, Error> {
343 let resp = self
344 .fc
345 .client
346 .get(format!(
347 "{}/_fakecloud/sns/pending-confirmations",
348 self.fc.base_url
349 ))
350 .send()
351 .await?;
352 FakeCloud::parse(resp).await
353 }
354
355 pub async fn confirm_subscription(
357 &self,
358 req: &ConfirmSubscriptionRequest,
359 ) -> Result<ConfirmSubscriptionResponse, Error> {
360 let resp = self
361 .fc
362 .client
363 .post(format!(
364 "{}/_fakecloud/sns/confirm-subscription",
365 self.fc.base_url
366 ))
367 .json(req)
368 .send()
369 .await?;
370 FakeCloud::parse(resp).await
371 }
372}
373
374pub struct SqsClient<'a> {
377 fc: &'a FakeCloud,
378}
379
380impl SqsClient<'_> {
381 pub async fn get_messages(&self) -> Result<SqsMessagesResponse, Error> {
383 let resp = self
384 .fc
385 .client
386 .get(format!("{}/_fakecloud/sqs/messages", self.fc.base_url))
387 .send()
388 .await?;
389 FakeCloud::parse(resp).await
390 }
391
392 pub async fn tick_expiration(&self) -> Result<ExpirationTickResponse, Error> {
394 let resp = self
395 .fc
396 .client
397 .post(format!(
398 "{}/_fakecloud/sqs/expiration-processor/tick",
399 self.fc.base_url
400 ))
401 .send()
402 .await?;
403 FakeCloud::parse(resp).await
404 }
405
406 pub async fn force_dlq(&self, queue_name: &str) -> Result<ForceDlqResponse, Error> {
408 let resp = self
409 .fc
410 .client
411 .post(format!(
412 "{}/_fakecloud/sqs/{}/force-dlq",
413 self.fc.base_url, queue_name
414 ))
415 .send()
416 .await?;
417 FakeCloud::parse(resp).await
418 }
419}
420
421pub struct EventsClient<'a> {
424 fc: &'a FakeCloud,
425}
426
427impl EventsClient<'_> {
428 pub async fn get_history(&self) -> Result<EventHistoryResponse, Error> {
430 let resp = self
431 .fc
432 .client
433 .get(format!("{}/_fakecloud/events/history", self.fc.base_url))
434 .send()
435 .await?;
436 FakeCloud::parse(resp).await
437 }
438
439 pub async fn fire_rule(&self, req: &FireRuleRequest) -> Result<FireRuleResponse, Error> {
441 let resp = self
442 .fc
443 .client
444 .post(format!("{}/_fakecloud/events/fire-rule", self.fc.base_url))
445 .json(req)
446 .send()
447 .await?;
448 FakeCloud::parse(resp).await
449 }
450}
451
452pub struct S3Client<'a> {
455 fc: &'a FakeCloud,
456}
457
458impl S3Client<'_> {
459 pub async fn get_notifications(&self) -> Result<S3NotificationsResponse, Error> {
461 let resp = self
462 .fc
463 .client
464 .get(format!("{}/_fakecloud/s3/notifications", self.fc.base_url))
465 .send()
466 .await?;
467 FakeCloud::parse(resp).await
468 }
469
470 pub async fn tick_lifecycle(&self) -> Result<LifecycleTickResponse, Error> {
472 let resp = self
473 .fc
474 .client
475 .post(format!(
476 "{}/_fakecloud/s3/lifecycle-processor/tick",
477 self.fc.base_url
478 ))
479 .send()
480 .await?;
481 FakeCloud::parse(resp).await
482 }
483}
484
485pub struct DynamoDbClient<'a> {
488 fc: &'a FakeCloud,
489}
490
491impl DynamoDbClient<'_> {
492 pub async fn tick_ttl(&self) -> Result<TtlTickResponse, Error> {
494 let resp = self
495 .fc
496 .client
497 .post(format!(
498 "{}/_fakecloud/dynamodb/ttl-processor/tick",
499 self.fc.base_url
500 ))
501 .send()
502 .await?;
503 FakeCloud::parse(resp).await
504 }
505}
506
507pub struct SecretsManagerClient<'a> {
510 fc: &'a FakeCloud,
511}
512
513impl SecretsManagerClient<'_> {
514 pub async fn tick_rotation(&self) -> Result<RotationTickResponse, Error> {
516 let resp = self
517 .fc
518 .client
519 .post(format!(
520 "{}/_fakecloud/secretsmanager/rotation-scheduler/tick",
521 self.fc.base_url
522 ))
523 .send()
524 .await?;
525 FakeCloud::parse(resp).await
526 }
527}
528
529pub struct CognitoClient<'a> {
532 fc: &'a FakeCloud,
533}
534
535impl CognitoClient<'_> {
536 pub async fn get_user_codes(
538 &self,
539 pool_id: &str,
540 username: &str,
541 ) -> Result<UserConfirmationCodes, Error> {
542 let resp = self
543 .fc
544 .client
545 .get(format!(
546 "{}/_fakecloud/cognito/confirmation-codes/{}/{}",
547 self.fc.base_url, pool_id, username
548 ))
549 .send()
550 .await?;
551 FakeCloud::parse(resp).await
552 }
553
554 pub async fn get_confirmation_codes(&self) -> Result<ConfirmationCodesResponse, Error> {
556 let resp = self
557 .fc
558 .client
559 .get(format!(
560 "{}/_fakecloud/cognito/confirmation-codes",
561 self.fc.base_url
562 ))
563 .send()
564 .await?;
565 FakeCloud::parse(resp).await
566 }
567
568 pub async fn confirm_user(
570 &self,
571 req: &ConfirmUserRequest,
572 ) -> Result<ConfirmUserResponse, Error> {
573 let resp = self
574 .fc
575 .client
576 .post(format!(
577 "{}/_fakecloud/cognito/confirm-user",
578 self.fc.base_url
579 ))
580 .json(req)
581 .send()
582 .await?;
583 let status = resp.status().as_u16();
584 let body: ConfirmUserResponse = resp.json().await?;
585 if status >= 400 {
586 return Err(Error::Api {
587 status,
588 body: body.error.unwrap_or_default(),
589 });
590 }
591 Ok(body)
592 }
593
594 pub async fn get_tokens(&self) -> Result<TokensResponse, Error> {
596 let resp = self
597 .fc
598 .client
599 .get(format!("{}/_fakecloud/cognito/tokens", self.fc.base_url))
600 .send()
601 .await?;
602 FakeCloud::parse(resp).await
603 }
604
605 pub async fn expire_tokens(
607 &self,
608 req: &ExpireTokensRequest,
609 ) -> Result<ExpireTokensResponse, Error> {
610 let resp = self
611 .fc
612 .client
613 .post(format!(
614 "{}/_fakecloud/cognito/expire-tokens",
615 self.fc.base_url
616 ))
617 .json(req)
618 .send()
619 .await?;
620 FakeCloud::parse(resp).await
621 }
622
623 pub async fn get_auth_events(&self) -> Result<AuthEventsResponse, Error> {
625 let resp = self
626 .fc
627 .client
628 .get(format!(
629 "{}/_fakecloud/cognito/auth-events",
630 self.fc.base_url
631 ))
632 .send()
633 .await?;
634 FakeCloud::parse(resp).await
635 }
636}
637
638pub struct ApiGatewayV2Client<'a> {
641 fc: &'a FakeCloud,
642}
643
644impl ApiGatewayV2Client<'_> {
645 pub async fn get_requests(&self) -> Result<ApiGatewayV2RequestsResponse, Error> {
647 let resp = self
648 .fc
649 .client
650 .get(format!(
651 "{}/_fakecloud/apigatewayv2/requests",
652 self.fc.base_url
653 ))
654 .send()
655 .await?;
656 FakeCloud::parse(resp).await
657 }
658}
659
660pub struct StepFunctionsClient<'a> {
663 fc: &'a FakeCloud,
664}
665
666impl StepFunctionsClient<'_> {
667 pub async fn get_executions(&self) -> Result<StepFunctionsExecutionsResponse, Error> {
669 let resp = self
670 .fc
671 .client
672 .get(format!(
673 "{}/_fakecloud/stepfunctions/executions",
674 self.fc.base_url
675 ))
676 .send()
677 .await?;
678 FakeCloud::parse(resp).await
679 }
680}
681
682pub struct BedrockClient<'a> {
685 fc: &'a FakeCloud,
686}
687
688impl BedrockClient<'_> {
689 pub async fn get_invocations(&self) -> Result<BedrockInvocationsResponse, Error> {
692 let resp = self
693 .fc
694 .client
695 .get(format!(
696 "{}/_fakecloud/bedrock/invocations",
697 self.fc.base_url
698 ))
699 .send()
700 .await?;
701 FakeCloud::parse(resp).await
702 }
703
704 pub async fn set_model_response(
706 &self,
707 model_id: &str,
708 response: &str,
709 ) -> Result<BedrockModelResponseConfig, Error> {
710 let resp = self
711 .fc
712 .client
713 .post(format!(
714 "{}/_fakecloud/bedrock/models/{}/response",
715 self.fc.base_url, model_id
716 ))
717 .header("content-type", "text/plain")
718 .body(response.to_string())
719 .send()
720 .await?;
721 FakeCloud::parse(resp).await
722 }
723
724 pub async fn set_response_rules(
726 &self,
727 model_id: &str,
728 rules: &[BedrockResponseRule],
729 ) -> Result<BedrockModelResponseConfig, Error> {
730 let body = serde_json::json!({ "rules": rules });
731 let resp = self
732 .fc
733 .client
734 .post(format!(
735 "{}/_fakecloud/bedrock/models/{}/responses",
736 self.fc.base_url, model_id
737 ))
738 .json(&body)
739 .send()
740 .await?;
741 FakeCloud::parse(resp).await
742 }
743
744 pub async fn clear_response_rules(
746 &self,
747 model_id: &str,
748 ) -> Result<BedrockModelResponseConfig, Error> {
749 let resp = self
750 .fc
751 .client
752 .delete(format!(
753 "{}/_fakecloud/bedrock/models/{}/responses",
754 self.fc.base_url, model_id
755 ))
756 .send()
757 .await?;
758 FakeCloud::parse(resp).await
759 }
760
761 pub async fn queue_fault(
763 &self,
764 rule: &BedrockFaultRule,
765 ) -> Result<BedrockStatusResponse, Error> {
766 let resp = self
767 .fc
768 .client
769 .post(format!("{}/_fakecloud/bedrock/faults", self.fc.base_url))
770 .json(rule)
771 .send()
772 .await?;
773 FakeCloud::parse(resp).await
774 }
775
776 pub async fn get_faults(&self) -> Result<BedrockFaultsResponse, Error> {
778 let resp = self
779 .fc
780 .client
781 .get(format!("{}/_fakecloud/bedrock/faults", self.fc.base_url))
782 .send()
783 .await?;
784 FakeCloud::parse(resp).await
785 }
786
787 pub async fn clear_faults(&self) -> Result<BedrockStatusResponse, Error> {
789 let resp = self
790 .fc
791 .client
792 .delete(format!("{}/_fakecloud/bedrock/faults", self.fc.base_url))
793 .send()
794 .await?;
795 FakeCloud::parse(resp).await
796 }
797}
798
799pub struct EcsClient<'a> {
802 fc: &'a FakeCloud,
803}
804
805impl EcsClient<'_> {
806 pub async fn get_clusters(&self) -> Result<EcsClustersResponse, Error> {
810 let resp = self
811 .fc
812 .client
813 .get(format!("{}/_fakecloud/ecs/clusters", self.fc.base_url))
814 .send()
815 .await?;
816 FakeCloud::parse(resp).await
817 }
818
819 pub async fn get_tasks(
822 &self,
823 cluster: Option<&str>,
824 status: Option<&str>,
825 ) -> Result<EcsTasksResponse, Error> {
826 fn encode(s: &str) -> String {
827 let mut out = String::with_capacity(s.len());
828 for b in s.bytes() {
829 match b {
830 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
831 out.push(b as char);
832 }
833 _ => out.push_str(&format!("%{:02X}", b)),
834 }
835 }
836 out
837 }
838 let mut url = format!("{}/_fakecloud/ecs/tasks", self.fc.base_url);
839 let mut sep = '?';
840 if let Some(c) = cluster {
841 url.push(sep);
842 url.push_str("cluster=");
843 url.push_str(&encode(c));
844 sep = '&';
845 }
846 if let Some(s) = status {
847 url.push(sep);
848 url.push_str("status=");
849 url.push_str(&encode(s));
850 }
851 let resp = self.fc.client.get(url).send().await?;
852 FakeCloud::parse(resp).await
853 }
854
855 pub async fn get_task_logs(&self, task_id: &str) -> Result<EcsTaskLogsResponse, Error> {
859 let resp = self
860 .fc
861 .client
862 .get(format!(
863 "{}/_fakecloud/ecs/tasks/{}/logs",
864 self.fc.base_url, task_id
865 ))
866 .send()
867 .await?;
868 FakeCloud::parse(resp).await
869 }
870
871 pub async fn force_stop_task(&self, task_id: &str) -> Result<EcsTask, Error> {
873 let resp = self
874 .fc
875 .client
876 .post(format!(
877 "{}/_fakecloud/ecs/tasks/{}/force-stop",
878 self.fc.base_url, task_id
879 ))
880 .send()
881 .await?;
882 FakeCloud::parse(resp).await
883 }
884
885 pub async fn mark_task_failed(
888 &self,
889 task_id: &str,
890 req: &EcsMarkFailedRequest,
891 ) -> Result<EcsTask, Error> {
892 let resp = self
893 .fc
894 .client
895 .post(format!(
896 "{}/_fakecloud/ecs/tasks/{}/mark-failed",
897 self.fc.base_url, task_id
898 ))
899 .json(req)
900 .send()
901 .await?;
902 FakeCloud::parse(resp).await
903 }
904
905 pub async fn get_events(&self) -> Result<EcsEventsResponse, Error> {
907 let resp = self
908 .fc
909 .client
910 .get(format!("{}/_fakecloud/ecs/events", self.fc.base_url))
911 .send()
912 .await?;
913 FakeCloud::parse(resp).await
914 }
915}