Skip to main content

redis_cloud/connectivity/
mod.rs

1//! Network connectivity and peering operations for Pro subscriptions
2//!
3//! This module manages advanced networking features for Redis Cloud Pro subscriptions,
4//! including VPC peering, AWS Transit Gateway attachments, GCP Private Service Connect,
5//! AWS `PrivateLink`, and other cloud-native networking integrations.
6//!
7//! # Supported Connectivity Types
8//!
9//! - **VPC Peering**: Direct peering between Redis Cloud VPC and your VPC
10//! - **Transit Gateway**: AWS Transit Gateway attachments for hub-and-spoke topologies
11//! - **Private Service Connect**: GCP Private Service Connect for private endpoints
12//! - **`PrivateLink`**: AWS `PrivateLink` for secure private connectivity
13//!
14//! # Module Organization
15//!
16//! The connectivity features are split into four specialized modules:
17//! - `vpc_peering` - VPC peering operations for AWS, GCP, and Azure
18//! - `psc` - Google Cloud Private Service Connect endpoints
19//! - `transit_gateway` - AWS Transit Gateway attachments
20//! - `private_link` - AWS `PrivateLink` connectivity
21
22pub mod private_link;
23pub mod psc;
24pub mod transit_gateway;
25pub mod vpc_peering;
26
27// Re-export handlers for convenience
28pub use private_link::PrivateLinkHandler;
29
30// Re-export PrivateLink types
31pub use private_link::{
32    PrincipalType, PrivateLinkActiveActiveConnectionsDisassociateRequest,
33    PrivateLinkAddPrincipalRequest, PrivateLinkConnectionDisassociate,
34    PrivateLinkConnectionsDisassociateRequest, PrivateLinkCreateRequest,
35    PrivateLinkRemovePrincipalRequest,
36};
37pub use psc::PscHandler;
38pub use transit_gateway::TransitGatewayHandler;
39pub use vpc_peering::VpcPeeringHandler;
40
41// Re-export types used by handlers
42pub use psc::PscEndpointUpdateRequest;
43pub use transit_gateway::{Cidr, TgwAttachmentRequest, TgwUpdateCidrsRequest};
44pub use vpc_peering::{
45    ActiveActiveVpcPeering, ActiveActiveVpcPeeringCreateRequest, ActiveActiveVpcPeeringList,
46    ActiveActiveVpcRegion, VpcCidr, VpcPeering, VpcPeeringCreateBaseRequest,
47    VpcPeeringCreateRequest, VpcPeeringUpdateAwsRequest, VpcPeeringUpdateRequest,
48};
49
50// For backward compatibility, provide a unified handler
51use crate::CloudClient;
52
53/// Unified connectivity handler combining VPC Peering, PSC, and Transit Gateway operations.
54///
55/// `ConnectivityHandler` is a backward-compatibility facade: each method
56/// delegates to the underlying specialized handler. New code should prefer
57/// the specialized handlers directly — they expose the full surface for their
58/// respective connectivity domains:
59///
60/// - [`VpcPeeringHandler`] — VPC peering for AWS, GCP, and Azure
61/// - [`PscHandler`] — Google Cloud Private Service Connect
62/// - [`TransitGatewayHandler`] — AWS Transit Gateway attachments
63///
64/// Errors returned from delegated calls propagate unchanged; see each backing
65/// handler's documentation for the full error surface.
66///
67/// # Example
68///
69/// ```rust,no_run
70/// use redis_cloud::{CloudClient, ConnectivityHandler};
71///
72/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
73/// let client = CloudClient::builder()
74///     .api_key("key")
75///     .api_secret("secret")
76///     .build()?;
77///
78/// let connectivity = ConnectivityHandler::new(client);
79/// let _vpc_task = connectivity.get_vpc_peering(123).await?;
80/// # Ok(())
81/// # }
82/// ```
83pub struct ConnectivityHandler {
84    /// VPC peering sub-handler. See [`VpcPeeringHandler`].
85    pub vpc_peering: VpcPeeringHandler,
86    /// Private Service Connect sub-handler. See [`PscHandler`].
87    pub psc: PscHandler,
88    /// Transit Gateway sub-handler. See [`TransitGatewayHandler`].
89    pub transit_gateway: TransitGatewayHandler,
90}
91
92impl ConnectivityHandler {
93    /// Construct a `ConnectivityHandler` wired to the supplied [`CloudClient`].
94    ///
95    /// Internally clones the client into each of the three sub-handlers, so
96    /// `client` can be dropped after construction if not needed elsewhere.
97    #[must_use]
98    pub fn new(client: CloudClient) -> Self {
99        Self {
100            vpc_peering: VpcPeeringHandler::new(client.clone()),
101            psc: PscHandler::new(client.clone()),
102            transit_gateway: TransitGatewayHandler::new(client),
103        }
104    }
105
106    // ========================================================================
107    // VPC Peering delegation methods
108    // ========================================================================
109
110    /// Get the VPC peering configuration for a Pro subscription.
111    ///
112    /// `GET /subscriptions/{subscriptionId}/peerings`. Delegates to
113    /// [`VpcPeeringHandler::get`].
114    ///
115    /// # Errors
116    ///
117    /// Propagates any [`crate::CloudError`] returned by the underlying call —
118    /// typically `NotFound` if the subscription has no peering configured,
119    /// `AuthenticationFailed` for bad credentials, or `InternalServerError`.
120    pub async fn get_vpc_peering(
121        &self,
122        subscription_id: i32,
123    ) -> crate::Result<crate::types::TaskStateUpdate> {
124        self.vpc_peering.get(subscription_id).await
125    }
126
127    /// Create a VPC peering on a Pro subscription.
128    ///
129    /// `POST /subscriptions/{subscriptionId}/peerings`. Delegates to
130    /// [`VpcPeeringHandler::create`]. Use
131    /// [`VpcPeeringCreateRequest::for_aws`] or
132    /// [`VpcPeeringCreateRequest::for_gcp`] to build a provider-targeted body
133    /// with the spec's required fields.
134    ///
135    /// # Errors
136    ///
137    /// Returns [`crate::CloudError`] for transport, auth, or 4xx/5xx
138    /// responses. `BadRequest` is common when the body is missing
139    /// provider-required fields.
140    pub async fn create_vpc_peering(
141        &self,
142        subscription_id: i32,
143        request: &VpcPeeringCreateRequest,
144    ) -> crate::Result<crate::types::TaskStateUpdate> {
145        self.vpc_peering.create(subscription_id, request).await
146    }
147
148    /// Delete a VPC peering by its peering ID.
149    ///
150    /// `DELETE /subscriptions/{subscriptionId}/peerings/{peeringId}`.
151    /// Delegates to [`VpcPeeringHandler::delete`]. The deletion is asynchronous;
152    /// the returned [`TaskStateUpdate`](crate::types::TaskStateUpdate) carries a
153    /// `task_id` that can be polled to completion.
154    ///
155    /// # Errors
156    ///
157    /// Returns [`crate::CloudError`] for non-2xx responses; `NotFound` if the
158    /// peering does not exist.
159    pub async fn delete_vpc_peering(
160        &self,
161        subscription_id: i32,
162        peering_id: i32,
163    ) -> crate::Result<crate::types::TaskStateUpdate> {
164        self.vpc_peering.delete(subscription_id, peering_id).await
165    }
166
167    /// Update a VPC peering's CIDR list.
168    ///
169    /// `PUT /subscriptions/{subscriptionId}/peerings/{peeringId}`. Translates
170    /// the AWS-only [`VpcPeeringUpdateAwsRequest`] into the underlying
171    /// [`VpcPeeringCreateRequest`] shape and delegates to
172    /// [`VpcPeeringHandler::update`].
173    ///
174    /// # Errors
175    ///
176    /// Returns [`crate::CloudError`] for transport, auth, or 4xx/5xx
177    /// responses.
178    pub async fn update_vpc_peering(
179        &self,
180        subscription_id: i32,
181        peering_id: i32,
182        request: &VpcPeeringUpdateAwsRequest,
183    ) -> crate::Result<crate::types::TaskStateUpdate> {
184        let create_request = VpcPeeringCreateRequest {
185            provider: None,
186            command_type: request.command_type.clone(),
187            vpc_cidr: request.vpc_cidr.clone(),
188            vpc_cidrs: request.vpc_cidrs.clone(),
189            ..Default::default()
190        };
191        self.vpc_peering
192            .update(subscription_id, peering_id, &create_request)
193            .await
194    }
195
196    // ========================================================================
197    // PSC (Private Service Connect) delegation methods
198    // ========================================================================
199
200    /// Get the Private Service Connect service for a Pro subscription.
201    ///
202    /// `GET /subscriptions/{subscriptionId}/private-service-connect`.
203    /// Delegates to [`PscHandler::get_service`].
204    ///
205    /// # Errors
206    ///
207    /// Returns [`crate::CloudError`] for transport, auth, or 4xx/5xx
208    /// responses. `NotFound` is returned when no PSC service is configured.
209    pub async fn get_psc_service(
210        &self,
211        subscription_id: i32,
212    ) -> crate::Result<crate::types::TaskStateUpdate> {
213        self.psc.get_service(subscription_id).await
214    }
215
216    /// Create a Private Service Connect service on a Pro subscription.
217    ///
218    /// `POST /subscriptions/{subscriptionId}/private-service-connect`.
219    /// Delegates to [`PscHandler::create_service`].
220    ///
221    /// # Errors
222    ///
223    /// Returns [`crate::CloudError`] for transport, auth, or 4xx/5xx
224    /// responses.
225    pub async fn create_psc_service(
226        &self,
227        subscription_id: i32,
228    ) -> crate::Result<crate::types::TaskStateUpdate> {
229        self.psc.create_service(subscription_id).await
230    }
231
232    /// Delete the Private Service Connect service for a Pro subscription.
233    ///
234    /// `DELETE /subscriptions/{subscriptionId}/private-service-connect`.
235    /// Delegates to [`PscHandler::delete_service`]. The deletion is
236    /// asynchronous; the returned
237    /// [`TaskStateUpdate`](crate::types::TaskStateUpdate) carries a `task_id`
238    /// that can be polled to completion.
239    ///
240    /// # Errors
241    ///
242    /// Returns [`crate::CloudError`] for non-2xx responses.
243    pub async fn delete_psc_service(
244        &self,
245        subscription_id: i32,
246    ) -> crate::Result<crate::types::TaskStateUpdate> {
247        self.psc.delete_service(subscription_id).await
248    }
249
250    /// Create a PSC endpoint under the subscription's PSC service.
251    ///
252    /// `POST /subscriptions/{subscriptionId}/private-service-connect/.../endpoints`.
253    /// Delegates to [`PscHandler::create_endpoint`].
254    ///
255    /// # Errors
256    ///
257    /// Returns [`crate::CloudError`] for transport, auth, or 4xx/5xx
258    /// responses; `BadRequest` if the endpoint payload is malformed.
259    pub async fn create_psc_endpoint(
260        &self,
261        subscription_id: i32,
262        psc_service_id: i32,
263        request: &PscEndpointUpdateRequest,
264    ) -> crate::Result<crate::types::TaskStateUpdate> {
265        self.psc
266            .create_endpoint(subscription_id, psc_service_id, request)
267            .await
268    }
269
270    // ========================================================================
271    // Transit Gateway delegation methods
272    // ========================================================================
273
274    /// List Transit Gateway attachments for a Pro subscription.
275    ///
276    /// `GET /subscriptions/{subscriptionId}/transitGateways`. Delegates to
277    /// [`TransitGatewayHandler::get_attachments`].
278    ///
279    /// # Errors
280    ///
281    /// Returns [`crate::CloudError`] for transport, auth, or 4xx/5xx
282    /// responses.
283    pub async fn get_tgws(
284        &self,
285        subscription_id: i32,
286    ) -> crate::Result<crate::types::TaskStateUpdate> {
287        self.transit_gateway.get_attachments(subscription_id).await
288    }
289
290    /// Create a Transit Gateway attachment for the given TGW ID.
291    ///
292    /// `POST /subscriptions/{subscriptionId}/transitGateways/{tgwId}`.
293    /// Delegates to [`TransitGatewayHandler::create_attachment_with_id`].
294    ///
295    /// # Errors
296    ///
297    /// Returns [`crate::CloudError`] for transport, auth, or 4xx/5xx
298    /// responses; `BadRequest` if `tgw_id` is not in a valid AWS format.
299    pub async fn create_tgw_attachment(
300        &self,
301        subscription_id: i32,
302        tgw_id: &str,
303    ) -> crate::Result<crate::types::TaskStateUpdate> {
304        self.transit_gateway
305            .create_attachment_with_id(subscription_id, tgw_id)
306            .await
307    }
308
309    /// Delete a Transit Gateway attachment by attachment ID.
310    ///
311    /// `DELETE /subscriptions/{subscriptionId}/transitGateways/{tgwId}`.
312    /// Delegates to [`TransitGatewayHandler::delete_attachment`]. The deletion
313    /// is asynchronous; the returned
314    /// [`TaskStateUpdate`](crate::types::TaskStateUpdate) carries a `task_id`
315    /// that can be polled to completion.
316    ///
317    /// # Errors
318    ///
319    /// Returns [`crate::CloudError`] for non-2xx responses; `NotFound` if
320    /// the attachment does not exist.
321    pub async fn delete_tgw_attachment(
322        &self,
323        subscription_id: i32,
324        attachment_id: i32,
325    ) -> crate::Result<crate::types::TaskStateUpdate> {
326        self.transit_gateway
327            .delete_attachment(subscription_id, attachment_id.to_string())
328            .await
329    }
330
331    /// Update the CIDR list on a Transit Gateway attachment.
332    ///
333    /// `PUT /subscriptions/{subscriptionId}/transitGateways/{tgwId}/attachment`.
334    /// Translates [`TgwUpdateCidrsRequest`] (which carries CIDR-with-status
335    /// entries) into the [`TgwAttachmentRequest`] the underlying handler
336    /// expects and delegates to
337    /// [`TransitGatewayHandler::update_attachment_cidrs`]. Only the
338    /// `cidr_address` portion of each CIDR entry is forwarded.
339    ///
340    /// # Errors
341    ///
342    /// Returns [`crate::CloudError`] for transport, auth, or 4xx/5xx
343    /// responses; `BadRequest` if any CIDR string is not in valid form.
344    pub async fn update_tgw_cidrs(
345        &self,
346        subscription_id: i32,
347        attachment_id: &str,
348        request: &TgwUpdateCidrsRequest,
349    ) -> crate::Result<crate::types::TaskStateUpdate> {
350        let attachment_request = TgwAttachmentRequest {
351            aws_account_id: None,
352            tgw_id: None,
353            cidrs: request.cidrs.as_ref().map(|cidrs| {
354                cidrs
355                    .iter()
356                    .filter_map(|c| c.cidr_address.clone())
357                    .collect()
358            }),
359        };
360        self.transit_gateway
361            .update_attachment_cidrs(
362                subscription_id,
363                attachment_id.to_string(),
364                &attachment_request,
365            )
366            .await
367    }
368
369    // ========================================================================
370    // Backward-compatibility shims
371    // ========================================================================
372
373    /// Update a PSC endpoint by endpoint ID. Backward-compatibility wrapper
374    /// over [`PscHandler::update_endpoint`].
375    ///
376    /// `PUT /subscriptions/{subscriptionId}/private-service-connect/.../endpoints/{endpointId}`.
377    ///
378    /// # Errors
379    ///
380    /// Returns [`crate::CloudError`] for transport, auth, or 4xx/5xx
381    /// responses.
382    pub async fn update_psc_service_endpoint(
383        &self,
384        subscription_id: i32,
385        psc_service_id: i32,
386        endpoint_id: i32,
387        request: &PscEndpointUpdateRequest,
388    ) -> crate::Result<crate::types::TaskStateUpdate> {
389        self.psc
390            .update_endpoint(subscription_id, psc_service_id, endpoint_id, request)
391            .await
392    }
393
394    /// Alias for [`Self::update_tgw_cidrs`] retained for backward
395    /// compatibility. New code should call `update_tgw_cidrs` directly.
396    ///
397    /// # Errors
398    ///
399    /// Forwards any [`crate::CloudError`] from the wrapped call.
400    pub async fn update_tgw_attachment_cidrs(
401        &self,
402        subscription_id: i32,
403        attachment_id: &str,
404        request: &TgwUpdateCidrsRequest,
405    ) -> crate::Result<crate::types::TaskStateUpdate> {
406        self.update_tgw_cidrs(subscription_id, attachment_id, request)
407            .await
408    }
409
410    // ========================================================================
411    // Simplified aliases
412    // ========================================================================
413
414    /// List Transit Gateway attachments for a Pro subscription (simplified).
415    ///
416    /// Preferred alias for [`get_tgws`](Self::get_tgws), whose name is
417    /// ambiguous. `get_tgws` is retained as a backward-compatibility shim.
418    ///
419    /// `GET /subscriptions/{subscriptionId}/transitGateways`.
420    ///
421    /// # Errors
422    ///
423    /// Returns [`crate::CloudError`] for transport, auth, or 4xx/5xx
424    /// responses.
425    ///
426    /// # Example
427    ///
428    /// ```no_run
429    /// use redis_cloud::{CloudClient, ConnectivityHandler};
430    ///
431    /// # async fn example() -> redis_cloud::Result<()> {
432    /// let client = CloudClient::builder()
433    ///     .api_key("your-api-key")
434    ///     .api_secret("your-api-secret")
435    ///     .build()?;
436    ///
437    /// let connectivity = ConnectivityHandler::new(client);
438    /// let attachments = connectivity.list_tgw_attachments(123).await?;
439    /// # Ok(())
440    /// # }
441    /// ```
442    pub async fn list_tgw_attachments(
443        &self,
444        subscription_id: i32,
445    ) -> crate::Result<crate::types::TaskStateUpdate> {
446        self.get_tgws(subscription_id).await
447    }
448}