lmrc-cloudflare 0.3.16

Cloudflare API client library for the LMRC Stack - comprehensive DNS, zones, and cache management with automatic retry logic
Documentation
//! Cache purging for Cloudflare.
//!
//! This module provides cache purging capabilities including:
//! - Purge everything
//! - Purge by URLs
//! - Purge by tags
//! - Purge by hosts
//! - Purge by prefixes

use crate::client::CloudflareClient;
use crate::error::Result;
use serde::{Deserialize, Serialize};

/// Service for purging cache.
///
/// Obtain an instance via [`CloudflareClient::cache()`].
#[derive(Clone)]
pub struct CacheService {
    client: CloudflareClient,
}

impl CacheService {
    /// Create a new cache service (internal use).
    pub(crate) fn new(client: CloudflareClient) -> Self {
        Self { client }
    }

    /// Purge all cached content for a zone.
    ///
    /// **Warning:** This will purge everything from the cache and may significantly
    /// increase load on your origin server.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_cloudflare::CloudflareClient;
    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
    /// client.cache()
    ///     .purge_everything("zone_id")
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn purge_everything(&self, zone_id: impl Into<String>) -> Result<PurgeResponse> {
        let zone_id = zone_id.into();
        let payload = serde_json::json!({
            "purge_everything": true
        });

        let response = self
            .client
            .post(&format!("/zones/{}/purge_cache", zone_id), &payload)
            .await?;

        CloudflareClient::handle_response(response).await
    }

    /// Purge specific URLs from cache.
    ///
    /// You can purge up to 30 URLs at a time.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_cloudflare::CloudflareClient;
    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
    /// client.cache()
    ///     .purge_urls("zone_id")
    ///     .urls(vec![
    ///         "https://example.com/page1",
    ///         "https://example.com/page2",
    ///     ])
    ///     .send()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn purge_urls(&self, zone_id: impl Into<String>) -> PurgeUrlsRequest {
        PurgeUrlsRequest {
            service: self.clone(),
            zone_id: zone_id.into(),
            files: Vec::new(),
        }
    }

    /// Purge cache by cache tags.
    ///
    /// Cache tags are used to identify cached content. You can purge up to 30 tags at a time.
    /// This feature requires an Enterprise plan.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_cloudflare::CloudflareClient;
    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
    /// client.cache()
    ///     .purge_tags("zone_id")
    ///     .tags(vec!["product", "blog"])
    ///     .send()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn purge_tags(&self, zone_id: impl Into<String>) -> PurgeTagsRequest {
        PurgeTagsRequest {
            service: self.clone(),
            zone_id: zone_id.into(),
            tags: Vec::new(),
        }
    }

    /// Purge cache by hosts.
    ///
    /// Purge all cached content for specific hosts. You can purge up to 30 hosts at a time.
    /// This feature requires an Enterprise plan.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_cloudflare::CloudflareClient;
    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
    /// client.cache()
    ///     .purge_hosts("zone_id")
    ///     .hosts(vec!["www.example.com", "api.example.com"])
    ///     .send()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn purge_hosts(&self, zone_id: impl Into<String>) -> PurgeHostsRequest {
        PurgeHostsRequest {
            service: self.clone(),
            zone_id: zone_id.into(),
            hosts: Vec::new(),
        }
    }

    /// Purge cache by prefixes.
    ///
    /// Purge all cached content with URLs that begin with specific prefixes.
    /// You can purge up to 30 prefixes at a time.
    /// This feature requires an Enterprise plan.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_cloudflare::CloudflareClient;
    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
    /// client.cache()
    ///     .purge_prefixes("zone_id")
    ///     .prefixes(vec!["example.com/images/", "example.com/videos/"])
    ///     .send()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn purge_prefixes(&self, zone_id: impl Into<String>) -> PurgePrefixesRequest {
        PurgePrefixesRequest {
            service: self.clone(),
            zone_id: zone_id.into(),
            prefixes: Vec::new(),
        }
    }
}

/// Response from a cache purge operation.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PurgeResponse {
    /// Purge operation ID
    pub id: String,
}

/// Request builder for purging URLs.
pub struct PurgeUrlsRequest {
    service: CacheService,
    zone_id: String,
    files: Vec<String>,
}

impl PurgeUrlsRequest {
    /// Set the URLs to purge.
    pub fn urls<I, S>(mut self, urls: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.files = urls.into_iter().map(|s| s.into()).collect();
        self
    }

