Skip to main content

lmrc_cloudflare/cache/
mod.rs

1//! Cache purging for Cloudflare.
2//!
3//! This module provides cache purging capabilities including:
4//! - Purge everything
5//! - Purge by URLs
6//! - Purge by tags
7//! - Purge by hosts
8//! - Purge by prefixes
9
10use crate::client::CloudflareClient;
11use crate::error::Result;
12use serde::{Deserialize, Serialize};
13
14/// Service for purging cache.
15///
16/// Obtain an instance via [`CloudflareClient::cache()`].
17#[derive(Clone)]
18pub struct CacheService {
19    client: CloudflareClient,
20}
21
22impl CacheService {
23    /// Create a new cache service (internal use).
24    pub(crate) fn new(client: CloudflareClient) -> Self {
25        Self { client }
26    }
27
28    /// Purge all cached content for a zone.
29    ///
30    /// **Warning:** This will purge everything from the cache and may significantly
31    /// increase load on your origin server.
32    ///
33    /// # Examples
34    ///
35    /// ```no_run
36    /// # use lmrc_cloudflare::CloudflareClient;
37    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
38    /// client.cache()
39    ///     .purge_everything("zone_id")
40    ///     .await?;
41    /// # Ok(())
42    /// # }
43    /// ```
44    pub async fn purge_everything(&self, zone_id: impl Into<String>) -> Result<PurgeResponse> {
45        let zone_id = zone_id.into();
46        let payload = serde_json::json!({
47            "purge_everything": true
48        });
49
50        let response = self
51            .client
52            .post(&format!("/zones/{}/purge_cache", zone_id), &payload)
53            .await?;
54
55        CloudflareClient::handle_response(response).await
56    }
57
58    /// Purge specific URLs from cache.
59    ///
60    /// You can purge up to 30 URLs at a time.
61    ///
62    /// # Examples
63    ///
64    /// ```no_run
65    /// # use lmrc_cloudflare::CloudflareClient;
66    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
67    /// client.cache()
68    ///     .purge_urls("zone_id")
69    ///     .urls(vec![
70    ///         "https://example.com/page1",
71    ///         "https://example.com/page2",
72    ///     ])
73    ///     .send()
74    ///     .await?;
75    /// # Ok(())
76    /// # }
77    /// ```
78    pub fn purge_urls(&self, zone_id: impl Into<String>) -> PurgeUrlsRequest {
79        PurgeUrlsRequest {
80            service: self.clone(),
81            zone_id: zone_id.into(),
82            files: Vec::new(),
83        }
84    }
85
86    /// Purge cache by cache tags.
87    ///
88    /// Cache tags are used to identify cached content. You can purge up to 30 tags at a time.
89    /// This feature requires an Enterprise plan.
90    ///
91    /// # Examples
92    ///
93    /// ```no_run
94    /// # use lmrc_cloudflare::CloudflareClient;
95    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
96    /// client.cache()
97    ///     .purge_tags("zone_id")
98    ///     .tags(vec!["product", "blog"])
99    ///     .send()
100    ///     .await?;
101    /// # Ok(())
102    /// # }
103    /// ```
104    pub fn purge_tags(&self, zone_id: impl Into<String>) -> PurgeTagsRequest {
105        PurgeTagsRequest {
106            service: self.clone(),
107            zone_id: zone_id.into(),
108            tags: Vec::new(),
109        }
110    }
111
112    /// Purge cache by hosts.
113    ///
114    /// Purge all cached content for specific hosts. You can purge up to 30 hosts at a time.
115    /// This feature requires an Enterprise plan.
116    ///
117    /// # Examples
118    ///
119    /// ```no_run
120    /// # use lmrc_cloudflare::CloudflareClient;
121    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
122    /// client.cache()
123    ///     .purge_hosts("zone_id")
124    ///     .hosts(vec!["www.example.com", "api.example.com"])
125    ///     .send()
126    ///     .await?;
127    /// # Ok(())
128    /// # }
129    /// ```
130    pub fn purge_hosts(&self, zone_id: impl Into<String>) -> PurgeHostsRequest {
131        PurgeHostsRequest {
132            service: self.clone(),
133            zone_id: zone_id.into(),
134            hosts: Vec::new(),
135        }
136    }
137
138    /// Purge cache by prefixes.
139    ///
140    /// Purge all cached content with URLs that begin with specific prefixes.
141    /// You can purge up to 30 prefixes at a time.
142    /// This feature requires an Enterprise plan.
143    ///
144    /// # Examples
145    ///
146    /// ```no_run
147    /// # use lmrc_cloudflare::CloudflareClient;
148    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
149    /// client.cache()
150    ///     .purge_prefixes("zone_id")
151    ///     .prefixes(vec!["example.com/images/", "example.com/videos/"])
152    ///     .send()
153    ///     .await?;
154    /// # Ok(())
155    /// # }
156    /// ```
157    pub fn purge_prefixes(&self, zone_id: impl Into<String>) -> PurgePrefixesRequest {
158        PurgePrefixesRequest {
159            service: self.clone(),
160            zone_id: zone_id.into(),
161            prefixes: Vec::new(),
162        }
163    }
164}
165
166/// Response from a cache purge operation.
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct PurgeResponse {
169    /// Purge operation ID
170    pub id: String,
171}
172
173/// Request builder for purging URLs.
174pub struct PurgeUrlsRequest {
175    service: CacheService,
176    zone_id: String,
177    files: Vec<String>,
178}
179
180impl PurgeUrlsRequest {
181    /// Set the URLs to purge.
182    pub fn urls<I, S>(mut self, urls: I) -> Self
183    where
184        I: IntoIterator<Item = S>,
185        S: Into<String>,
186    {
187        self.files = urls.into_iter().map(|s| s.into()).collect();
188        self
189    }
190
191    /// Add a single URL to purge.
192    pub fn add_url(mut self, url: impl Into<String>) -> Self {
193        self.files.push(url.into());
194        self
195    }
196
197    /// Send the request.
198    pub async fn send(self) -> Result<PurgeResponse> {
199        if self.files.is_empty() {
200            return Err(crate::error::Error::InvalidInput(
201                "At least one URL is required".to_string(),
202            ));
203        }
204
205        if self.files.len() > 30 {
206            return Err(crate::error::Error::InvalidInput(
207                "Cannot purge more than 30 URLs at a time".to_string(),
208            ));
209        }
210
211        let payload = serde_json::json!({
212            "files": self.files
213        });
214
215        let response = self
216            .service
217            .client
218            .post(&format!("/zones/{}/purge_cache", self.zone_id), &payload)
219            .await?;
220
221        CloudflareClient::handle_response(response).await
222    }
223}
224
225/// Request builder for purging by tags.
226pub struct PurgeTagsRequest {
227    service: CacheService,
228    zone_id: String,
229    tags: Vec<String>,
230}
231
232impl PurgeTagsRequest {
233    /// Set the tags to purge.
234    pub fn tags<I, S>(mut self, tags: I) -> Self
235    where
236        I: IntoIterator<Item = S>,
237        S: Into<String>,
238    {
239        self.tags = tags.into_iter().map(|s| s.into()).collect();
240        self
241    }
242
243    /// Add a single tag to purge.
244    pub fn add_tag(mut self, tag: impl Into<String>) -> Self {
245        self.tags.push(tag.into());
246        self
247    }
248
249    /// Send the request.
250    pub async fn send(self) -> Result<PurgeResponse> {
251        if self.tags.is_empty() {
252            return Err(crate::error::Error::InvalidInput(
253                "At least one tag is required".to_string(),
254            ));
255        }
256
257        if self.tags.len() > 30 {
258            return Err(crate::error::Error::InvalidInput(
259                "Cannot purge more than 30 tags at a time".to_string(),
260            ));
261        }
262
263        let payload = serde_json::json!({
264            "tags": self.tags
265        });
266
267        let response = self
268            .service
269            .client
270            .post(&format!("/zones/{}/purge_cache", self.zone_id), &payload)
271            .await?;
272
273        CloudflareClient::handle_response(response).await
274    }
275}
276
277/// Request builder for purging by hosts.
278pub struct PurgeHostsRequest {
279    service: CacheService,
280    zone_id: String,
281    hosts: Vec<String>,
282}
283
284impl PurgeHostsRequest {
285    /// Set the hosts to purge.
286    pub fn hosts<I, S>(mut self, hosts: I) -> Self
287    where
288        I: IntoIterator<Item = S>,
289        S: Into<String>,
290    {
291        self.hosts = hosts.into_iter().map(|s| s.into()).collect();
292        self
293    }
294
295    /// Add a single host to purge.
296    pub fn add_host(mut self, host: impl Into<String>) -> Self {
297        self.hosts.push(host.into());
298        self
299    }
300
301    /// Send the request.
302    pub async fn send(self) -> Result<PurgeResponse> {
303        if self.hosts.is_empty() {
304            return Err(crate::error::Error::InvalidInput(
305                "At least one host is required".to_string(),
306            ));
307        }
308
309        if self.hosts.len() > 30 {
310            return Err(crate::error::Error::InvalidInput(
311                "Cannot purge more than 30 hosts at a time".to_string(),
312            ));
313        }
314
315        let payload = serde_json::json!({
316            "hosts": self.hosts
317        });
318
319        let response = self
320            .service
321            .client
322            .post(&format!("/zones/{}/purge_cache", self.zone_id), &payload)
323            .await?;
324
325        CloudflareClient::handle_response(response).await
326    }
327}
328
329/// Request builder for purging by prefixes.
330pub struct PurgePrefixesRequest {
331    service: CacheService,
332    zone_id: String,
333    prefixes: Vec<String>,
334}
335
336impl PurgePrefixesRequest {
337    /// Set the prefixes to purge.
338    pub fn prefixes<I, S>(mut self, prefixes: I) -> Self
339    where
340        I: IntoIterator<Item = S>,
341        S: Into<String>,
342    {
343        self.prefixes = prefixes.into_iter().map(|s| s.into()).collect();
344        self
345    }
346
347    /// Add a single prefix to purge.
348    pub fn add_prefix(mut self, prefix: impl Into<String>) -> Self {
349        self.prefixes.push(prefix.into());
350        self
351    }
352
353    /// Send the request.
354    pub async fn send(self) -> Result<PurgeResponse> {
355        if self.prefixes.is_empty() {
356            return Err(crate::error::Error::InvalidInput(
357                "At least one prefix is required".to_string(),
358            ));
359        }
360
361        if self.prefixes.len() > 30 {
362            return Err(crate::error::Error::InvalidInput(
363                "Cannot purge more than 30 prefixes at a time".to_string(),
364            ));
365        }
366
367        let payload = serde_json::json!({
368            "prefixes": self.prefixes
369        });
370
371        let response = self
372            .service
373            .client
374            .post(&format!("/zones/{}/purge_cache", self.zone_id), &payload)
375            .await?;
376
377        CloudflareClient::handle_response(response).await
378    }
379}