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    // ── Internal helpers ────────────────────────────────────────────
106
107    async fn parse<T: serde::de::DeserializeOwned>(resp: reqwest::Response) -> Result<T, Error> {
108        let status = resp.status().as_u16();
109        if !resp.status().is_success() {
110            let body = resp.text().await.unwrap_or_default();
111            return Err(Error::Api { status, body });
112        }
113        Ok(resp.json::<T>().await?)
114    }
115}
116
117// ── RDS ─────────────────────────────────────────────────────────────
118
119pub struct RdsClient<'a> {
120    fc: &'a FakeCloud,
121}
122
123impl RdsClient<'_> {
124    /// List fakecloud-managed RDS DB instances with runtime metadata.
125    pub async fn get_instances(&self) -> Result<RdsInstancesResponse, Error> {
126        let resp = self
127            .fc
128            .client
129            .get(format!("{}/_fakecloud/rds/instances", self.fc.base_url))
130            .send()
131            .await?;
132        FakeCloud::parse(resp).await
133    }
134}
135
136// ── ElastiCache ─────────────────────────────────────────────────────
137
138pub struct ElastiCacheClient<'a> {
139    fc: &'a FakeCloud,
140}
141
142impl ElastiCacheClient<'_> {
143    /// List fakecloud-managed ElastiCache cache clusters with runtime metadata.
144    pub async fn get_clusters(&self) -> Result<ElastiCacheClustersResponse, Error> {
145        let resp = self
146            .fc
147            .client
148            .get(format!(
149                "{}/_fakecloud/elasticache/clusters",
150                self.fc.base_url
151            ))
152            .send()
153            .await?;
154        FakeCloud::parse(resp).await
155    }
156
157    /// List fakecloud-managed ElastiCache replication groups with runtime metadata.
158    pub async fn get_replication_groups(
159        &self,
160    ) -> Result<ElastiCacheReplicationGroupsResponse, Error> {
161        let resp = self
162            .fc
163            .client
164            .get(format!(
165                "{}/_fakecloud/elasticache/replication-groups",
166                self.fc.base_url
167            ))
168            .send()
169            .await?;
170        FakeCloud::parse(resp).await
171    }
172
173    /// List fakecloud-managed ElastiCache serverless caches with runtime metadata.
174    pub async fn get_serverless_caches(
175        &self,
176    ) -> Result<ElastiCacheServerlessCachesResponse, Error> {
177        let resp = self
178            .fc
179            .client
180            .get(format!(
181                "{}/_fakecloud/elasticache/serverless-caches",
182                self.fc.base_url
183            ))
184            .send()
185            .await?;
186        FakeCloud::parse(resp).await
187    }
188}
189
190// ── Lambda ──────────────────────────────────────────────────────────
191
192pub struct LambdaClient<'a> {
193    fc: &'a FakeCloud,
194}
195
196impl LambdaClient<'_> {
197    /// List recorded Lambda invocations.
198    pub async fn get_invocations(&self) -> Result<LambdaInvocationsResponse, Error> {
199        let resp = self
200            .fc
201            .client
202            .get(format!(
203                "{}/_fakecloud/lambda/invocations",
204                self.fc.base_url
205            ))
206            .send()
207            .await?;
208        FakeCloud::parse(resp).await
209    }
210
211    /// List warm (cached) Lambda containers.
212    pub async fn get_warm_containers(&self) -> Result<WarmContainersResponse, Error> {
213        let resp = self
214            .fc
215            .client
216            .get(format!(
217                "{}/_fakecloud/lambda/warm-containers",
218                self.fc.base_url
219            ))
220            .send()
221            .await?;
222        FakeCloud::parse(resp).await
223    }
224
225    /// Evict the warm container for a specific function.
226    pub async fn evict_container(
227        &self,
228        function_name: &str,
229    ) -> Result<EvictContainerResponse, Error> {
230        let resp = self
231            .fc
232            .client
233            .post(format!(
234                "{}/_fakecloud/lambda/{}/evict-container",
235                self.fc.base_url, function_name
236            ))
237            .send()
238            .await?;
239        FakeCloud::parse(resp).await
240    }
241}
242
243// ── SES ─────────────────────────────────────────────────────────────
244
245pub struct SesClient<'a> {
246    fc: &'a FakeCloud,
247}
248
249impl SesClient<'_> {
250    /// List all sent emails.
251    pub async fn get_emails(&self) -> Result<SesEmailsResponse, Error> {
252        let resp = self
253            .fc
254            .client
255            .get(format!("{}/_fakecloud/ses/emails", self.fc.base_url))
256            .send()
257            .await?;
258        FakeCloud::parse(resp).await
259    }
260
261    /// Simulate an inbound email (SES receipt rules).
262    pub async fn simulate_inbound(
263        &self,
264        req: &InboundEmailRequest,
265    ) -> Result<InboundEmailResponse, Error> {
266        let resp = self
267            .fc
268            .client
269            .post(format!("{}/_fakecloud/ses/inbound", self.fc.base_url))
270            .json(req)
271            .send()
272            .await?;
273        FakeCloud::parse(resp).await
274    }
275}
276
277// ── SNS ─────────────────────────────────────────────────────────────
278
279pub struct SnsClient<'a> {
280    fc: &'a FakeCloud,
281}
282
283impl SnsClient<'_> {
284    /// List all published SNS messages.
285    pub async fn get_messages(&self) -> Result<SnsMessagesResponse, Error> {
286        let resp = self
287            .fc
288            .client
289            .get(format!("{}/_fakecloud/sns/messages", self.fc.base_url))
290            .send()
291            .await?;
292        FakeCloud::parse(resp).await
293    }
294
295    /// List subscriptions pending confirmation.
296    pub async fn get_pending_confirmations(&self) -> Result<PendingConfirmationsResponse, Error> {
297        let resp = self
298            .fc
299            .client
300            .get(format!(
301                "{}/_fakecloud/sns/pending-confirmations",
302                self.fc.base_url
303            ))
304            .send()
305            .await?;
306        FakeCloud::parse(resp).await
307    }
308
309    /// Confirm a pending subscription.
310    pub async fn confirm_subscription(
311        &self,
312        req: &ConfirmSubscriptionRequest,
313    ) -> Result<ConfirmSubscriptionResponse, Error> {
314        let resp = self
315            .fc
316            .client
317            .post(format!(
318                "{}/_fakecloud/sns/confirm-subscription",
319                self.fc.base_url
320            ))
321            .json(req)
322            .send()
323            .await?;
324        FakeCloud::parse(resp).await
325    }
326}
327
328// ── SQS ─────────────────────────────────────────────────────────────
329
330pub struct SqsClient<'a> {
331    fc: &'a FakeCloud,
332}
333
334impl SqsClient<'_> {
335    /// List all messages across all queues.
336    pub async fn get_messages(&self) -> Result<SqsMessagesResponse, Error> {
337        let resp = self
338            .fc
339            .client
340            .get(format!("{}/_fakecloud/sqs/messages", self.fc.base_url))
341            .send()
342            .await?;
343        FakeCloud::parse(resp).await
344    }
345
346    /// Tick the message expiration processor (expire visibility-timed-out messages).
347    pub async fn tick_expiration(&self) -> Result<ExpirationTickResponse, Error> {
348        let resp = self
349            .fc
350            .client
351            .post(format!(
352                "{}/_fakecloud/sqs/expiration-processor/tick",
353                self.fc.base_url
354            ))
355            .send()
356            .await?;
357        FakeCloud::parse(resp).await
358    }
359
360    /// Force all messages in a queue to its DLQ.
361    pub async fn force_dlq(&self, queue_name: &str) -> Result<ForceDlqResponse, Error> {
362        let resp = self
363            .fc
364            .client
365            .post(format!(
366                "{}/_fakecloud/sqs/{}/force-dlq",
367                self.fc.base_url, queue_name
368            ))
369            .send()
370            .await?;
371        FakeCloud::parse(resp).await
372    }
373}
374
375// ── EventBridge ─────────────────────────────────────────────────────
376
377pub struct EventsClient<'a> {
378    fc: &'a FakeCloud,
379}
380
381impl EventsClient<'_> {
382    /// Get event history and delivery records.
383    pub async fn get_history(&self) -> Result<EventHistoryResponse, Error> {
384        let resp = self
385            .fc
386            .client
387            .get(format!("{}/_fakecloud/events/history", self.fc.base_url))
388            .send()
389            .await?;
390        FakeCloud::parse(resp).await
391    }
392
393    /// Fire a specific EventBridge rule manually.
394    pub async fn fire_rule(&self, req: &FireRuleRequest) -> Result<FireRuleResponse, Error> {
395        let resp = self
396            .fc
397            .client
398            .post(format!("{}/_fakecloud/events/fire-rule", self.fc.base_url))
399            .json(req)
400            .send()
401            .await?;
402        FakeCloud::parse(resp).await
403    }
404}
405
406// ── S3 ──────────────────────────────────────────────────────────────
407
408pub struct S3Client<'a> {
409    fc: &'a FakeCloud,
410}
411
412impl S3Client<'_> {
413    /// List S3 notification events.
414    pub async fn get_notifications(&self) -> Result<S3NotificationsResponse, Error> {
415        let resp = self
416            .fc
417            .client
418            .get(format!("{}/_fakecloud/s3/notifications", self.fc.base_url))
419            .send()
420            .await?;
421        FakeCloud::parse(resp).await
422    }
423
424    /// Tick the S3 lifecycle processor.
425    pub async fn tick_lifecycle(&self) -> Result<LifecycleTickResponse, Error> {
426        let resp = self
427            .fc
428            .client
429            .post(format!(
430                "{}/_fakecloud/s3/lifecycle-processor/tick",
431                self.fc.base_url
432            ))
433            .send()
434            .await?;
435        FakeCloud::parse(resp).await
436    }
437}
438
439// ── DynamoDB ────────────────────────────────────────────────────────
440
441pub struct DynamoDbClient<'a> {
442    fc: &'a FakeCloud,
443}
444
445impl DynamoDbClient<'_> {
446    /// Tick the DynamoDB TTL processor.
447    pub async fn tick_ttl(&self) -> Result<TtlTickResponse, Error> {
448        let resp = self
449            .fc
450            .client
451            .post(format!(
452                "{}/_fakecloud/dynamodb/ttl-processor/tick",
453                self.fc.base_url
454            ))
455            .send()
456            .await?;
457        FakeCloud::parse(resp).await
458    }
459}
460
461// ── SecretsManager ──────────────────────────────────────────────────
462
463pub struct SecretsManagerClient<'a> {
464    fc: &'a FakeCloud,
465}
466
467impl SecretsManagerClient<'_> {
468    /// Tick the SecretsManager rotation scheduler.
469    pub async fn tick_rotation(&self) -> Result<RotationTickResponse, Error> {
470        let resp = self
471            .fc
472            .client
473            .post(format!(
474                "{}/_fakecloud/secretsmanager/rotation-scheduler/tick",
475                self.fc.base_url
476            ))
477            .send()
478            .await?;
479        FakeCloud::parse(resp).await
480    }
481}
482
483// ── Cognito ─────────────────────────────────────────────────────────
484
485pub struct CognitoClient<'a> {
486    fc: &'a FakeCloud,
487}
488
489impl CognitoClient<'_> {
490    /// Get confirmation codes for a specific user.
491    pub async fn get_user_codes(
492        &self,
493        pool_id: &str,
494        username: &str,
495    ) -> Result<UserConfirmationCodes, Error> {
496        let resp = self
497            .fc
498            .client
499            .get(format!(
500                "{}/_fakecloud/cognito/confirmation-codes/{}/{}",
501                self.fc.base_url, pool_id, username
502            ))
503            .send()
504            .await?;
505        FakeCloud::parse(resp).await
506    }
507
508    /// List all confirmation codes across all pools.
509    pub async fn get_confirmation_codes(&self) -> Result<ConfirmationCodesResponse, Error> {
510        let resp = self
511            .fc
512            .client
513            .get(format!(
514                "{}/_fakecloud/cognito/confirmation-codes",
515                self.fc.base_url
516            ))
517            .send()
518            .await?;
519        FakeCloud::parse(resp).await
520    }
521
522    /// Confirm a user (bypass email/phone verification).
523    pub async fn confirm_user(
524        &self,
525        req: &ConfirmUserRequest,
526    ) -> Result<ConfirmUserResponse, Error> {
527        let resp = self
528            .fc
529            .client
530            .post(format!(
531                "{}/_fakecloud/cognito/confirm-user",
532                self.fc.base_url
533            ))
534            .json(req)
535            .send()
536            .await?;
537        let status = resp.status().as_u16();
538        let body: ConfirmUserResponse = resp.json().await?;
539        if status >= 400 {
540            return Err(Error::Api {
541                status,
542                body: body.error.unwrap_or_default(),
543            });
544        }
545        Ok(body)
546    }
547
548    /// List all active tokens.
549    pub async fn get_tokens(&self) -> Result<TokensResponse, Error> {
550        let resp = self
551            .fc
552            .client
553            .get(format!("{}/_fakecloud/cognito/tokens", self.fc.base_url))
554            .send()
555            .await?;
556        FakeCloud::parse(resp).await
557    }
558
559    /// Expire tokens (optionally filtered by pool/user).
560    pub async fn expire_tokens(
561        &self,
562        req: &ExpireTokensRequest,
563    ) -> Result<ExpireTokensResponse, Error> {
564        let resp = self
565            .fc
566            .client
567            .post(format!(
568                "{}/_fakecloud/cognito/expire-tokens",
569                self.fc.base_url
570            ))
571            .json(req)
572            .send()
573            .await?;
574        FakeCloud::parse(resp).await
575    }
576
577    /// List auth events.
578    pub async fn get_auth_events(&self) -> Result<AuthEventsResponse, Error> {
579        let resp = self
580            .fc
581            .client
582            .get(format!(
583                "{}/_fakecloud/cognito/auth-events",
584                self.fc.base_url
585            ))
586            .send()
587            .await?;
588        FakeCloud::parse(resp).await
589    }
590}
591
592// ── API Gateway v2 ──────────────────────────────────────────────────
593
594pub struct ApiGatewayV2Client<'a> {
595    fc: &'a FakeCloud,
596}
597
598impl ApiGatewayV2Client<'_> {
599    /// List all HTTP API requests that were received and processed.
600    pub async fn get_requests(&self) -> Result<ApiGatewayV2RequestsResponse, Error> {
601        let resp = self
602            .fc
603            .client
604            .get(format!(
605                "{}/_fakecloud/apigatewayv2/requests",
606                self.fc.base_url
607            ))
608            .send()
609            .await?;
610        FakeCloud::parse(resp).await
611    }
612}
613
614// ── Step Functions ──────────────────────────────────────────────────
615
616pub struct StepFunctionsClient<'a> {
617    fc: &'a FakeCloud,
618}
619
620impl StepFunctionsClient<'_> {
621    /// List all Step Functions executions with status, input, output, and timestamps.
622    pub async fn get_executions(&self) -> Result<StepFunctionsExecutionsResponse, Error> {
623        let resp = self
624            .fc
625            .client
626            .get(format!(
627                "{}/_fakecloud/stepfunctions/executions",
628                self.fc.base_url
629            ))
630            .send()
631            .await?;
632        FakeCloud::parse(resp).await
633    }
634}