    /// Add a single URL to purge.
    pub fn add_url(mut self, url: impl Into<String>) -> Self {
        self.files.push(url.into());
        self
    }

    /// Send the request.
    pub async fn send(self) -> Result<PurgeResponse> {
        if self.files.is_empty() {
            return Err(crate::error::Error::InvalidInput(
                "At least one URL is required".to_string(),
            ));
        }

        if self.files.len() > 30 {
            return Err(crate::error::Error::InvalidInput(
                "Cannot purge more than 30 URLs at a time".to_string(),
            ));
        }

        let payload = serde_json::json!({
            "files": self.files
        });

        let response = self
            .service
            .client
            .post(&format!("/zones/{}/purge_cache", self.zone_id), &payload)
            .await?;

        CloudflareClient::handle_response(response).await
    }
}

/// Request builder for purging by tags.
pub struct PurgeTagsRequest {
    service: CacheService,
    zone_id: String,
    tags: Vec<String>,
}

impl PurgeTagsRequest {
    /// Set the tags to purge.
    pub fn tags<I, S>(mut self, tags: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.tags = tags.into_iter().map(|s| s.into()).collect();
        self
    }

    /// Add a single tag to purge.
    pub fn add_tag(mut self, tag: impl Into<String>) -> Self {
        self.tags.push(tag.into());
        self
    }

    /// Send the request.
    pub async fn send(self) -> Result<PurgeResponse> {
        if self.tags.is_empty() {
            return Err(crate::error::Error::InvalidInput(
                "At least one tag is required".to_string(),
            ));
        }

        if self.tags.len() > 30 {
            return Err(crate::error::Error::InvalidInput(
                "Cannot purge more than 30 tags at a time".to_string(),
            ));
        }

        let payload = serde_json::json!({
            "tags": self.tags
        });

        let response = self
            .service
            .client
            .post(&format!("/zones/{}/purge_cache", self.zone_id), &payload)
            .await?;

        CloudflareClient::handle_response(response).await
    }
}

/// Request builder for purging by hosts.
pub struct PurgeHostsRequest {
    service: CacheService,
    zone_id: String,
    hosts: Vec<String>,
}

impl PurgeHostsRequest {
    /// Set the hosts to purge.
    pub fn hosts<I, S>(mut self, hosts: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.hosts = hosts.into_iter().map(|s| s.into()).collect();
        self
    }

    /// Add a single host to purge.
    pub fn add_host(mut self, host: impl Into<String>) -> Self {
        self.hosts.push(host.into());
        self
    }

    /// Send the request.
    pub async fn send(self) -> Result<PurgeResponse> {
        if self.hosts.is_empty() {
            return Err(crate::error::Error::InvalidInput(
                "At least one host is required".to_string(),
            ));
        }

        if self.hosts.len() > 30 {
            return Err(crate::error::Error::InvalidInput(
                "Cannot purge more than 30 hosts at a time".to_string(),
            ));
        }

        let payload = serde_json::json!({
            "hosts": self.hosts
        });

        let response = self
            .service
            .client
            .post(&format!("/zones/{}/purge_cache", self.zone_id), &payload)
            .await?;

        CloudflareClient::handle_response(response).await
    }
}

/// Request builder for purging by prefixes.
pub struct PurgePrefixesRequest {
    service: CacheService,
    zone_id: String,
    prefixes: Vec<String>,
}

impl PurgePrefixesRequest {
    /// Set the prefixes to purge.
    pub fn prefixes<I, S>(mut self, prefixes: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.prefixes = prefixes.into_iter().map(|s| s.into()).collect();
        self
    }

    /// Add a single prefix to purge.
    pub fn add_prefix(mut self, prefix: impl Into<String>) -> Self {
        self.prefixes.push(prefix.into());
        self
    }

    /// Send the request.
    pub async fn send(self) -> Result<PurgeResponse> {
        if self.prefixes.is_empty() {
            return Err(crate::error::Error::InvalidInput(
                "At least one prefix is required".to_string(),
            ));
        }

        if self.prefixes.len() > 30 {
            return Err(crate::error::Error::InvalidInput(
                "Cannot purge more than 30 prefixes at a time".to_string(),
            ));
        }

        let payload = serde_json::json!({
            "prefixes": self.prefixes
        });

        let response = self
            .service
            .client
            .post(&format!("/zones/{}/purge_cache", self.zone_id), &payload)
            .await?;

        CloudflareClient::handle_response(response).await
    }
}