diff --git a/cloudflare/src/endpoints/mod.rs b/cloudflare/src/endpoints/mod.rs index f590cc90..9d2508a9 100644 --- a/cloudflare/src/endpoints/mod.rs +++ b/cloudflare/src/endpoints/mod.rs @@ -10,6 +10,7 @@ pub mod cfd_tunnel; pub mod dns; pub mod load_balancing; pub mod r2; +pub mod warp_connector_tunnel; pub mod workers; pub mod workerskv; pub mod zones; diff --git a/cloudflare/src/endpoints/warp_connector_tunnel/create_tunnel.rs b/cloudflare/src/endpoints/warp_connector_tunnel/create_tunnel.rs new file mode 100644 index 00000000..88d5e713 --- /dev/null +++ b/cloudflare/src/endpoints/warp_connector_tunnel/create_tunnel.rs @@ -0,0 +1,43 @@ +use serde::Serialize; +use serde_with::serde_as; + +use crate::framework::response::ApiSuccess; +use crate::{ + endpoints::warp_connector_tunnel::data_structures::WarpConnectorTunnel, + framework::endpoint::{EndpointSpec, Method, RequestBody}, +}; + +/// Create a Warp Connector tunnel +/// +#[derive(Debug)] +pub struct CreateTunnel<'a> { + pub account_identifier: &'a str, + pub params: Params<'a>, +} + +impl EndpointSpec for CreateTunnel<'_> { + type JsonResponse = WarpConnectorTunnel; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::POST + } + fn path(&self) -> String { + format!("accounts/{}/warp_connector", self.account_identifier) + } + #[inline] + fn body(&'_ self) -> Option> { + let body = serde_json::to_string(&self.params).unwrap(); + Some(RequestBody::Json(body)) + } +} + +/// Params for creating a Warp Connector Tunnel +#[serde_as] +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Debug)] +pub struct Params<'a> { + /// The name for the Tunnel to be created. It must be unique within the account. + pub name: &'a str, + pub ha: bool, +} diff --git a/cloudflare/src/endpoints/warp_connector_tunnel/data_structures.rs b/cloudflare/src/endpoints/warp_connector_tunnel/data_structures.rs new file mode 100644 index 00000000..fc113114 --- /dev/null +++ b/cloudflare/src/endpoints/warp_connector_tunnel/data_structures.rs @@ -0,0 +1,153 @@ +use chrono::{offset::Utc, DateTime}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::endpoints::cfd_tunnel::{ActiveConnection, TunnelStatusType}; +use crate::framework::response::ApiResult; + +/// A Warp Connector Tunnel. +/// +/// Mirrors the JSON returned by the Cloudflare API when creating or fetching a +/// warp_connector tunnel, including its credentials file and connection token. +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct WarpConnectorTunnel { + pub id: Uuid, + pub account_tag: String, + pub created_at: DateTime, + pub deleted_at: Option>, + pub name: String, + pub connections: Vec, + pub conns_active_at: Option>, + pub conns_inactive_at: Option>, + pub tun_type: String, + pub metadata: serde_json::Value, + pub status: TunnelStatusType, + /// Present on create responses; absent on delete responses. + #[serde(flatten)] + pub credentials: Option, +} + +/// Credentials bundle returned only when a Warp Connector Tunnel is created. +/// +/// Flattened into [`WarpConnectorTunnel`] so it inlines `credentials_file` and +/// `token` at the top level of the JSON. +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct WarpConnectorCredentials { + pub credentials_file: WarpConnectorCredentialsFile, + pub token: String, +} + +/// Credentials file contents for a Warp Connector Tunnel. +/// +/// Field names follow the API's PascalCase convention. +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct WarpConnectorCredentialsFile { + #[serde(rename = "AccountTag")] + pub account_tag: String, + #[serde(rename = "TunnelID")] + pub tunnel_id: Uuid, + #[serde(rename = "TunnelName")] + pub tunnel_name: String, + #[serde(rename = "TunnelSecret")] + pub tunnel_secret: String, +} + +impl ApiResult for WarpConnectorTunnel {} +impl ApiResult for Vec {} + +/// A Warp Connector client maintaining a connection to a Cloudflare data center. +/// +/// Returned by both the connections list and the single-connector get endpoints. +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct WarpConnector { + pub id: Option, + pub arch: Option, + pub conns: Option>, + pub features: Option>, + pub ha_status: Option, + pub run_at: Option>, + pub version: Option, +} + +/// A single Warp Connector connection between a client and Cloudflare's edge. +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct WarpConnectorConn { + pub id: Option, + pub client_id: Option, + pub client_version: Option, + pub colo_name: Option, + pub opened_at: Option>, + pub origin_ip: Option, +} + +/// HA status reported by a Warp Connector client. +#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[serde(rename_all = "lowercase")] +pub enum WarpConnectorHaStatus { + Offline, + Passive, + Active, +} + +impl ApiResult for WarpConnector {} +impl ApiResult for Vec {} + +/// HA configuration for a Warp Connector Tunnel. +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct WarpConnectorHaConfiguration { + /// Monotonically increasing configuration version, incremented on each PUT. + pub configuration_version: u64, + pub created_at: DateTime, + pub ha_mode: WarpConnectorHaMode, + pub tunnel_id: Uuid, + /// Provider-specific configuration; present for `aws` and `local` modes. + #[serde(default)] + pub config: Option, + pub updated_at: Option>, +} + +/// HA mode for a Warp Connector tunnel. +#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[serde(rename_all = "lowercase")] +pub enum WarpConnectorHaMode { + /// HA enabled but no provider configured yet. + None, + /// HA explicitly turned off. + Disabled, + /// AWS ENI move-based failover. + Aws, + /// Local VIP-based failover. + Local, +} + +/// Provider-specific HA configuration payload, discriminated by shape. +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[serde(untagged)] +pub enum WarpConnectorProviderConfiguration { + Aws(WarpConnectorAwsProviderConfiguration), + Local(WarpConnectorHaLocalProviderConfiguration), +} + +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct WarpConnectorAwsProviderConfiguration { + /// Floating Network Resource ID — the secondary ENI moved between nodes + /// on failover. + pub fnr_id: String, +} + +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct WarpConnectorHaLocalProviderConfiguration { + /// VIPs to assign on the CloudflareWARP interface. + pub vips: Vec, + /// VIPs to clean up on demotion or version drift. + #[serde(skip_serializing_if = "Option::is_none")] + pub vips_previous: Option>, +} + +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct WarpConnectorVip { + /// Virtual IP address (IPv4 or IPv6). + pub address: String, +} + +impl ApiResult for WarpConnectorHaConfiguration {} diff --git a/cloudflare/src/endpoints/warp_connector_tunnel/delete_tunnel.rs b/cloudflare/src/endpoints/warp_connector_tunnel/delete_tunnel.rs new file mode 100644 index 00000000..1291a25e --- /dev/null +++ b/cloudflare/src/endpoints/warp_connector_tunnel/delete_tunnel.rs @@ -0,0 +1,39 @@ +use crate::endpoints::warp_connector_tunnel::data_structures::WarpConnectorTunnel; +use crate::framework::endpoint::{serialize_query, EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; +use serde::Serialize; + +/// Delete a WARP Connector tunnel +/// +#[derive(Debug)] +pub struct DeleteTunnel<'a> { + pub account_identifier: &'a str, + pub tunnel_id: &'a str, + pub params: Params, +} + +impl EndpointSpec for DeleteTunnel<'_> { + type JsonResponse = WarpConnectorTunnel; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::DELETE + } + fn path(&self) -> String { + format!( + "accounts/{}/warp_connector/{}", + self.account_identifier, self.tunnel_id + ) + } + #[inline] + fn query(&self) -> Option { + serialize_query(&self.params) + } +} + +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Debug, Default)] +pub struct Params { + // should delete tunnel connections if any exists + pub cascade: bool, +} diff --git a/cloudflare/src/endpoints/warp_connector_tunnel/get_connector.rs b/cloudflare/src/endpoints/warp_connector_tunnel/get_connector.rs new file mode 100644 index 00000000..a61ee264 --- /dev/null +++ b/cloudflare/src/endpoints/warp_connector_tunnel/get_connector.rs @@ -0,0 +1,28 @@ +use crate::endpoints::warp_connector_tunnel::data_structures::WarpConnector; +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// Fetch connector + connection details for a single Warp Connector client. +/// +#[derive(Debug)] +pub struct GetConnector<'a> { + pub account_identifier: &'a str, + pub tunnel_id: &'a str, + pub connector_id: &'a str, +} + +impl EndpointSpec for GetConnector<'_> { + type JsonResponse = WarpConnector; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::GET + } + + fn path(&self) -> String { + format!( + "accounts/{}/warp_connector/{}/connectors/{}", + self.account_identifier, self.tunnel_id, self.connector_id + ) + } +} diff --git a/cloudflare/src/endpoints/warp_connector_tunnel/get_ha_configuration.rs b/cloudflare/src/endpoints/warp_connector_tunnel/get_ha_configuration.rs new file mode 100644 index 00000000..13bd9ea9 --- /dev/null +++ b/cloudflare/src/endpoints/warp_connector_tunnel/get_ha_configuration.rs @@ -0,0 +1,27 @@ +use crate::endpoints::warp_connector_tunnel::data_structures::WarpConnectorHaConfiguration; +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// Get the HA configuration for a Warp Connector Tunnel. +/// +#[derive(Debug)] +pub struct GetHaConfiguration<'a> { + pub account_identifier: &'a str, + pub tunnel_id: &'a str, +} + +impl EndpointSpec for GetHaConfiguration<'_> { + type JsonResponse = WarpConnectorHaConfiguration; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::GET + } + + fn path(&self) -> String { + format!( + "accounts/{}/warp_connector/{}/configurations", + self.account_identifier, self.tunnel_id + ) + } +} diff --git a/cloudflare/src/endpoints/warp_connector_tunnel/get_token.rs b/cloudflare/src/endpoints/warp_connector_tunnel/get_token.rs new file mode 100644 index 00000000..344268fd --- /dev/null +++ b/cloudflare/src/endpoints/warp_connector_tunnel/get_token.rs @@ -0,0 +1,37 @@ +use serde::{Deserialize, Serialize}; + +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::{ApiResult, ApiSuccess}; + +/// Fetch the token used to authenticate a Warp Connector Tunnel. +/// +#[derive(Debug)] +pub struct GetToken<'a> { + pub account_identifier: &'a str, + pub tunnel_id: &'a str, +} + +impl EndpointSpec for GetToken<'_> { + type JsonResponse = WarpConnectorToken; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::GET + } + + fn path(&self) -> String { + format!( + "accounts/{}/warp_connector/{}/token", + self.account_identifier, self.tunnel_id + ) + } +} + +/// Tunnel token returned by the API. The raw JSON value is a plain string +/// (e.g. `"eyJhIjoi…"`) but it is wrapped here so the crate's `ApiResult` +/// trait can be implemented without violating Rust's orphan rules. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct WarpConnectorToken(pub String); + +impl ApiResult for WarpConnectorToken {} diff --git a/cloudflare/src/endpoints/warp_connector_tunnel/get_tunnel.rs b/cloudflare/src/endpoints/warp_connector_tunnel/get_tunnel.rs new file mode 100644 index 00000000..3984d7ca --- /dev/null +++ b/cloudflare/src/endpoints/warp_connector_tunnel/get_tunnel.rs @@ -0,0 +1,27 @@ +use crate::endpoints::warp_connector_tunnel::data_structures::WarpConnectorTunnel; +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// Fetch a single Warp Connector Tunnel. +/// +#[derive(Debug)] +pub struct GetTunnel<'a> { + pub account_identifier: &'a str, + pub tunnel_id: &'a str, +} + +impl EndpointSpec for GetTunnel<'_> { + type JsonResponse = WarpConnectorTunnel; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::GET + } + + fn path(&self) -> String { + format!( + "accounts/{}/warp_connector/{}", + self.account_identifier, self.tunnel_id + ) + } +} diff --git a/cloudflare/src/endpoints/warp_connector_tunnel/list_connections.rs b/cloudflare/src/endpoints/warp_connector_tunnel/list_connections.rs new file mode 100644 index 00000000..6ee5b044 --- /dev/null +++ b/cloudflare/src/endpoints/warp_connector_tunnel/list_connections.rs @@ -0,0 +1,27 @@ +use crate::endpoints::warp_connector_tunnel::data_structures::WarpConnector; +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// List active connections for a Warp Connector Tunnel. +/// +#[derive(Debug)] +pub struct ListConnections<'a> { + pub account_identifier: &'a str, + pub tunnel_id: &'a str, +} + +impl EndpointSpec for ListConnections<'_> { + type JsonResponse = Vec; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::GET + } + + fn path(&self) -> String { + format!( + "accounts/{}/warp_connector/{}/connections", + self.account_identifier, self.tunnel_id + ) + } +} diff --git a/cloudflare/src/endpoints/warp_connector_tunnel/list_tunnels.rs b/cloudflare/src/endpoints/warp_connector_tunnel/list_tunnels.rs new file mode 100644 index 00000000..dedd8fe2 --- /dev/null +++ b/cloudflare/src/endpoints/warp_connector_tunnel/list_tunnels.rs @@ -0,0 +1,54 @@ +use crate::endpoints::cfd_tunnel::TunnelStatusType; +use crate::endpoints::warp_connector_tunnel::data_structures::WarpConnectorTunnel; +use chrono::{DateTime, Utc}; +use serde::Serialize; + +use crate::framework::endpoint::{serialize_query, EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// List/search Warp Connector Tunnels in an account. +/// +#[derive(Debug)] +pub struct ListTunnels<'a> { + pub account_identifier: &'a str, + pub params: Params, +} + +impl EndpointSpec for ListTunnels<'_> { + type JsonResponse = Vec; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::GET + } + fn path(&self) -> String { + format!("accounts/{}/warp_connector", self.account_identifier) + } + #[inline] + fn query(&self) -> Option { + serialize_query(&self.params) + } +} + +/// Params for filtering listed Warp Connector tunnels. +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Debug, Default)] +pub struct Params { + pub name: Option, + pub uuid: Option, + pub is_deleted: Option, + pub status: Option, + pub existed_at: Option>, + pub was_active_at: Option>, + pub was_inactive_at: Option>, + pub include_prefix: Option, + pub exclude_prefix: Option, + #[serde(flatten)] + pub pagination_params: Option, +} + +#[derive(Serialize, Clone, Debug)] +pub struct PaginationParams { + pub page: u64, + pub per_page: u64, +} diff --git a/cloudflare/src/endpoints/warp_connector_tunnel/mod.rs b/cloudflare/src/endpoints/warp_connector_tunnel/mod.rs new file mode 100644 index 00000000..4da8962f --- /dev/null +++ b/cloudflare/src/endpoints/warp_connector_tunnel/mod.rs @@ -0,0 +1,12 @@ +pub mod create_tunnel; +mod data_structures; +pub mod delete_tunnel; +pub mod get_connector; +pub mod get_ha_configuration; +pub mod get_token; +pub mod get_tunnel; +pub mod list_connections; +pub mod list_tunnels; +pub mod trigger_failover; +pub mod update_ha_configuration; +pub mod update_tunnel; diff --git a/cloudflare/src/endpoints/warp_connector_tunnel/trigger_failover.rs b/cloudflare/src/endpoints/warp_connector_tunnel/trigger_failover.rs new file mode 100644 index 00000000..e28d8e9a --- /dev/null +++ b/cloudflare/src/endpoints/warp_connector_tunnel/trigger_failover.rs @@ -0,0 +1,53 @@ +use serde::{Deserialize, Serialize}; + +use crate::framework::endpoint::{EndpointSpec, Method, RequestBody}; +use crate::framework::response::{ApiResult, ApiSuccess}; + +/// Trigger a manual failover for a Warp Connector Tunnel, promoting a +/// specific client to be the active connector. The tunnel must be configured +/// for HA and the client must already be linked to it. +/// +#[derive(Debug)] +pub struct TriggerFailover<'a> { + pub account_identifier: &'a str, + pub tunnel_id: &'a str, + pub params: Params<'a>, +} + +impl EndpointSpec for TriggerFailover<'_> { + type JsonResponse = FailoverResponse; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::PUT + } + + fn path(&self) -> String { + format!( + "accounts/{}/warp_connector/{}/failover", + self.account_identifier, self.tunnel_id + ) + } + + #[inline] + fn body(&self) -> Option> { + let body = serde_json::to_string(&self.params).unwrap(); + Some(RequestBody::Json(body)) + } +} + +/// Params for triggering a manual failover. +#[derive(Serialize, Clone, Debug)] +pub struct Params<'a> { + /// UUID of the Cloudflare Tunnel connector to promote. + pub client_id: &'a str, +} + +/// The failover endpoint's `result` field is documented as `unknown` and the +/// example response is the empty object `{}`. Wrap a raw JSON value so the +/// caller can still inspect anything the API returns. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(transparent)] +pub struct FailoverResponse(pub serde_json::Value); + +impl ApiResult for FailoverResponse {} diff --git a/cloudflare/src/endpoints/warp_connector_tunnel/update_ha_configuration.rs b/cloudflare/src/endpoints/warp_connector_tunnel/update_ha_configuration.rs new file mode 100644 index 00000000..987e8a28 --- /dev/null +++ b/cloudflare/src/endpoints/warp_connector_tunnel/update_ha_configuration.rs @@ -0,0 +1,49 @@ +use serde::Serialize; + +use crate::endpoints::warp_connector_tunnel::data_structures::{ + WarpConnectorHaConfiguration, WarpConnectorHaMode, WarpConnectorProviderConfiguration, +}; +use crate::framework::endpoint::{EndpointSpec, Method, RequestBody}; +use crate::framework::response::ApiSuccess; + +/// Add or update the HA configuration for a Warp Connector Tunnel. +/// +#[derive(Debug)] +pub struct UpdateHaConfiguration<'a> { + pub account_identifier: &'a str, + pub tunnel_id: &'a str, + pub params: Params, +} + +impl EndpointSpec for UpdateHaConfiguration<'_> { + type JsonResponse = WarpConnectorHaConfiguration; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::PUT + } + + fn path(&self) -> String { + format!( + "accounts/{}/warp_connector/{}/configurations", + self.account_identifier, self.tunnel_id + ) + } + + #[inline] + fn body(&self) -> Option> { + let body = serde_json::to_string(&self.params).unwrap(); + Some(RequestBody::Json(body)) + } +} + +/// Params for updating the HA configuration. +/// +/// `config` is required for `aws` and `local` modes and must be omitted (or +/// `None`) for `none` and `disabled`. +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Debug)] +pub struct Params { + pub ha_mode: WarpConnectorHaMode, + pub config: Option, +} diff --git a/cloudflare/src/endpoints/warp_connector_tunnel/update_tunnel.rs b/cloudflare/src/endpoints/warp_connector_tunnel/update_tunnel.rs new file mode 100644 index 00000000..b6496ae7 --- /dev/null +++ b/cloudflare/src/endpoints/warp_connector_tunnel/update_tunnel.rs @@ -0,0 +1,49 @@ +use serde::Serialize; +use serde_with::serde_as; + +use crate::framework::response::ApiSuccess; +use crate::{ + endpoints::warp_connector_tunnel::data_structures::WarpConnectorTunnel, + framework::endpoint::{EndpointSpec, Method, RequestBody}, +}; + +/// Update a Warp Connector tunnel +/// +#[derive(Debug)] +pub struct UpdateTunnel<'a> { + pub account_identifier: &'a str, + pub tunnel_id: &'a str, + pub params: Params<'a>, +} + +impl EndpointSpec for UpdateTunnel<'_> { + type JsonResponse = WarpConnectorTunnel; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::PATCH + } + fn path(&self) -> String { + format!( + "accounts/{}/warp_connector/{}", + self.account_identifier, self.tunnel_id + ) + } + #[inline] + fn body(&'_ self) -> Option> { + let body = serde_json::to_string(&self.params).unwrap(); + Some(RequestBody::Json(body)) + } +} + +/// Params for updating a Warp Connector Tunnel +#[serde_as] +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Debug, Default)] +pub struct Params<'a> { + /// A user-friendly name for a tunnel. + pub name: Option<&'a str>, + /// Base64-encoded secret of at least 32 bytes used to authenticate a + /// locally-managed tunnel. + pub tunnel_secret: Option<&'a str>, +}