Skip to main content

fakecloud_sdk/
client.rs

1use crate::error::Error;
2use crate::types::*;
3
4/// Client for the fakecloud introspection and simulation API (`/_fakecloud/*`).
5pub struct FakeCloud {
6    base_url: String,
7    client: reqwest::Client,
8}
9
10impl FakeCloud {
11    /// Create a new client pointing at the given fakecloud base URL (e.g. `http://localhost:4566`).
12    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    // ── Health & Reset ──────────────────────────────────────────────
20
21    /// Check server health.
22    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    /// Reset all service state. Uses the legacy `/_reset` endpoint.
32    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    /// Reset a single service's state.
42    pub async fn reset_service(&self, service: &str) -> Result<ResetServiceResponse, Error> {
43        let resp = self
44            .client
45            .post(format!("{}/_fakecloud/reset/{}", self.base_url, service))
46            .send()
47            .await?;
48        Self::parse(resp).await
49    }
50
51    // ── Sub-clients ─────────────────────────────────────────────────
52
53    pub fn lambda(&self) -> LambdaClient<'_> {
54        LambdaClient { fc: self }
55    }
56
57    pub fn ses(&self) -> SesClient<'_> {
58        SesClient { fc: self }
59    }
60
61    pub fn sns(&self) -> SnsClient<'_> {
62        SnsClient { fc: self }
63    }
64
65    pub fn sqs(&self) -> SqsClient<'_> {
66        SqsClient { fc: self }
67    }
68
69    pub fn events(&self) -> EventsClient<'_> {
70        EventsClient { fc: self }
71    }
72
73    pub fn s3(&self) -> S3Client<'_> {
74        S3Client { fc: self }
75    }
76
77    pub fn dynamodb(&self) -> DynamoDbClient<'_> {
78        DynamoDbClient { fc: self }
79    }
80
81    pub fn secretsmanager(&self) -> SecretsManagerClient<'_> {
82        SecretsManagerClient { fc: self }
83    }
84
85    pub fn cognito(&self) -> CognitoClient<'_> {
86        CognitoClient { fc: self }
87    }
88
89    pub fn rds(&self) -> RdsClient<'_> {
90        RdsClient { fc: self }
91    }
92
93    pub fn elasticache(&self) -> ElastiCacheClient<'_> {
94        ElastiCacheClient { fc: self }
95    }
96
97    pub fn apigatewayv2(&self) -> ApiGatewayV2Client<'_> {
98        ApiGatewayV2Client { fc: self }
99    }
100
101    pub fn stepfunctions(&self) -> StepFunctionsClient<'_> {
102        StepFunctionsClient { fc: self }
103    }
104
105    pub fn bedrock(&self) -> BedrockClient<'_> {
106        BedrockClient { fc: self }
107    }
108
109    // ── Internal helpers ────────────────────────────────────────────
110
111    async fn parse<T: serde::de::DeserializeOwned>(resp: reqwest::Response) -> Result<T, Error> {
112        let status = resp.status().as_u16();
113        if !resp.status().is_success() {
114            let body = resp.text().await.unwrap_or_default();
115            return Err(Error::Api { status, body });
116        }
117        Ok(resp.json::<T>().await?)
118    }
119}
120
121// ── RDS ─────────────────────────────────────────────────────────────
122
123pub struct RdsClient<'a> {
124    fc: &'a FakeCloud,
125}
126
127impl RdsClient<'_> {
128    /// List fakecloud-managed RDS DB instances with runtime metadata.
129    pub async fn get_instances(&self) -> Result<RdsInstancesResponse, Error> {
130        let resp = self
131            .fc
132            .client
133            .get(format!("{}/_fakecloud/rds/instances", self.fc.base_url))
134            .send()
135            .await?;
136        FakeCloud::parse(resp).await
137    }
138}
139
140// ── ElastiCache ─────────────────────────────────────────────────────
141
142pub struct ElastiCacheClient<'a> {
143    fc: &'a FakeCloud,
144}
145
146impl ElastiCacheClient<'_> {
147    /// List fakecloud-managed ElastiCache cache clusters with runtime metadata.
148    pub async fn get_clusters(&self) -> Result<ElastiCacheClustersResponse, Error> {
149        let resp = self
150            .fc
151            .client
152            .get(format!(
153                "{}/_fakecloud/elasticache/clusters",
154                self.fc.base_url
155            ))
156            .send()
157            .await?;
158        FakeCloud::parse(resp).await
159    }
160
161    /// List fakecloud-managed ElastiCache replication groups with runtime metadata.
162    pub async fn get_replication_groups(
163        &self,
164    ) -> Result<ElastiCacheReplicationGroupsResponse, Error> {
165        let resp = self
166            .fc
167            .client
168            .get(format!(
169                "{}/_fakecloud/elasticache/replication-groups",
170                self.fc.base_url
171            ))
172            .send()
173            .await?;
174        FakeCloud::parse(resp).await
175    }
176
177    /// List fakecloud-managed ElastiCache serverless caches with runtime metadata.
178    pub async fn get_serverless_caches(
179        &self,
180    ) -> Result<ElastiCacheServerlessCachesResponse, Error> {
181        let resp = self
182            .fc
183            .client
184            .get(format!(
185                "{}/_fakecloud/elasticache/serverless-caches",
186                self.fc.base_url
187            ))
188            .send()
189            .await?;
190        FakeCloud::parse(resp).await
191    }
192}
193
194// ── Lambda ──────────────────────────────────────────────────────────
195
196pub struct LambdaClient<'a> {
197    fc: &'a FakeCloud,
198}
199
200impl LambdaClient<'_> {
201    /// List recorded Lambda invocations.
202    pub async fn get_invocations(&self) -> Result<LambdaInvocationsResponse, Error> {
203        let resp = self
204            .fc
205            .client
206            .get(format!(
207                "{}/_fakecloud/lambda/invocations",
208                self.fc.base_url
209            ))
210            .send()
211            .await?;
212        FakeCloud::parse(resp).await
213    }
214
215    /// List warm (cached) Lambda containers.
216    pub async fn get_warm_containers(&self) -> Result<WarmContainersResponse, Error> {
217        let resp = self
218            .fc
219            .client
220            .get(format!(
221                "{}/_fakecloud/lambda/warm-containers",
222                self.fc.base_url
223            ))
224            .send()
225            .await?;
226        FakeCloud::parse(resp).await
227    }
228
229    /// Evict the warm container for a specific function.
230    pub async fn evict_container(
231        &self,
232        function_name: &str,
233    ) -> Result<EvictContainerResponse, Error> {
234        let resp = self
235            .fc
236            .client
237            .post(format!(
238                "{}/_fakecloud/lambda/{}/evict-container",
239                self.fc.base_url, function_name
240            ))
241            .send()
242            .await?;
243        FakeCloud::parse(resp).await
244    }
245}
246
247// ── SES ─────────────────────────────────────────────────────────────
248
249pub struct SesClient<'a> {
250    fc: &'a FakeCloud,
251}
252
253impl SesClient<'_> {
254    /// List all sent emails.
255    pub async fn get_emails(&self) -> Result<SesEmailsResponse, Error> {
256        let resp = self
257            .fc
258            .client
259            .get(format!("{}/_fakecloud/ses/emails", self.fc.base_url))
260            .send()
261            .await?;
262        FakeCloud::parse(resp).await
263    }
264
265    /// Simulate an inbound email (SES receipt rules).
266    pub async fn simulate_inbound(
267        &self,
268        req: &InboundEmailRequest,
269    ) -> Result<InboundEmailResponse, Error> {
270        let resp = self
271            .fc
272            .client
273            .post(format!("{}/_fakecloud/ses/inbound", self.fc.base_url))
274            .json(req)
275            .send()
276            .await?;
277        FakeCloud::parse(resp).await
278    }
279}
280
281// ── SNS ─────────────────────────────────────────────────────────────
282
283pub struct SnsClient<'a> {
284    fc: &'a FakeCloud,
285}
286
287impl SnsClient<'_> {
288    /// List all published SNS messages.
289    pub async fn get_messages(&self) -> Result<SnsMessagesResponse, Error> {
290        let resp = self
291            .fc
292            .client
293            .get(format!("{}/_fakecloud/sns/messages", self.fc.base_url))
294            .send()
295            .await?;
296        FakeCloud::parse(resp).await
297    }
298
299    /// List subscriptions pending confirmation.
300    pub async fn get_pending_confirmations(&self) -> Result<PendingConfirmationsResponse, Error> {
301        let resp = self
302            .fc
303            .client
304            .get(format!(
305                "{}/_fakecloud/sns/pending-confirmations",
306                self.fc.base_url
307            ))
308            .send()
309            .await?;
310        FakeCloud::parse(resp).await
311    }
312
313    /// Confirm a pending subscription.
314    pub async fn confirm_subscription(
315        &self,
316        req: &ConfirmSubscriptionRequest,
317    ) -> Result<ConfirmSubscriptionResponse, Error> {
318        let resp = self
319            .fc
320            .client
321            .post(format!(
322                "{}/_fakecloud/sns/confirm-subscription",
323                self.fc.base_url
324            ))
325            .json(req)
326            .send()
327            .await?;
328        FakeCloud::parse(resp).await
329    }
330}
331
332// ── SQS ─────────────────────────────────────────────────────────────
333
334pub struct SqsClient<'a> {
335    fc: &'a FakeCloud,
336}
337
338impl SqsClient<'_> {
339    /// List all messages across all queues.
340    pub async fn get_messages(&self) -> Result<SqsMessagesResponse, Error> {
341        let resp = self
342            .fc
343            .client
344            .get(format!("{}/_fakecloud/sqs/messages", self.fc.base_url))
345            .send()
346            .await?;
347        FakeCloud::parse(resp).await
348    }
349
350    /// Tick the message expiration processor (expire visibility-timed-out messages).
351    pub async fn tick_expiration(&self) -> Result<ExpirationTickResponse, Error> {
352        let resp = self
353            .fc
354            .client
355            .post(format!(
356                "{}/_fakecloud/sqs/expiration-processor/tick",
357                self.fc.base_url
358            ))
359            .send()
360            .await?;
361        FakeCloud::parse(resp).await
362    }
363
364    /// Force all messages in a queue to its DLQ.
365    pub async fn force_dlq(&self, queue_name: &str) -> Result<ForceDlqResponse, Error> {
366        let resp = self
367            .fc
368            .client
369            .post(format!(
370                "{}/_fakecloud/sqs/{}/force-dlq",
371                self.fc.base_url, queue_name
372            ))
373            .send()
374            .await?;
375        FakeCloud::parse(resp).await
376    }
377}
378
379// ── EventBridge ─────────────────────────────────────────────────────
380
381pub struct EventsClient<'a> {
382    fc: &'a FakeCloud,
383}
384
385impl EventsClient<'_> {
386    /// Get event history and delivery records.
387    pub async fn get_history(&self) -> Result<EventHistoryResponse, Error> {
388        let resp = self
389            .fc
390            .client
391            .get(format!("{}/_fakecloud/events/history", self.fc.base_url))
392            .send()
393            .await?;
394        FakeCloud::parse(resp).await
395    }
396
397    /// Fire a specific EventBridge rule manually.
398    pub async fn fire_rule(&self, req: &FireRuleRequest) -> Result<FireRuleResponse, Error> {
399        let resp = self
400            .fc
401            .client
402            .post(format!("{}/_fakecloud/events/fire-rule", self.fc.base_url))
403            .json(req)
404            .send()
405            .await?;
406        FakeCloud::parse(resp).await
407    }
408}
409
410// ── S3 ──────────────────────────────────────────────────────────────
411
412pub struct S3Client<'a> {
413    fc: &'a FakeCloud,
414}
415
416impl S3Client<'_> {
417    /// List S3 notification events.
418    pub async fn get_notifications(&self) -> Result<S3NotificationsResponse, Error> {
419        let resp = self
420            .fc
421            .client
422            .get(format!("{}/_fakecloud/s3/notifications", self.fc.base_url))
423            .send()
424            .await?;
425        FakeCloud::parse(resp).await
426    }
427
428    /// Tick the S3 lifecycle processor.
429    pub async fn tick_lifecycle(&self) -> Result<LifecycleTickResponse, Error> {
430        let resp = self
431            .fc
432            .client
433            .post(format!(
434                "{}/_fakecloud/s3/lifecycle-processor/tick",
435                self.fc.base_url
436            ))
437            .send()
438            .await?;
439        FakeCloud::parse(resp).await
440    }
441}
442
443// ── DynamoDB ────────────────────────────────────────────────────────
444
445pub struct DynamoDbClient<'a> {
446    fc: &'a FakeCloud,
447}
448
449impl DynamoDbClient<'_> {
450    /// Tick the DynamoDB TTL processor.
451    pub async fn tick_ttl(&self) -> Result<TtlTickResponse, Error> {
452        let resp = self
453            .fc
454            .client
455            .post(format!(
456                "{}/_fakecloud/dynamodb/ttl-processor/tick",
457                self.fc.base_url
458            ))
459            .send()
460            .await?;
461        FakeCloud::parse(resp).await
462    }
463}
464
465// ── SecretsManager ──────────────────────────────────────────────────
466
467pub struct SecretsManagerClient<'a> {
468    fc: &'a FakeCloud,
469}
470
471impl SecretsManagerClient<'_> {
472    /// Tick the SecretsManager rotation scheduler.
473    pub async fn tick_rotation(&self) -> Result<RotationTickResponse, Error> {
474        let resp = self
475            .fc
476            .client
477            .post(format!(
478                "{}/_fakecloud/secretsmanager/rotation-scheduler/tick",
479                self.fc.base_url
480            ))
481            .send()
482            .await?;
483        FakeCloud::parse(resp).await
484    }
485}
486
487// ── Cognito ─────────────────────────────────────────────────────────
488
489pub struct CognitoClient<'a> {
490    fc: &'a FakeCloud,
491}
492
493impl CognitoClient<'_> {
494    /// Get confirmation codes for a specific user.
495    pub async fn get_user_codes(
496        &self,
497        pool_id: &str,
498        username: &str,
499    ) -> Result<UserConfirmationCodes, Error> {
500        let resp = self
501            .fc
502            .client
503            .get(format!(
504                "{}/_fakecloud/cognito/confirmation-codes/{}/{}",
505                self.fc.base_url, pool_id, username
506            ))
507            .send()
508            .await?;
509        FakeCloud::parse(resp).await
510    }
511
512    /// List all confirmation codes across all pools.
513    pub async fn get_confirmation_codes(&self) -> Result<ConfirmationCodesResponse, Error> {
514        let resp = self
515            .fc
516            .client
517            .get(format!(
518                "{}/_fakecloud/cognito/confirmation-codes",
519                self.fc.base_url
520            ))
521            .send()
522            .await?;
523        FakeCloud::parse(resp).await
524    }
525
526    /// Confirm a user (bypass email/phone verification).
527    pub async fn confirm_user(
528        &self,
529        req: &ConfirmUserRequest,
530    ) -> Result<ConfirmUserResponse, Error> {
531        let resp = self
532            .fc
533            .client
534            .post(format!(
535                "{}/_fakecloud/cognito/confirm-user",
536                self.fc.base_url
537            ))
538            .json(req)
539            .send()
540            .await?;
541        let status = resp.status().as_u16();
542        let body: ConfirmUserResponse = resp.json().await?;
543        if status >= 400 {
544            return Err(Error::Api {
545                status,
546                body: body.error.unwrap_or_default(),
547            });
548        }
549        Ok(body)
550    }
551
552    /// List all active tokens.
553    pub async fn get_tokens(&self) -> Result<TokensResponse, Error> {
554        let resp = self
555            .fc
556            .client
557            .get(format!("{}/_fakecloud/cognito/tokens", self.fc.base_url))
558            .send()
559            .await?;
560        FakeCloud::parse(resp).await
561    }
562
563    /// Expire tokens (optionally filtered by pool/user).
564    pub async fn expire_tokens(
565        &self,
566        req: &ExpireTokensRequest,
567    ) -> Result<ExpireTokensResponse, Error> {
568        let resp = self
569            .fc
570            .client
571            .post(format!(
572                "{}/_fakecloud/cognito/expire-tokens",
573                self.fc.base_url
574            ))
575            .json(req)
576            .send()
577            .await?;
578        FakeCloud::parse(resp).await
579    }
580
581    /// List auth events.
582    pub async fn get_auth_events(&self) -> Result<AuthEventsResponse, Error> {
583        let resp = self
584            .fc
585            .client
586            .get(format!(
587                "{}/_fakecloud/cognito/auth-events",
588                self.fc.base_url
589            ))
590            .send()
591            .await?;
592        FakeCloud::parse(resp).await
593    }
594}
595
596// ── API Gateway v2 ──────────────────────────────────────────────────
597
598pub struct ApiGatewayV2Client<'a> {
599    fc: &'a FakeCloud,
600}
601
602impl ApiGatewayV2Client<'_> {
603    /// List all HTTP API requests that were received and processed.
604    pub async fn get_requests(&self) -> Result<ApiGatewayV2RequestsResponse, Error> {
605        let resp = self
606            .fc
607            .client
608            .get(format!(
609                "{}/_fakecloud/apigatewayv2/requests",
610                self.fc.base_url
611            ))
612            .send()
613            .await?;
614        FakeCloud::parse(resp).await
615    }
616}
617
618// ── Step Functions ──────────────────────────────────────────────────
619
620pub struct StepFunctionsClient<'a> {
621    fc: &'a FakeCloud,
622}
623
624impl StepFunctionsClient<'_> {
625    /// List all Step Functions executions with status, input, output, and timestamps.
626    pub async fn get_executions(&self) -> Result<StepFunctionsExecutionsResponse, Error> {
627        let resp = self
628            .fc
629            .client
630            .get(format!(
631                "{}/_fakecloud/stepfunctions/executions",
632                self.fc.base_url
633            ))
634            .send()
635            .await?;
636        FakeCloud::parse(resp).await
637    }
638}
639
640// ── Bedrock ─────────────────────────────────────────────────────────
641
642pub struct BedrockClient<'a> {
643    fc: &'a FakeCloud,
644}
645
646impl BedrockClient<'_> {
647    /// List recorded Bedrock runtime invocations. Each invocation has an optional
648    /// `error` field that is set for calls faulted via [`Self::queue_fault`].
649    pub async fn get_invocations(&self) -> Result<BedrockInvocationsResponse, Error> {
650        let resp = self
651            .fc
652            .client
653            .get(format!(
654                "{}/_fakecloud/bedrock/invocations",
655                self.fc.base_url
656            ))
657            .send()
658            .await?;
659        FakeCloud::parse(resp).await
660    }
661
662    /// Configure a single canned response for a Bedrock model.
663    pub async fn set_model_response(
664        &self,
665        model_id: &str,
666        response: &str,
667    ) -> Result<BedrockModelResponseConfig, Error> {
668        let resp = self
669            .fc
670            .client
671            .post(format!(
672                "{}/_fakecloud/bedrock/models/{}/response",
673                self.fc.base_url, model_id
674            ))
675            .header("content-type", "text/plain")
676            .body(response.to_string())
677            .send()
678            .await?;
679        FakeCloud::parse(resp).await
680    }
681
682    /// Replace the prompt-conditional response rule list for a Bedrock model.
683    pub async fn set_response_rules(
684        &self,
685        model_id: &str,
686        rules: &[BedrockResponseRule],
687    ) -> Result<BedrockModelResponseConfig, Error> {
688        let body = serde_json::json!({ "rules": rules });
689        let resp = self
690            .fc
691            .client
692            .post(format!(
693                "{}/_fakecloud/bedrock/models/{}/responses",
694                self.fc.base_url, model_id
695            ))
696            .json(&body)
697            .send()
698            .await?;
699        FakeCloud::parse(resp).await
700    }
701
702    /// Clear all prompt-conditional response rules for a Bedrock model.
703    pub async fn clear_response_rules(
704        &self,
705        model_id: &str,
706    ) -> Result<BedrockModelResponseConfig, Error> {
707        let resp = self
708            .fc
709            .client
710            .delete(format!(
711                "{}/_fakecloud/bedrock/models/{}/responses",
712                self.fc.base_url, model_id
713            ))
714            .send()
715            .await?;
716        FakeCloud::parse(resp).await
717    }
718
719    /// Queue a fault rule that will cause the next matching Bedrock runtime call(s) to fail.
720    pub async fn queue_fault(
721        &self,
722        rule: &BedrockFaultRule,
723    ) -> Result<BedrockStatusResponse, Error> {
724        let resp = self
725            .fc
726            .client
727            .post(format!("{}/_fakecloud/bedrock/faults", self.fc.base_url))
728            .json(rule)
729            .send()
730            .await?;
731        FakeCloud::parse(resp).await
732    }
733
734    /// List currently queued fault rules.
735    pub async fn get_faults(&self) -> Result<BedrockFaultsResponse, Error> {
736        let resp = self
737            .fc
738            .client
739            .get(format!("{}/_fakecloud/bedrock/faults", self.fc.base_url))
740            .send()
741            .await?;
742        FakeCloud::parse(resp).await
743    }
744
745    /// Clear all queued fault rules.
746    pub async fn clear_faults(&self) -> Result<BedrockStatusResponse, Error> {
747        let resp = self
748            .fc
749            .client
750            .delete(format!("{}/_fakecloud/bedrock/faults", self.fc.base_url))
751            .send()
752            .await?;
753        FakeCloud::parse(resp).await
754    }
755}