Skip to main content

http_cache/
lib.rs

1#![forbid(unsafe_code, future_incompatible)]
2#![deny(
3    missing_docs,
4    missing_debug_implementations,
5    missing_copy_implementations,
6    nonstandard_style,
7    unused_qualifications,
8    unused_import_braces,
9    unused_extern_crates,
10    trivial_casts,
11    trivial_numeric_casts
12)]
13#![allow(clippy::doc_lazy_continuation)]
14#![cfg_attr(docsrs, feature(doc_cfg))]
15//! A caching middleware that follows HTTP caching rules, thanks to
16//! [`http-cache-semantics`](https://github.com/kornelski/rusty-http-cache-semantics).
17//! By default, it uses [`cacache`](https://github.com/zkat/cacache-rs) as the backend cache manager.
18//!
19//! This crate provides the core HTTP caching functionality that can be used to build
20//! caching middleware for various HTTP clients and server frameworks. It implements
21//! RFC 7234 HTTP caching semantics, supporting features like:
22//!
23//! - Automatic cache invalidation for unsafe HTTP methods (PUT, POST, DELETE, PATCH)
24//! - Respect for HTTP cache-control headers
25//! - Conditional requests (ETag, Last-Modified)
26//! - Multiple cache storage backends
27//! - Streaming response support
28//!
29//! ## Basic Usage
30//!
31//! The core types for building HTTP caches:
32//!
33//! ```rust
34//! # #[cfg(feature = "manager-cacache")]
35//! # fn main() {
36//! use http_cache::{CACacheManager, HttpCache, CacheMode, HttpCacheOptions};
37//!
38//! // Create a cache manager with disk storage
39//! let manager = CACacheManager::new("./cache".into(), true);
40//!
41//! // Create an HTTP cache with default behavior
42//! let cache = HttpCache {
43//!     mode: CacheMode::Default,
44//!     manager,
45//!     options: HttpCacheOptions::default(),
46//! };
47//! # }
48//! # #[cfg(not(feature = "manager-cacache"))]
49//! # fn main() {}
50//! ```
51//!
52//! ## Cache Modes
53//!
54//! Different cache modes provide different behaviors:
55//!
56//! ```rust
57//! # #[cfg(feature = "manager-cacache")]
58//! # fn main() {
59//! use http_cache::{CacheMode, HttpCache, CACacheManager, HttpCacheOptions};
60//!
61//! let manager = CACacheManager::new("./cache".into(), true);
62//!
63//! // Default mode: follows HTTP caching rules
64//! let default_cache = HttpCache {
65//!     mode: CacheMode::Default,
66//!     manager: manager.clone(),
67//!     options: HttpCacheOptions::default(),
68//! };
69//!
70//! // NoStore mode: never caches responses
71//! let no_store_cache = HttpCache {
72//!     mode: CacheMode::NoStore,
73//!     manager: manager.clone(),
74//!     options: HttpCacheOptions::default(),
75//! };
76//!
77//! // ForceCache mode: caches responses even if headers suggest otherwise
78//! let force_cache = HttpCache {
79//!     mode: CacheMode::ForceCache,
80//!     manager,
81//!     options: HttpCacheOptions::default(),
82//! };
83//! # }
84//! # #[cfg(not(feature = "manager-cacache"))]
85//! # fn main() {}
86//! ```
87//!
88//! ## Custom Cache Keys
89//!
90//! You can customize how cache keys are generated:
91//!
92//! ```rust
93//! # #[cfg(feature = "manager-cacache")]
94//! # fn main() {
95//! use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode};
96//! use std::sync::Arc;
97//! use http::request::Parts;
98//!
99//! let manager = CACacheManager::new("./cache".into(), true);
100//!
101//! let options = HttpCacheOptions {
102//!     cache_key: Some(Arc::new(|req: &Parts| {
103//!         // Custom cache key that includes query parameters
104//!         format!("{}:{}", req.method, req.uri)
105//!     })),
106//!     ..Default::default()
107//! };
108//!
109//! let cache = HttpCache {
110//!     mode: CacheMode::Default,
111//!     manager,
112//!     options,
113//! };
114//! # }
115//! # #[cfg(not(feature = "manager-cacache"))]
116//! # fn main() {}
117//! ```
118//!
119//! ## Maximum TTL Control
120//!
121//! Set a maximum time-to-live for cached responses, particularly useful with `CacheMode::IgnoreRules`:
122//!
123//! ```rust
124//! # #[cfg(feature = "manager-cacache")]
125//! # fn main() {
126//! use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode};
127//! use std::time::Duration;
128//!
129//! let manager = CACacheManager::new("./cache".into(), true);
130//!
131//! // Limit cache duration to 5 minutes regardless of server headers
132//! let options = HttpCacheOptions {
133//!     max_ttl: Some(Duration::from_secs(300)), // 5 minutes
134//!     ..Default::default()
135//! };
136//!
137//! let cache = HttpCache {
138//!     mode: CacheMode::IgnoreRules, // Ignore server cache-control headers
139//!     manager,
140//!     options,
141//! };
142//! # }
143//! # #[cfg(not(feature = "manager-cacache"))]
144//! # fn main() {}
145//! ```
146//!
147//! ## Response-Based Cache Mode Override
148//!
149//! Override cache behavior based on the response you receive. This is useful for scenarios like
150//! forcing cache for successful responses even when headers say not to cache, or never caching
151//! error responses like rate limits:
152//!
153//! ```rust
154//! # #[cfg(feature = "manager-cacache")]
155//! # fn main() {
156//! use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode};
157//! use std::sync::Arc;
158//!
159//! let manager = CACacheManager::new("./cache".into(), true);
160//!
161//! let options = HttpCacheOptions {
162//!     response_cache_mode_fn: Some(Arc::new(|_request_parts, response| {
163//!         match response.status {
164//!             // Force cache successful responses even if headers say not to cache
165//!             200..=299 => Some(CacheMode::ForceCache),
166//!             // Never cache rate-limited responses
167//!             429 => Some(CacheMode::NoStore),
168//!             // Use default behavior for everything else
169//!             _ => None,
170//!         }
171//!     })),
172//!     ..Default::default()
173//! };
174//!
175//! let cache = HttpCache {
176//!     mode: CacheMode::Default,
177//!     manager,
178//!     options,
179//! };
180//! # }
181//! # #[cfg(not(feature = "manager-cacache"))]
182//! # fn main() {}
183//! ```
184//!
185//! ## Content-Type Based Caching
186//!
187//! You can implement selective caching based on response content types using `response_cache_mode_fn`.
188//! This is useful when you only want to cache certain types of content:
189//!
190//! ```rust
191//! # #[cfg(feature = "manager-cacache")]
192//! # fn main() {
193//! use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode};
194//! use std::sync::Arc;
195//!
196//! let manager = CACacheManager::new("./cache".into(), true);
197//!
198//! let options = HttpCacheOptions {
199//!     response_cache_mode_fn: Some(Arc::new(|_request_parts, response| {
200//!         // Check the Content-Type header to decide caching behavior
201//!         if let Some(content_type) = response.headers.get("content-type") {
202//!             match content_type.as_str() {
203//!                 // Cache JSON APIs aggressively
204//!                 ct if ct.starts_with("application/json") => Some(CacheMode::ForceCache),
205//!                 // Cache images with default rules
206//!                 ct if ct.starts_with("image/") => Some(CacheMode::Default),
207//!                 // Cache static assets
208//!                 ct if ct.starts_with("text/css") => Some(CacheMode::ForceCache),
209//!                 ct if ct.starts_with("application/javascript") => Some(CacheMode::ForceCache),
210//!                 // Don't cache HTML pages (dynamic content)
211//!                 ct if ct.starts_with("text/html") => Some(CacheMode::NoStore),
212//!                 // Don't cache unknown content types
213//!                 _ => Some(CacheMode::NoStore),
214//!             }
215//!         } else {
216//!             // No Content-Type header - don't cache
217//!             Some(CacheMode::NoStore)
218//!         }
219//!     })),
220//!     ..Default::default()
221//! };
222//!
223//! let cache = HttpCache {
224//!     mode: CacheMode::Default, // This gets overridden by response_cache_mode_fn
225//!     manager,
226//!     options,
227//! };
228//! # }
229//! # #[cfg(not(feature = "manager-cacache"))]
230//! # fn main() {}
231//! ```
232//!
233//! ## Streaming Support
234//!
235//! For handling large responses without full buffering, use the `StreamingManager`:
236//!
237//! ```rust
238//! # #[cfg(feature = "streaming")]
239//! # {
240//! use http_cache::StreamingBody;
241//! use bytes::Bytes;
242//! use http_body::Body;
243//! use http_body_util::Full;
244//!
245//! // StreamingManager uses redb (metadata) + tokio::fs (bodies) with a moka
246//! // in-memory hot cache for disk-backed streaming.
247//! // Create with: StreamingManager::with_temp_dir(1000).await.unwrap()
248//!
249//! // StreamingBody can handle both buffered and streaming scenarios
250//! let body: StreamingBody<Full<Bytes>> = StreamingBody::buffered(Bytes::from("cached content"));
251//! println!("Body size: {:?}", body.size_hint());
252//! # }
253//! ```
254//!
255//! **Note**: Streaming support requires the `StreamingManager` with the `streaming` feature.
256//! Other cache managers (CACacheManager, MokaManager, QuickManager) do not support streaming
257//! and will buffer response bodies in memory.
258//!
259//! ## Features
260//!
261//! The following features are available. By default `manager-cacache` is enabled.
262//!
263//! - `manager-cacache` (default): enable [cacache](https://github.com/zkat/cacache-rs),
264//! a disk cache, backend manager. Uses tokio runtime.
265//! - `manager-moka` (disabled): enable [moka](https://github.com/moka-rs/moka),
266//! an in-memory cache, backend manager.
267//! - `manager-foyer` (disabled): enable [foyer](https://github.com/foyer-rs/foyer),
268//! a hybrid in-memory + disk cache, backend manager. Uses tokio runtime.
269//! - `http-headers-compat` (disabled): enable backwards compatibility for deserializing cached
270//! responses from older versions that used single-value headers. Enable this if you need to read
271//! cache entries created by older versions of http-cache.
272//! - `streaming` (disabled): enable the `StreamingManager` for streaming cache
273//!   support. Uses redb for metadata, raw `tokio::fs` files for bodies, and
274//!   moka as an in-memory hot cache of metadata.
275//! - `with-http-types` (disabled): enable [http-types](https://github.com/http-rs/http-types)
276//! type conversion support
277//!
278//! ### URL Implementation Features
279//!
280//! Exactly one URL implementation must be enabled. These features are **mutually exclusive**:
281//!
282//! - `url-standard` (default): uses the [url](https://github.com/servo/rust-url) crate.
283//!   Note: This brings in the `idna` crate which has a Unicode license.
284//! - `url-ada` (disabled): uses [ada-url](https://github.com/ada-url/rust) for WHATWG-compliant
285//!   URL parsing without the Unicode/IDNA license dependency.
286//!
287//! If you need to avoid the Unicode license, use `url-ada`:
288//!
289//! ```toml
290//! [dependencies]
291//! http-cache = { version = "1.0", default-features = false, features = ["manager-cacache", "url-ada"] }
292//! ```
293//!
294//! ### Legacy bincode features (deprecated)
295//!
296//! These features are deprecated due to [RUSTSEC-2025-0141](https://rustsec.org/advisories/RUSTSEC-2025-0141)
297//! and will be removed in the next major version:
298//!
299//! - `manager-cacache-bincode`: cacache with bincode serialization
300//! - `manager-moka-bincode`: moka with bincode serialization
301//!
302//! **Note**: Only `StreamingManager` (via the `streaming` feature) provides streaming support.
303//! Other managers will buffer response bodies in memory even when used with `StreamingManager`.
304//!
305//! ## Integration
306//!
307//! This crate is designed to be used as a foundation for HTTP client and server middleware.
308//! See the companion crates for specific integrations:
309//!
310//! - [`http-cache-reqwest`](https://docs.rs/http-cache-reqwest) for reqwest client middleware
311//! - [`http-cache-surf`](https://docs.rs/http-cache-surf) for surf client middleware  
312//! - [`http-cache-tower`](https://docs.rs/http-cache-tower) for tower service middleware
313
314// URL feature validation - exactly one URL implementation must be enabled
315#[cfg(all(feature = "url-standard", feature = "url-ada"))]
316compile_error!("features `url-standard` and `url-ada` are mutually exclusive");
317
318#[cfg(not(any(feature = "url-standard", feature = "url-ada")))]
319compile_error!("either feature `url-standard` or `url-ada` must be enabled");
320
321mod body;
322mod error;
323mod managers;
324
325#[cfg(feature = "rate-limiting")]
326pub mod rate_limiting;
327
328use std::{
329    collections::HashMap,
330    convert::TryFrom,
331    fmt::{self, Debug},
332    future::Future,
333    str::FromStr,
334    sync::Arc,
335    time::{Duration, SystemTime},
336};
337
338use http::{
339    header::CACHE_CONTROL, request, response, HeaderValue, Response, StatusCode,
340};
341use http_cache_semantics::{AfterResponse, BeforeRequest, CachePolicy};
342use serde::{Deserialize, Deserializer, Serialize, Serializer};
343
344// URL type alias - allows users to choose between `url` (default) and `ada-url` crates
345// When using `url-ada` feature, this becomes `ada_url::Url`
346#[cfg(feature = "url-ada")]
347pub use ada_url::Url;
348#[cfg(not(feature = "url-ada"))]
349pub use url::Url;
350
351// ============================================================================
352// URL Helper Functions
353// ============================================================================
354// These functions abstract away API differences between `url` and `ada-url` crates.
355// Internal code should use these helpers instead of calling URL methods directly.
356
357/// Parse a URL string into a `Url` type.
358///
359/// This helper abstracts the parsing API difference between `url` and `ada-url`:
360/// - `url` crate: `Url::parse(s)` returns `Result<Url, ParseError>`
361/// - `ada-url` crate: `Url::parse(s, None)` returns `Result<Url, ParseUrlError>`
362#[inline]
363pub fn url_parse(s: &str) -> Result<Url> {
364    #[cfg(feature = "url-ada")]
365    {
366        Url::parse(s, None).map_err(|e| -> BoxError { e.to_string().into() })
367    }
368    #[cfg(not(feature = "url-ada"))]
369    {
370        Url::parse(s).map_err(|e| -> BoxError { Box::new(e) })
371    }
372}
373
374/// Set the path component of a URL.
375///
376/// API differences:
377/// - `url` crate: `url.set_path(path)`
378/// - `ada-url` crate: `url.set_pathname(Some(path))`
379#[inline]
380pub fn url_set_path(url: &mut Url, path: &str) {
381    #[cfg(feature = "url-ada")]
382    {
383        let _ = url.set_pathname(Some(path));
384    }
385    #[cfg(not(feature = "url-ada"))]
386    {
387        url.set_path(path);
388    }
389}
390
391/// Set the query component of a URL.
392///
393/// API differences:
394/// - `url` crate: `url.set_query(Some(query))` or `url.set_query(None)`
395/// - `ada-url` crate: `url.set_search(Some(query))` or `url.set_search(None)`
396#[inline]
397pub fn url_set_query(url: &mut Url, query: Option<&str>) {
398    #[cfg(feature = "url-ada")]
399    {
400        url.set_search(query);
401    }
402    #[cfg(not(feature = "url-ada"))]
403    {
404        url.set_query(query);
405    }
406}
407
408/// Get the hostname of a URL as a string.
409///
410/// API differences:
411/// - `url` crate: `url.host_str()` returns `Option<&str>`
412/// - `ada-url` crate: `url.hostname()` returns `&str` (empty string if no host)
413#[inline]
414#[must_use]
415pub fn url_hostname(url: &Url) -> Option<&str> {
416    #[cfg(feature = "url-ada")]
417    {
418        let hostname = url.hostname();
419        if hostname.is_empty() {
420            None
421        } else {
422            Some(hostname)
423        }
424    }
425    #[cfg(not(feature = "url-ada"))]
426    {
427        url.host_str()
428    }
429}
430
431/// Get the host of a URL as a string for display purposes (e.g., warning headers).
432///
433/// This returns the host portion as a string, or "unknown" if not available.
434/// Used in places like HTTP Warning headers where we need a displayable host value.
435#[inline]
436#[must_use]
437pub fn url_host_str(url: &Url) -> String {
438    #[cfg(feature = "url-ada")]
439    {
440        let hostname = url.hostname();
441        if hostname.is_empty() {
442            "unknown".to_string()
443        } else {
444            hostname.to_string()
445        }
446    }
447    #[cfg(not(feature = "url-ada"))]
448    {
449        url.host()
450            .map(|h| h.to_string())
451            .unwrap_or_else(|| "unknown".to_string())
452    }
453}
454
455pub use body::StreamingBody;
456pub use error::{
457    BadHeader, BadRequest, BadVersion, BoxError, ClientStreamingError,
458    HttpCacheError, HttpCacheResult, Result, StreamingError,
459};
460
461#[cfg(any(
462    feature = "manager-cacache",
463    feature = "manager-cacache-bincode"
464))]
465pub use managers::cacache::CACacheManager;
466
467#[cfg(feature = "streaming")]
468pub use managers::streaming_cache::StreamingManager;
469
470#[cfg(any(feature = "manager-moka", feature = "manager-moka-bincode"))]
471pub use managers::moka::MokaManager;
472
473#[cfg(feature = "manager-foyer")]
474pub use managers::foyer::FoyerManager;
475
476#[cfg(feature = "rate-limiting")]
477pub use rate_limiting::{
478    CacheAwareRateLimiter, DirectRateLimiter, DomainRateLimiter,
479};
480
481#[cfg(feature = "rate-limiting")]
482pub use rate_limiting::Quota;
483
484// Exposing the moka cache for convenience, renaming to avoid naming conflicts
485#[cfg(any(feature = "manager-moka", feature = "manager-moka-bincode"))]
486#[cfg_attr(docsrs, doc(cfg(feature = "manager-moka")))]
487pub use moka::future::{Cache as MokaCache, CacheBuilder as MokaCacheBuilder};
488
489// Custom headers used to indicate cache status (hit or miss)
490/// `x-cache` header: Value will be HIT if the response was served from cache, MISS if not
491pub const XCACHE: &str = "x-cache";
492/// `x-cache-lookup` header: Value will be HIT if a response existed in cache, MISS if not
493pub const XCACHELOOKUP: &str = "x-cache-lookup";
494/// `warning` header: HTTP warning header as per RFC 7234
495const WARNING: &str = "warning";
496
497/// Represents a basic cache status
498/// Used in the custom headers `x-cache` and `x-cache-lookup`
499#[derive(Debug, Copy, Clone)]
500pub enum HitOrMiss {
501    /// Yes, there was a hit
502    HIT,
503    /// No, there was no hit
504    MISS,
505}
506
507impl fmt::Display for HitOrMiss {
508    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
509        match self {
510            Self::HIT => write!(f, "HIT"),
511            Self::MISS => write!(f, "MISS"),
512        }
513    }
514}
515
516/// Represents an HTTP version
517#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
518#[non_exhaustive]
519pub enum HttpVersion {
520    /// HTTP Version 0.9
521    #[serde(rename = "HTTP/0.9")]
522    Http09,
523    /// HTTP Version 1.0
524    #[serde(rename = "HTTP/1.0")]
525    Http10,
526    /// HTTP Version 1.1
527    #[serde(rename = "HTTP/1.1")]
528    Http11,
529    /// HTTP Version 2.0
530    #[serde(rename = "HTTP/2.0")]
531    H2,
532    /// HTTP Version 3.0
533    #[serde(rename = "HTTP/3.0")]
534    H3,
535}
536
537impl fmt::Display for HttpVersion {
538    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
539        match *self {
540            HttpVersion::Http09 => write!(f, "HTTP/0.9"),
541            HttpVersion::Http10 => write!(f, "HTTP/1.0"),
542            HttpVersion::Http11 => write!(f, "HTTP/1.1"),
543            HttpVersion::H2 => write!(f, "HTTP/2.0"),
544            HttpVersion::H3 => write!(f, "HTTP/3.0"),
545        }
546    }
547}
548
549/// Extract a URL from HTTP request parts for cache key generation
550///
551/// This function reconstructs the full URL from the request parts, handling both
552/// HTTP and HTTPS schemes based on the connection type or explicit headers.
553fn extract_url_from_request_parts(parts: &request::Parts) -> Result<Url> {
554    // First check if the URI is already absolute
555    if let Some(_scheme) = parts.uri.scheme() {
556        // URI is absolute, use it directly
557        return url_parse(&parts.uri.to_string())
558            .map_err(|_| -> BoxError { BadHeader.into() });
559    }
560
561    // Get the host header
562    let host = parts
563        .headers
564        .get("host")
565        .ok_or(BadHeader)?
566        .to_str()
567        .map_err(|_| BadHeader)?;
568
569    // Determine scheme based on host and headers
570    let scheme = determine_scheme(host, &parts.headers)?;
571
572    // Create base URL using the URL helper for cross-crate compatibility
573    let mut base_url = url_parse(&format!("{}://{}/", &scheme, host))
574        .map_err(|_| -> BoxError { BadHeader.into() })?;
575
576    // Set the path and query from the URI using helpers
577    if let Some(path_and_query) = parts.uri.path_and_query() {
578        url_set_path(&mut base_url, path_and_query.path());
579        if let Some(query) = path_and_query.query() {
580            url_set_query(&mut base_url, Some(query));
581        }
582    }
583
584    Ok(base_url)
585}
586
587/// Determine the appropriate scheme for URL construction
588fn determine_scheme(host: &str, headers: &http::HeaderMap) -> Result<String> {
589    // Check for explicit protocol forwarding header first
590    if let Some(forwarded_proto) = headers.get("x-forwarded-proto") {
591        let proto = forwarded_proto.to_str().map_err(|_| BadHeader)?;
592        return match proto {
593            "http" | "https" => Ok(proto.to_string()),
594            _ => Ok("https".to_string()), // Default to secure for unknown protocols
595        };
596    }
597
598    // Check if this looks like a local development host
599    if host.starts_with("localhost") || host.starts_with("127.0.0.1") {
600        Ok("http".to_string())
601    } else {
602        Ok("https".to_string()) // Default to secure for all other hosts
603    }
604}
605
606/// Represents HTTP headers in either legacy or modern format
607#[derive(Debug, Clone)]
608pub enum HttpHeaders {
609    /// Modern header representation - allows multiple values per key
610    Modern(HashMap<String, Vec<String>>),
611    /// Legacy header representation - kept for backward compatibility with deserialization
612    #[cfg(feature = "http-headers-compat")]
613    Legacy(HashMap<String, String>),
614}
615
616// Serialize directly as the inner HashMap (no enum variant wrapper)
617// This ensures compatibility: serialized data is just the raw HashMap
618impl Serialize for HttpHeaders {
619    fn serialize<S>(
620        &self,
621        serializer: S,
622    ) -> std::result::Result<S::Ok, S::Error>
623    where
624        S: Serializer,
625    {
626        #[cfg(feature = "http-headers-compat")]
627        {
628            // Always serialize as Legacy format when compat is enabled
629            match self {
630                HttpHeaders::Modern(modern) => {
631                    // Convert Modern to Legacy format by joining values
632                    let legacy: HashMap<String, String> = modern
633                        .iter()
634                        .map(|(k, v)| (k.clone(), v.join(", ")))
635                        .collect();
636                    legacy.serialize(serializer)
637                }
638                HttpHeaders::Legacy(legacy) => legacy.serialize(serializer),
639            }
640        }
641
642        #[cfg(not(feature = "http-headers-compat"))]
643        {
644            match self {
645                HttpHeaders::Modern(modern) => modern.serialize(serializer),
646            }
647        }
648    }
649}
650
651// Deserialize directly as HashMap based on feature flag
652// With http-headers-compat: reads HashMap<String, String> (legacy alpha.2 format)
653// Without http-headers-compat: reads HashMap<String, Vec<String>> (modern format)
654impl<'de> Deserialize<'de> for HttpHeaders {
655    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
656    where
657        D: Deserializer<'de>,
658    {
659        #[cfg(feature = "http-headers-compat")]
660        {
661            let legacy = HashMap::<String, String>::deserialize(deserializer)?;
662            Ok(HttpHeaders::Legacy(legacy))
663        }
664
665        #[cfg(not(feature = "http-headers-compat"))]
666        {
667            let modern =
668                HashMap::<String, Vec<String>>::deserialize(deserializer)?;
669            Ok(HttpHeaders::Modern(modern))
670        }
671    }
672}
673
674impl HttpHeaders {
675    /// Creates a new empty HttpHeaders in modern format
676    pub fn new() -> Self {
677        HttpHeaders::Modern(HashMap::new())
678    }
679
680    /// Inserts a header key-value pair, replacing any existing values for that key
681    /// Keys are normalized to lowercase per RFC 7230
682    pub fn insert(&mut self, key: String, value: String) {
683        let normalized_key = key.to_ascii_lowercase();
684        match self {
685            #[cfg(feature = "http-headers-compat")]
686            HttpHeaders::Legacy(legacy) => {
687                legacy.insert(normalized_key, value);
688            }
689            HttpHeaders::Modern(modern) => {
690                // Replace existing values with a new single-element vec
691                modern.insert(normalized_key, vec![value]);
692            }
693        }
694    }
695
696    /// Appends a header value, preserving existing values for the same key
697    /// Keys are normalized to lowercase per RFC 7230
698    pub fn append(&mut self, key: String, value: String) {
699        let normalized_key = key.to_ascii_lowercase();
700        match self {
701            #[cfg(feature = "http-headers-compat")]
702            HttpHeaders::Legacy(legacy) => {
703                // Legacy format doesn't support multi-value, fall back to insert
704                legacy.insert(normalized_key, value);
705            }
706            HttpHeaders::Modern(modern) => {
707                modern
708                    .entry(normalized_key)
709                    .or_insert_with(Vec::new)
710                    .push(value);
711            }
712        }
713    }
714
715    /// Retrieves the first value for a given header key
716    /// Keys are normalized to lowercase per RFC 7230
717    pub fn get(&self, key: &str) -> Option<&String> {
718        let normalized_key = key.to_ascii_lowercase();
719        match self {
720            #[cfg(feature = "http-headers-compat")]
721            HttpHeaders::Legacy(legacy) => legacy.get(&normalized_key),
722            HttpHeaders::Modern(modern) => {
723                modern.get(&normalized_key).and_then(|vals| vals.first())
724            }
725        }
726    }
727
728    /// Removes a header key and its associated values
729    /// Keys are normalized to lowercase per RFC 7230
730    pub fn remove(&mut self, key: &str) {
731        let normalized_key = key.to_ascii_lowercase();
732        match self {
733            #[cfg(feature = "http-headers-compat")]
734            HttpHeaders::Legacy(legacy) => {
735                legacy.remove(&normalized_key);
736            }
737            HttpHeaders::Modern(modern) => {
738                modern.remove(&normalized_key);
739            }
740        }
741    }
742
743    /// Checks if a header key exists
744    /// Keys are normalized to lowercase per RFC 7230
745    pub fn contains_key(&self, key: &str) -> bool {
746        let normalized_key = key.to_ascii_lowercase();
747        match self {
748            #[cfg(feature = "http-headers-compat")]
749            HttpHeaders::Legacy(legacy) => legacy.contains_key(&normalized_key),
750            HttpHeaders::Modern(modern) => modern.contains_key(&normalized_key),
751        }
752    }
753
754    /// Returns an iterator over the header key-value pairs
755    pub fn iter(&self) -> HttpHeadersIterator<'_> {
756        match self {
757            #[cfg(feature = "http-headers-compat")]
758            HttpHeaders::Legacy(legacy) => {
759                HttpHeadersIterator { inner: legacy.iter().collect(), index: 0 }
760            }
761            HttpHeaders::Modern(modern) => HttpHeadersIterator {
762                inner: modern
763                    .iter()
764                    .flat_map(|(k, vals)| vals.iter().map(move |v| (k, v)))
765                    .collect(),
766                index: 0,
767            },
768        }
769    }
770}
771
772impl From<&http::HeaderMap> for HttpHeaders {
773    fn from(headers: &http::HeaderMap) -> Self {
774        let mut modern_headers = HashMap::new();
775
776        // headers.keys() already yields each unique name exactly once
777        for name in headers.keys() {
778            let values: Vec<String> = headers
779                .get_all(name)
780                .iter()
781                .filter_map(|v| v.to_str().ok())
782                .map(|s| s.to_string())
783                .collect();
784
785            if !values.is_empty() {
786                modern_headers.insert(name.to_string(), values);
787            }
788        }
789
790        HttpHeaders::Modern(modern_headers)
791    }
792}
793
794impl From<HttpHeaders> for HashMap<String, Vec<String>> {
795    fn from(headers: HttpHeaders) -> Self {
796        match headers {
797            #[cfg(feature = "http-headers-compat")]
798            HttpHeaders::Legacy(legacy) => {
799                legacy.into_iter().map(|(k, v)| (k, vec![v])).collect()
800            }
801            HttpHeaders::Modern(modern) => modern,
802        }
803    }
804}
805
806impl Default for HttpHeaders {
807    fn default() -> Self {
808        HttpHeaders::new()
809    }
810}
811
812impl IntoIterator for HttpHeaders {
813    type Item = (String, String);
814    type IntoIter = HttpHeadersIntoIterator;
815
816    fn into_iter(self) -> Self::IntoIter {
817        HttpHeadersIntoIterator {
818            inner: match self {
819                #[cfg(feature = "http-headers-compat")]
820                HttpHeaders::Legacy(legacy) => legacy.into_iter().collect(),
821                HttpHeaders::Modern(modern) => modern
822                    .into_iter()
823                    .flat_map(|(k, vals)| {
824                        vals.into_iter().map(move |v| (k.clone(), v))
825                    })
826                    .collect(),
827            },
828            index: 0,
829        }
830    }
831}
832
833/// Iterator for HttpHeaders
834#[derive(Debug)]
835pub struct HttpHeadersIntoIterator {
836    inner: Vec<(String, String)>,
837    index: usize,
838}
839
840impl Iterator for HttpHeadersIntoIterator {
841    type Item = (String, String);
842
843    fn next(&mut self) -> Option<Self::Item> {
844        if self.index < self.inner.len() {
845            let item = self.inner[self.index].clone();
846            self.index += 1;
847            Some(item)
848        } else {
849            None
850        }
851    }
852}
853
854impl<'a> IntoIterator for &'a HttpHeaders {
855    type Item = (&'a String, &'a String);
856    type IntoIter = HttpHeadersIterator<'a>;
857
858    fn into_iter(self) -> Self::IntoIter {
859        self.iter()
860    }
861}
862
863/// Iterator for HttpHeaders references
864#[derive(Debug)]
865pub struct HttpHeadersIterator<'a> {
866    inner: Vec<(&'a String, &'a String)>,
867    index: usize,
868}
869
870impl<'a> Iterator for HttpHeadersIterator<'a> {
871    type Item = (&'a String, &'a String);
872
873    fn next(&mut self) -> Option<Self::Item> {
874        if self.index < self.inner.len() {
875            let item = self.inner[self.index];
876            self.index += 1;
877            Some(item)
878        } else {
879            None
880        }
881    }
882}
883
884/// A basic generic type that represents an HTTP response
885#[derive(Debug, Clone, Deserialize, Serialize)]
886pub struct HttpResponse {
887    /// HTTP response body
888    pub body: Vec<u8>,
889    /// HTTP response headers
890    pub headers: HttpHeaders,
891    /// HTTP response status code
892    pub status: u16,
893    /// HTTP response url
894    pub url: Url,
895    /// HTTP response version
896    pub version: HttpVersion,
897    /// Metadata
898    #[serde(default)]
899    pub metadata: Option<Vec<u8>>,
900}
901
902impl HttpResponse {
903    /// Returns `http::response::Parts`
904    pub fn parts(&self) -> Result<response::Parts> {
905        let mut converted =
906            response::Builder::new().status(self.status).body(())?;
907        {
908            let headers = converted.headers_mut();
909            for header in &self.headers {
910                headers.append(
911                    http::header::HeaderName::from_str(header.0.as_str())?,
912                    HeaderValue::from_str(header.1.as_str())?,
913                );
914            }
915        }
916        Ok(converted.into_parts().0)
917    }
918
919    /// Returns the status code of the warning header if present
920    #[must_use]
921    fn warning_code(&self) -> Option<usize> {
922        self.headers.get(WARNING).and_then(|hdr| {
923            hdr.as_str().chars().take(3).collect::<String>().parse().ok()
924        })
925    }
926
927    /// Adds a warning header to a response
928    fn add_warning(&mut self, url: &Url, code: usize, message: &str) {
929        // warning    = "warning" ":" 1#warning-value
930        // warning-value = warn-code SP warn-agent SP warn-text [SP warn-date]
931        // warn-code  = 3DIGIT
932        // warn-agent = ( host [ ":" port ] ) | pseudonym
933        //                 ; the name or pseudonym of the server adding
934        //                 ; the warning header, for use in debugging
935        // warn-text  = quoted-string
936        // warn-date  = <"> HTTP-date <">
937        // (https://tools.ietf.org/html/rfc2616#section-14.46)
938        let host = url_host_str(url);
939        // Escape message to prevent header injection and ensure valid HTTP format
940        let escaped_message =
941            message.replace('"', "'").replace(['\n', '\r'], " ");
942        self.headers.insert(
943            WARNING.to_string(),
944            format!(
945                "{} {} \"{}\" \"{}\"",
946                code,
947                host,
948                escaped_message,
949                httpdate::fmt_http_date(SystemTime::now())
950            ),
951        );
952    }
953
954    /// Removes a warning header from a response
955    fn remove_warning(&mut self) {
956        self.headers.remove(WARNING);
957    }
958
959    /// Update the headers from `http::response::Parts`
960    pub fn update_headers(&mut self, parts: &response::Parts) -> Result<()> {
961        // Clear keys that appear in the new headers, then append all values
962        for name in parts.headers.keys() {
963            self.headers.remove(name.as_str());
964        }
965        for (name, value) in parts.headers.iter() {
966            if let Ok(v) = value.to_str() {
967                self.headers.append(name.as_str().to_string(), v.to_string());
968            }
969        }
970        Ok(())
971    }
972
973    /// Checks if the Cache-Control header contains the must-revalidate directive
974    #[must_use]
975    fn must_revalidate(&self) -> bool {
976        self.headers.get(CACHE_CONTROL.as_str()).is_some_and(|val| {
977            val.as_str().to_lowercase().contains("must-revalidate")
978        })
979    }
980
981    /// Adds the custom `x-cache` header to the response
982    pub fn cache_status(&mut self, hit_or_miss: HitOrMiss) {
983        self.headers.insert(XCACHE.to_string(), hit_or_miss.to_string());
984    }
985
986    /// Adds the custom `x-cache-lookup` header to the response
987    pub fn cache_lookup_status(&mut self, hit_or_miss: HitOrMiss) {
988        self.headers.insert(XCACHELOOKUP.to_string(), hit_or_miss.to_string());
989    }
990}
991
992/// A trait providing methods for storing, reading, and removing cache records.
993pub trait CacheManager: Send + Sync + 'static {
994    /// Attempts to pull a cached response and related policy from cache.
995    fn get(
996        &self,
997        cache_key: &str,
998    ) -> impl Future<Output = Result<Option<(HttpResponse, CachePolicy)>>> + Send;
999    /// Attempts to cache a response and related policy.
1000    fn put(
1001        &self,
1002        cache_key: String,
1003        res: HttpResponse,
1004        policy: CachePolicy,
1005    ) -> impl Future<Output = Result<HttpResponse>> + Send;
1006    /// Attempts to remove a record from cache.
1007    fn delete(
1008        &self,
1009        cache_key: &str,
1010    ) -> impl Future<Output = Result<()>> + Send;
1011}
1012
1013/// A streaming cache manager that supports streaming request/response bodies
1014/// without buffering them in memory. This is ideal for large responses.
1015pub trait StreamingCacheManager: Send + Sync + 'static {
1016    /// The body type used by this cache manager
1017    type Body: http_body::Body + Send + 'static;
1018
1019    /// Attempts to pull a cached response and related policy from cache with streaming body.
1020    fn get(
1021        &self,
1022        cache_key: &str,
1023    ) -> impl Future<Output = Result<Option<(Response<Self::Body>, CachePolicy)>>>
1024           + Send
1025    where
1026        <Self::Body as http_body::Body>::Data: Send,
1027        <Self::Body as http_body::Body>::Error:
1028            Into<StreamingError> + Send + Sync + 'static;
1029
1030    /// Attempts to cache a response with a streaming body and related policy.
1031    fn put<B>(
1032        &self,
1033        cache_key: String,
1034        response: Response<B>,
1035        policy: CachePolicy,
1036        request_url: Url,
1037        metadata: Option<Vec<u8>>,
1038    ) -> impl Future<Output = Result<Response<Self::Body>>> + Send
1039    where
1040        B: http_body::Body + Send + 'static,
1041        B::Data: Send,
1042        B::Error: Into<StreamingError>,
1043        <Self::Body as http_body::Body>::Data: Send,
1044        <Self::Body as http_body::Body>::Error:
1045            Into<StreamingError> + Send + Sync + 'static;
1046
1047    /// Converts a generic body to the manager's body type for non-cacheable responses.
1048    /// This is called when a response should not be cached but still needs to be returned
1049    /// with the correct body type.
1050    fn convert_body<B>(
1051        &self,
1052        response: Response<B>,
1053    ) -> impl Future<Output = Result<Response<Self::Body>>> + Send
1054    where
1055        B: http_body::Body + Send + 'static,
1056        B::Data: Send,
1057        B::Error: Into<StreamingError>,
1058        <Self::Body as http_body::Body>::Data: Send,
1059        <Self::Body as http_body::Body>::Error:
1060            Into<StreamingError> + Send + Sync + 'static;
1061
1062    /// Attempts to remove a record from cache.
1063    fn delete(
1064        &self,
1065        cache_key: &str,
1066    ) -> impl Future<Output = Result<()>> + Send;
1067
1068    /// Creates an empty body of the manager's body type.
1069    /// Used for returning 504 Gateway Timeout responses on OnlyIfCached cache misses.
1070    fn empty_body(&self) -> Self::Body;
1071
1072    /// Convert the manager's body type to a reqwest-compatible bytes stream.
1073    /// This enables efficient streaming without collecting the entire body.
1074    #[cfg(feature = "streaming")]
1075    fn body_to_bytes_stream(
1076        body: Self::Body,
1077    ) -> impl futures_util::Stream<
1078        Item = std::result::Result<
1079            bytes::Bytes,
1080            Box<dyn std::error::Error + Send + Sync>,
1081        >,
1082    > + Send
1083    where
1084        <Self::Body as http_body::Body>::Data: Send,
1085        <Self::Body as http_body::Body>::Error: Send + Sync + 'static;
1086}
1087
1088/// Describes the functionality required for interfacing with HTTP client middleware
1089pub trait Middleware: Send {
1090    /// Allows the cache mode to be overridden.
1091    ///
1092    /// This overrides any cache mode set in the configuration, including cache_mode_fn.
1093    fn overridden_cache_mode(&self) -> Option<CacheMode> {
1094        None
1095    }
1096    /// Determines if the request method is either GET or HEAD
1097    fn is_method_get_head(&self) -> bool;
1098    /// Returns a new cache policy with default options
1099    fn policy(&self, response: &HttpResponse) -> Result<CachePolicy>;
1100    /// Returns a new cache policy with custom options
1101    fn policy_with_options(
1102        &self,
1103        response: &HttpResponse,
1104        options: CacheOptions,
1105    ) -> Result<CachePolicy>;
1106    /// Attempts to update the request headers with the passed `http::request::Parts`
1107    fn update_headers(&mut self, parts: &request::Parts) -> Result<()>;
1108    /// Attempts to force the "no-cache" directive on the request
1109    fn force_no_cache(&mut self) -> Result<()>;
1110    /// Attempts to construct `http::request::Parts` from the request
1111    fn parts(&self) -> Result<request::Parts>;
1112    /// Attempts to determine the requested url
1113    fn url(&self) -> Result<Url>;
1114    /// Attempts to determine the request method
1115    fn method(&self) -> Result<String>;
1116    /// Attempts to fetch an upstream resource and return an [`HttpResponse`]
1117    fn remote_fetch(
1118        &mut self,
1119    ) -> impl Future<Output = Result<HttpResponse>> + Send;
1120}
1121
1122/// An interface for HTTP caching that works with composable middleware patterns
1123/// like Tower. This trait separates the concerns of request analysis, cache lookup,
1124/// and response processing into discrete steps.
1125pub trait HttpCacheInterface<B = Vec<u8>>: Send + Sync {
1126    /// Analyze a request to determine cache behavior
1127    fn analyze_request(
1128        &self,
1129        parts: &request::Parts,
1130        mode_override: Option<CacheMode>,
1131    ) -> Result<CacheAnalysis>;
1132
1133    /// Look up a cached response for the given cache key
1134    #[allow(async_fn_in_trait)]
1135    async fn lookup_cached_response(
1136        &self,
1137        key: &str,
1138    ) -> Result<Option<(HttpResponse, CachePolicy)>>;
1139
1140    /// Process a fresh response from upstream and potentially cache it
1141    #[allow(async_fn_in_trait)]
1142    async fn process_response(
1143        &self,
1144        analysis: CacheAnalysis,
1145        response: Response<B>,
1146        metadata: Option<Vec<u8>>,
1147    ) -> Result<Response<B>>;
1148
1149    /// Update request headers for conditional requests (e.g., If-None-Match)
1150    fn prepare_conditional_request(
1151        &self,
1152        parts: &mut request::Parts,
1153        cached_response: &HttpResponse,
1154        policy: &CachePolicy,
1155    ) -> Result<()>;
1156
1157    /// Handle a 304 Not Modified response by returning the cached response
1158    #[allow(async_fn_in_trait)]
1159    async fn handle_not_modified(
1160        &self,
1161        cached_response: HttpResponse,
1162        fresh_parts: &response::Parts,
1163    ) -> Result<HttpResponse>;
1164}
1165
1166/// Streaming version of the HTTP cache interface that supports streaming request/response bodies
1167/// without buffering them in memory. This is ideal for large responses or when memory usage
1168/// is a concern.
1169pub trait HttpCacheStreamInterface: Send + Sync {
1170    /// The body type used by this cache implementation
1171    type Body: http_body::Body + Send + 'static;
1172
1173    /// Analyze a request to determine cache behavior
1174    fn analyze_request(
1175        &self,
1176        parts: &request::Parts,
1177        mode_override: Option<CacheMode>,
1178    ) -> Result<CacheAnalysis>;
1179
1180    /// Look up a cached response for the given cache key, returning a streaming body
1181    #[allow(async_fn_in_trait)]
1182    async fn lookup_cached_response(
1183        &self,
1184        key: &str,
1185    ) -> Result<Option<(Response<Self::Body>, CachePolicy)>>
1186    where
1187        <Self::Body as http_body::Body>::Data: Send,
1188        <Self::Body as http_body::Body>::Error:
1189            Into<StreamingError> + Send + Sync + 'static;
1190
1191    /// Process a fresh response from upstream and potentially cache it with streaming support
1192    #[allow(async_fn_in_trait)]
1193    async fn process_response<B>(
1194        &self,
1195        analysis: CacheAnalysis,
1196        response: Response<B>,
1197        metadata: Option<Vec<u8>>,
1198    ) -> Result<Response<Self::Body>>
1199    where
1200        B: http_body::Body + Send + 'static,
1201        B::Data: Send,
1202        B::Error: Into<StreamingError>,
1203        <Self::Body as http_body::Body>::Data: Send,
1204        <Self::Body as http_body::Body>::Error:
1205            Into<StreamingError> + Send + Sync + 'static;
1206
1207    /// Update request headers for conditional requests (e.g., If-None-Match)
1208    fn prepare_conditional_request(
1209        &self,
1210        parts: &mut request::Parts,
1211        cached_response: &Response<Self::Body>,
1212        policy: &CachePolicy,
1213    ) -> Result<()>;
1214
1215    /// Handle a 304 Not Modified response by returning the cached response
1216    #[allow(async_fn_in_trait)]
1217    async fn handle_not_modified(
1218        &self,
1219        cached_response: Response<Self::Body>,
1220        fresh_parts: &response::Parts,
1221    ) -> Result<Response<Self::Body>>
1222    where
1223        <Self::Body as http_body::Body>::Data: Send,
1224        <Self::Body as http_body::Body>::Error:
1225            Into<StreamingError> + Send + Sync + 'static;
1226}
1227
1228/// Analysis result for a request, containing cache key and caching decisions
1229#[derive(Debug, Clone)]
1230pub struct CacheAnalysis {
1231    /// The cache key for this request
1232    pub cache_key: String,
1233    /// Whether this request should be cached
1234    pub should_cache: bool,
1235    /// The effective cache mode for this request
1236    pub cache_mode: CacheMode,
1237    /// Keys to bust from cache before processing
1238    pub cache_bust_keys: Vec<String>,
1239    /// The request parts for policy creation
1240    pub request_parts: request::Parts,
1241    /// Whether this is a GET or HEAD request
1242    pub is_get_head: bool,
1243}
1244
1245/// Describes the type of fetch to perform when the streaming cache
1246/// orchestrator needs to make a network request.
1247///
1248/// The streaming `run` method accepts a callback `FnOnce(FetchRequest) -> Fut`
1249/// rather than an `impl Middleware`, so this enum tells the caller whether to
1250/// issue a fresh request or a conditional (revalidation) request.
1251#[derive(Debug)]
1252pub enum FetchRequest {
1253    /// A fresh fetch (cache miss or forced).
1254    Fresh,
1255    /// A fresh fetch where the caller should add `cache-control: no-cache`
1256    /// to the outgoing request.  Used for [`CacheMode::NoCache`] to signal
1257    /// upstream caches that they must revalidate.
1258    FreshNoCache,
1259    /// A conditional fetch for revalidation.  The contained
1260    /// [`request::Parts`] carry the conditional headers (e.g.
1261    /// `If-None-Match`, `If-Modified-Since`) that should be merged into
1262    /// the outgoing request before sending it.  Callers should replace
1263    /// existing headers of the same name (insert, not append).
1264    Conditional(Box<request::Parts>),
1265}
1266
1267/// Cache mode determines how the HTTP cache behaves for requests.
1268///
1269/// These modes are similar to [make-fetch-happen cache options](https://github.com/npm/make-fetch-happen#--optscache)
1270/// and provide fine-grained control over caching behavior.
1271///
1272/// # Examples
1273///
1274/// ```rust
1275/// # #[cfg(feature = "manager-cacache")]
1276/// # fn main() {
1277/// use http_cache::{CacheMode, HttpCache, CACacheManager, HttpCacheOptions};
1278///
1279/// let manager = CACacheManager::new("./cache".into(), true);
1280///
1281/// // Use different cache modes for different scenarios
1282/// let default_cache = HttpCache {
1283///     mode: CacheMode::Default,        // Standard HTTP caching rules
1284///     manager: manager.clone(),
1285///     options: HttpCacheOptions::default(),
1286/// };
1287///
1288/// let force_cache = HttpCache {
1289///     mode: CacheMode::ForceCache,     // Cache everything, ignore staleness
1290///     manager: manager.clone(),
1291///     options: HttpCacheOptions::default(),
1292/// };
1293///
1294/// let no_cache = HttpCache {
1295///     mode: CacheMode::NoStore,        // Never cache anything
1296///     manager,
1297///     options: HttpCacheOptions::default(),
1298/// };
1299/// # }
1300/// # #[cfg(not(feature = "manager-cacache"))]
1301/// # fn main() {}
1302/// ```
1303#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
1304pub enum CacheMode {
1305    /// Standard HTTP caching behavior (recommended for most use cases).
1306    ///
1307    /// This mode:
1308    /// - Checks the cache for fresh responses and uses them
1309    /// - Makes conditional requests for stale responses (revalidation)
1310    /// - Makes normal requests when no cached response exists
1311    /// - Updates the cache with new responses
1312    /// - Falls back to stale responses if revalidation fails
1313    ///
1314    /// This is the most common mode and follows HTTP caching standards closely.
1315    #[default]
1316    Default,
1317
1318    /// Completely bypasses the cache.
1319    ///
1320    /// This mode:
1321    /// - Never reads from the cache
1322    /// - Never writes to the cache
1323    /// - Always makes fresh network requests
1324    ///
1325    /// Use this when you need to ensure every request goes to the origin server.
1326    NoStore,
1327
1328    /// Bypasses cache on request but updates cache with response.
1329    ///
1330    /// This mode:
1331    /// - Ignores any cached responses
1332    /// - Always makes a fresh network request
1333    /// - Updates the cache with the response
1334    ///
1335    /// Equivalent to a "hard refresh" - useful when you know the cache is stale.
1336    Reload,
1337
1338    /// Always revalidates cached responses.
1339    ///
1340    /// This mode:
1341    /// - Makes conditional requests if a cached response exists
1342    /// - Makes normal requests if no cached response exists
1343    /// - Updates the cache with responses
1344    ///
1345    /// Use this when you want to ensure content freshness while still benefiting
1346    /// from conditional requests (304 Not Modified responses).
1347    NoCache,
1348
1349    /// Uses cached responses regardless of staleness.
1350    ///
1351    /// This mode:
1352    /// - Uses any cached response, even if stale
1353    /// - Makes network requests only when no cached response exists
1354    /// - Updates the cache with new responses
1355    ///
1356    /// Useful for offline scenarios or when performance is more important than freshness.
1357    ForceCache,
1358
1359    /// Only serves from cache, never makes network requests.
1360    ///
1361    /// This mode:
1362    /// - Uses any cached response, even if stale
1363    /// - Returns an error if no cached response exists
1364    /// - Never makes network requests
1365    ///
1366    /// Use this for offline-only scenarios or when you want to guarantee
1367    /// no network traffic.
1368    OnlyIfCached,
1369
1370    /// Ignores HTTP caching rules and caches everything.
1371    ///
1372    /// This mode:
1373    /// - Caches all 200 responses regardless of cache-control headers
1374    /// - Uses cached responses regardless of staleness
1375    /// - Makes network requests when no cached response exists
1376    ///
1377    /// Use this when you want aggressive caching and don't want to respect
1378    /// server cache directives.
1379    IgnoreRules,
1380}
1381
1382impl TryFrom<http::Version> for HttpVersion {
1383    type Error = BoxError;
1384
1385    fn try_from(value: http::Version) -> Result<Self> {
1386        Ok(match value {
1387            http::Version::HTTP_09 => Self::Http09,
1388            http::Version::HTTP_10 => Self::Http10,
1389            http::Version::HTTP_11 => Self::Http11,
1390            http::Version::HTTP_2 => Self::H2,
1391            http::Version::HTTP_3 => Self::H3,
1392            _ => return Err(Box::new(BadVersion)),
1393        })
1394    }
1395}
1396
1397impl From<HttpVersion> for http::Version {
1398    fn from(value: HttpVersion) -> Self {
1399        match value {
1400            HttpVersion::Http09 => Self::HTTP_09,
1401            HttpVersion::Http10 => Self::HTTP_10,
1402            HttpVersion::Http11 => Self::HTTP_11,
1403            HttpVersion::H2 => Self::HTTP_2,
1404            HttpVersion::H3 => Self::HTTP_3,
1405        }
1406    }
1407}
1408
1409#[cfg(feature = "http-types")]
1410impl TryFrom<http_types::Version> for HttpVersion {
1411    type Error = BoxError;
1412
1413    fn try_from(value: http_types::Version) -> Result<Self> {
1414        Ok(match value {
1415            http_types::Version::Http0_9 => Self::Http09,
1416            http_types::Version::Http1_0 => Self::Http10,
1417            http_types::Version::Http1_1 => Self::Http11,
1418            http_types::Version::Http2_0 => Self::H2,
1419            http_types::Version::Http3_0 => Self::H3,
1420            _ => return Err(Box::new(BadVersion)),
1421        })
1422    }
1423}
1424
1425#[cfg(feature = "http-types")]
1426impl From<HttpVersion> for http_types::Version {
1427    fn from(value: HttpVersion) -> Self {
1428        match value {
1429            HttpVersion::Http09 => Self::Http0_9,
1430            HttpVersion::Http10 => Self::Http1_0,
1431            HttpVersion::Http11 => Self::Http1_1,
1432            HttpVersion::H2 => Self::Http2_0,
1433            HttpVersion::H3 => Self::Http3_0,
1434        }
1435    }
1436}
1437
1438/// Options struct provided by
1439/// [`http-cache-semantics`](https://github.com/kornelski/rusty-http-cache-semantics).
1440pub use http_cache_semantics::CacheOptions;
1441
1442/// A closure that takes [`http::request::Parts`] and returns a [`String`].
1443/// By default, the cache key is a combination of the request method and uri with a colon in between.
1444pub type CacheKey = Arc<dyn Fn(&request::Parts) -> String + Send + Sync>;
1445
1446/// A closure that takes [`http::request::Parts`] and returns a [`CacheMode`]
1447pub type CacheModeFn = Arc<dyn Fn(&request::Parts) -> CacheMode + Send + Sync>;
1448
1449/// A closure that takes [`http::request::Parts`], [`HttpResponse`] and returns a [`CacheMode`] to override caching behavior based on the response
1450pub type ResponseCacheModeFn = Arc<
1451    dyn Fn(&request::Parts, &HttpResponse) -> Option<CacheMode> + Send + Sync,
1452>;
1453
1454/// A closure that takes [`http::request::Parts`], [`Option<CacheKey>`], the default cache key ([`&str`]) and returns [`Vec<String>`] of keys to bust the cache for.
1455/// An empty vector means that no cache busting will be performed.
1456pub type CacheBust = Arc<
1457    dyn Fn(&request::Parts, &Option<CacheKey>, &str) -> Vec<String>
1458        + Send
1459        + Sync,
1460>;
1461
1462/// Type alias for metadata stored alongside cached responses.
1463/// Users are responsible for serialization/deserialization of this data.
1464pub type HttpCacheMetadata = Vec<u8>;
1465
1466/// A closure that takes [`http::request::Parts`] and [`http::response::Parts`] and returns optional metadata to store with the cached response.
1467/// This allows middleware to compute and store additional information alongside cached responses.
1468pub type MetadataProvider = Arc<
1469    dyn Fn(&request::Parts, &response::Parts) -> Option<HttpCacheMetadata>
1470        + Send
1471        + Sync,
1472>;
1473
1474/// A closure that takes a mutable reference to [`HttpResponse`] and modifies it before caching.
1475pub type ModifyResponse = Arc<dyn Fn(&mut HttpResponse) + Send + Sync>;
1476
1477/// Configuration options for customizing HTTP cache behavior on a per-request basis.
1478///
1479/// This struct allows you to override default caching behavior for individual requests
1480/// by providing custom cache options, cache keys, cache modes, and cache busting logic.
1481///
1482/// # Examples
1483///
1484/// ## Basic Custom Cache Key
1485/// ```rust
1486/// use http_cache::{HttpCacheOptions, CacheKey};
1487/// use http::request::Parts;
1488/// use std::sync::Arc;
1489///
1490/// let options = HttpCacheOptions {
1491///     cache_key: Some(Arc::new(|parts: &Parts| {
1492///         format!("custom:{}:{}", parts.method, parts.uri.path())
1493///     })),
1494///     ..Default::default()
1495/// };
1496/// ```
1497///
1498/// ## Custom Cache Mode per Request
1499/// ```rust
1500/// use http_cache::{HttpCacheOptions, CacheMode, CacheModeFn};
1501/// use http::request::Parts;
1502/// use std::sync::Arc;
1503///
1504/// let options = HttpCacheOptions {
1505///     cache_mode_fn: Some(Arc::new(|parts: &Parts| {
1506///         if parts.headers.contains_key("x-no-cache") {
1507///             CacheMode::NoStore
1508///         } else {
1509///             CacheMode::Default
1510///         }
1511///     })),
1512///     ..Default::default()
1513/// };
1514/// ```
1515///
1516/// ## Response-Based Cache Mode Override
1517/// ```rust
1518/// use http_cache::{HttpCacheOptions, ResponseCacheModeFn, CacheMode};
1519/// use http::request::Parts;
1520/// use http_cache::HttpResponse;
1521/// use std::sync::Arc;
1522///
1523/// let options = HttpCacheOptions {
1524///     response_cache_mode_fn: Some(Arc::new(|_parts: &Parts, response: &HttpResponse| {
1525///         // Force cache 2xx responses even if headers say not to cache
1526///         if response.status >= 200 && response.status < 300 {
1527///             Some(CacheMode::ForceCache)
1528///         } else if response.status == 429 { // Rate limited
1529///             Some(CacheMode::NoStore) // Don't cache rate limit responses
1530///         } else {
1531///             None // Use default behavior
1532///         }
1533///     })),
1534///     ..Default::default()
1535/// };
1536/// ```
1537///
1538/// ## Content-Type Based Cache Mode Override
1539/// ```rust
1540/// use http_cache::{HttpCacheOptions, ResponseCacheModeFn, CacheMode};
1541/// use http::request::Parts;
1542/// use http_cache::HttpResponse;
1543/// use std::sync::Arc;
1544///
1545/// let options = HttpCacheOptions {
1546///     response_cache_mode_fn: Some(Arc::new(|_parts: &Parts, response: &HttpResponse| {
1547///         // Cache different content types with different strategies
1548///         if let Some(content_type) = response.headers.get("content-type") {
1549///             match content_type.as_str() {
1550///                 ct if ct.starts_with("application/json") => Some(CacheMode::ForceCache),
1551///                 ct if ct.starts_with("image/") => Some(CacheMode::Default),
1552///                 ct if ct.starts_with("text/html") => Some(CacheMode::NoStore),
1553///                 _ => None, // Use default behavior for other types
1554///             }
1555///         } else {
1556///             Some(CacheMode::NoStore) // No content-type = don't cache
1557///         }
1558///     })),
1559///     ..Default::default()
1560/// };
1561/// ```
1562///
1563/// ## Cache Busting for Related Resources
1564/// ```rust
1565/// use http_cache::{HttpCacheOptions, CacheBust, CacheKey};
1566/// use http::request::Parts;
1567/// use std::sync::Arc;
1568///
1569/// let options = HttpCacheOptions {
1570///     cache_bust: Some(Arc::new(|parts: &Parts, _cache_key: &Option<CacheKey>, _uri: &str| {
1571///         if parts.method == "POST" && parts.uri.path().starts_with("/api/users") {
1572///             vec![
1573///                 "GET:/api/users".to_string(),
1574///                 "GET:/api/users/list".to_string(),
1575///             ]
1576///         } else {
1577///             vec![]
1578///         }
1579///     })),
1580///     ..Default::default()
1581/// };
1582/// ```
1583///
1584/// ## Storing Metadata with Cached Responses
1585/// ```rust
1586/// use http_cache::{HttpCacheOptions, MetadataProvider};
1587/// use http::{request, response};
1588/// use std::sync::Arc;
1589///
1590/// let options = HttpCacheOptions {
1591///     metadata_provider: Some(Arc::new(|request_parts: &request::Parts, response_parts: &response::Parts| {
1592///         // Store computed information with the cached response
1593///         let content_type = response_parts
1594///             .headers
1595///             .get("content-type")
1596///             .and_then(|v| v.to_str().ok())
1597///             .unwrap_or("unknown");
1598///
1599///         // Return serialized metadata (users handle serialization)
1600///         Some(format!("path={};content-type={}", request_parts.uri.path(), content_type).into_bytes())
1601///     })),
1602///     ..Default::default()
1603/// };
1604/// ```
1605#[derive(Clone)]
1606pub struct HttpCacheOptions {
1607    /// Override the default cache options.
1608    pub cache_options: Option<CacheOptions>,
1609    /// Override the default cache key generator.
1610    ///
1611    /// **Note:** Custom closures receive only `&request::Parts` and do not receive
1612    /// the `override_method` parameter used for cache invalidation. This means
1613    /// RFC 7234 section 4.4 cross-method invalidation (e.g., a POST invalidating
1614    /// cached GET/HEAD entries) will not work correctly unless your closure
1615    /// differentiates keys by HTTP method.
1616    pub cache_key: Option<CacheKey>,
1617    /// Override the default cache mode.
1618    pub cache_mode_fn: Option<CacheModeFn>,
1619    /// Override cache behavior based on the response received.
1620    /// This function is called after receiving a response and can override
1621    /// the cache mode for that specific response. Returning `None` means
1622    /// use the default cache mode. This allows fine-grained control over
1623    /// caching behavior based on response status, headers, or content.
1624    pub response_cache_mode_fn: Option<ResponseCacheModeFn>,
1625    /// Bust the caches of the returned keys.
1626    pub cache_bust: Option<CacheBust>,
1627    /// Modifies the response before storing it in the cache.
1628    pub modify_response: Option<ModifyResponse>,
1629    /// Determines if the cache status headers should be added to the response.
1630    pub cache_status_headers: bool,
1631    /// Maximum time-to-live for cached responses.
1632    /// When set, this overrides any longer cache durations specified by the server.
1633    /// Particularly useful with `CacheMode::IgnoreRules` to provide expiration control.
1634    pub max_ttl: Option<Duration>,
1635    /// Rate limiter that applies only on cache misses.
1636    /// When enabled, requests that result in cache hits are returned immediately,
1637    /// while cache misses are rate limited before making network requests.
1638    /// This provides the optimal behavior for web scrapers and similar applications.
1639    #[cfg(feature = "rate-limiting")]
1640    pub rate_limiter: Option<Arc<dyn CacheAwareRateLimiter>>,
1641    /// Optional callback to provide metadata to store alongside cached responses.
1642    /// The callback receives request and response parts and can return metadata bytes.
1643    /// This is useful for storing computed information that should be associated with
1644    /// cached responses without recomputation on cache hits.
1645    pub metadata_provider: Option<MetadataProvider>,
1646}
1647
1648impl Default for HttpCacheOptions {
1649    fn default() -> Self {
1650        Self {
1651            cache_options: None,
1652            cache_key: None,
1653            cache_mode_fn: None,
1654            response_cache_mode_fn: None,
1655            cache_bust: None,
1656            modify_response: None,
1657            cache_status_headers: true,
1658            max_ttl: None,
1659            #[cfg(feature = "rate-limiting")]
1660            rate_limiter: None,
1661            metadata_provider: None,
1662        }
1663    }
1664}
1665
1666impl Debug for HttpCacheOptions {
1667    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1668        #[cfg(feature = "rate-limiting")]
1669        {
1670            f.debug_struct("HttpCacheOptions")
1671                .field("cache_options", &self.cache_options)
1672                .field("cache_key", &"Fn(&request::Parts) -> String")
1673                .field("cache_mode_fn", &"Fn(&request::Parts) -> CacheMode")
1674                .field(
1675                    "response_cache_mode_fn",
1676                    &"Fn(&request::Parts, &HttpResponse) -> Option<CacheMode>",
1677                )
1678                .field("cache_bust", &"Fn(&request::Parts) -> Vec<String>")
1679                .field("modify_response", &"Fn(&mut ModifyResponse)")
1680                .field("cache_status_headers", &self.cache_status_headers)
1681                .field("max_ttl", &self.max_ttl)
1682                .field("rate_limiter", &"Option<CacheAwareRateLimiter>")
1683                .field(
1684                    "metadata_provider",
1685                    &"Fn(&request::Parts, &response::Parts) -> Option<Vec<u8>>",
1686                )
1687                .finish()
1688        }
1689
1690        #[cfg(not(feature = "rate-limiting"))]
1691        {
1692            f.debug_struct("HttpCacheOptions")
1693                .field("cache_options", &self.cache_options)
1694                .field("cache_key", &"Fn(&request::Parts) -> String")
1695                .field("cache_mode_fn", &"Fn(&request::Parts) -> CacheMode")
1696                .field(
1697                    "response_cache_mode_fn",
1698                    &"Fn(&request::Parts, &HttpResponse) -> Option<CacheMode>",
1699                )
1700                .field("cache_bust", &"Fn(&request::Parts) -> Vec<String>")
1701                .field("modify_response", &"Fn(&mut ModifyResponse)")
1702                .field("cache_status_headers", &self.cache_status_headers)
1703                .field("max_ttl", &self.max_ttl)
1704                .field(
1705                    "metadata_provider",
1706                    &"Fn(&request::Parts, &response::Parts) -> Option<Vec<u8>>",
1707                )
1708                .finish()
1709        }
1710    }
1711}
1712
1713impl HttpCacheOptions {
1714    fn create_cache_key(
1715        &self,
1716        parts: &request::Parts,
1717        override_method: Option<&str>,
1718    ) -> String {
1719        if let Some(cache_key) = &self.cache_key {
1720            cache_key(parts)
1721        } else {
1722            format!(
1723                "{}:{}",
1724                override_method.unwrap_or_else(|| parts.method.as_str()),
1725                parts.uri
1726            )
1727        }
1728    }
1729
1730    /// Helper function for other crates to generate cache keys for invalidation
1731    /// This ensures consistent cache key generation across all implementations
1732    pub fn create_cache_key_for_invalidation(
1733        &self,
1734        parts: &request::Parts,
1735        method_override: &str,
1736    ) -> String {
1737        self.create_cache_key(parts, Some(method_override))
1738    }
1739
1740    /// Converts HttpResponse to http::Response with the given body type
1741    pub fn http_response_to_response<B>(
1742        http_response: &HttpResponse,
1743        body: B,
1744    ) -> Result<Response<B>> {
1745        let mut response_builder = Response::builder()
1746            .status(http_response.status)
1747            .version(http_response.version.into());
1748
1749        for (name, value) in &http_response.headers {
1750            if let (Ok(header_name), Ok(header_value)) =
1751                (name.parse::<http::HeaderName>(), value.parse::<HeaderValue>())
1752            {
1753                response_builder =
1754                    response_builder.header(header_name, header_value);
1755            }
1756        }
1757
1758        Ok(response_builder.body(body)?)
1759    }
1760
1761    /// Converts response parts to HttpResponse format for cache mode evaluation
1762    fn parts_to_http_response(
1763        &self,
1764        parts: &response::Parts,
1765        request_parts: &request::Parts,
1766        metadata: Option<Vec<u8>>,
1767    ) -> Result<HttpResponse> {
1768        Ok(HttpResponse {
1769            body: vec![], // We don't need the full body for cache mode decision
1770            headers: (&parts.headers).into(),
1771            status: parts.status.as_u16(),
1772            url: extract_url_from_request_parts(request_parts)?,
1773            version: parts.version.try_into()?,
1774            metadata,
1775        })
1776    }
1777
1778    /// Evaluates response-based cache mode override
1779    fn evaluate_response_cache_mode(
1780        &self,
1781        request_parts: &request::Parts,
1782        http_response: &HttpResponse,
1783        original_mode: CacheMode,
1784    ) -> CacheMode {
1785        if let Some(response_cache_mode_fn) = &self.response_cache_mode_fn {
1786            if let Some(override_mode) =
1787                response_cache_mode_fn(request_parts, http_response)
1788            {
1789                return override_mode;
1790            }
1791        }
1792        original_mode
1793    }
1794
1795    /// Generates metadata for a response using the metadata_provider callback if configured
1796    pub fn generate_metadata(
1797        &self,
1798        request_parts: &request::Parts,
1799        response_parts: &response::Parts,
1800    ) -> Option<HttpCacheMetadata> {
1801        self.metadata_provider
1802            .as_ref()
1803            .and_then(|provider| provider(request_parts, response_parts))
1804    }
1805
1806    /// Modifies the response before caching if a modifier function is provided
1807    pub fn modify_response_before_caching(&self, response: &mut HttpResponse) {
1808        if let Some(modify_response) = &self.modify_response {
1809            modify_response(response);
1810        }
1811    }
1812
1813    /// Creates a cache policy for the given request and response
1814    fn create_cache_policy(
1815        &self,
1816        request_parts: &request::Parts,
1817        response_parts: &response::Parts,
1818    ) -> CachePolicy {
1819        let cache_options = self.cache_options.unwrap_or_default();
1820
1821        // If max_ttl is specified, we need to modify the response headers to enforce it
1822        if let Some(max_ttl) = self.max_ttl {
1823            // Parse existing cache-control header
1824            let cache_control = response_parts
1825                .headers
1826                .get("cache-control")
1827                .and_then(|v| v.to_str().ok())
1828                .unwrap_or("");
1829
1830            // Extract existing max-age if present
1831            let existing_max_age =
1832                cache_control.split(',').find_map(|directive| {
1833                    let directive = directive.trim();
1834                    if directive.starts_with("max-age=") {
1835                        directive.strip_prefix("max-age=")?.parse::<u64>().ok()
1836                    } else {
1837                        None
1838                    }
1839                });
1840
1841            // Convert max_ttl to seconds
1842            let max_ttl_seconds = max_ttl.as_secs();
1843
1844            // Apply max_ttl by setting max-age to the minimum of existing max-age and max_ttl
1845            let effective_max_age = match existing_max_age {
1846                Some(existing) => std::cmp::min(existing, max_ttl_seconds),
1847                None => max_ttl_seconds,
1848            };
1849
1850            // Build new cache-control header
1851            let mut new_directives = Vec::new();
1852
1853            // Add non-max-age directives from existing cache-control
1854            for directive in cache_control.split(',').map(|d| d.trim()) {
1855                if !directive.starts_with("max-age=") && !directive.is_empty() {
1856                    new_directives.push(directive.to_string());
1857                }
1858            }
1859
1860            // Add our effective max-age
1861            new_directives.push(format!("max-age={}", effective_max_age));
1862
1863            let new_cache_control = new_directives.join(", ");
1864
1865            // Create modified response parts - we have to clone since response::Parts has private fields
1866            let mut modified_response_parts = response_parts.clone();
1867            modified_response_parts.headers.insert(
1868                "cache-control",
1869                HeaderValue::from_str(&new_cache_control)
1870                    .unwrap_or_else(|_| HeaderValue::from_static("max-age=0")),
1871            );
1872
1873            CachePolicy::new_options(
1874                request_parts,
1875                &modified_response_parts,
1876                SystemTime::now(),
1877                cache_options,
1878            )
1879        } else {
1880            CachePolicy::new_options(
1881                request_parts,
1882                response_parts,
1883                SystemTime::now(),
1884                cache_options,
1885            )
1886        }
1887    }
1888
1889    /// Determines if a response should be cached based on cache mode and HTTP semantics
1890    fn should_cache_response(
1891        &self,
1892        effective_cache_mode: CacheMode,
1893        http_response: &HttpResponse,
1894        is_get_head: bool,
1895        policy: &CachePolicy,
1896    ) -> bool {
1897        // HTTP status codes that are cacheable by default (RFC 7234)
1898        let is_cacheable_status = matches!(
1899            http_response.status,
1900            200 | 203 | 204 | 206 | 300 | 301 | 404 | 405 | 410 | 414 | 501
1901        );
1902
1903        if is_cacheable_status {
1904            match effective_cache_mode {
1905                CacheMode::ForceCache => is_get_head,
1906                CacheMode::IgnoreRules => true,
1907                CacheMode::NoStore => false,
1908                _ => is_get_head && policy.is_storable(),
1909            }
1910        } else {
1911            false
1912        }
1913    }
1914
1915    /// Common request analysis logic shared between streaming and non-streaming implementations
1916    fn analyze_request_internal(
1917        &self,
1918        parts: &request::Parts,
1919        mode_override: Option<CacheMode>,
1920        default_mode: CacheMode,
1921    ) -> Result<CacheAnalysis> {
1922        let effective_mode = mode_override
1923            .or_else(|| self.cache_mode_fn.as_ref().map(|f| f(parts)))
1924            .unwrap_or(default_mode);
1925
1926        let is_get_head = parts.method == "GET" || parts.method == "HEAD";
1927        let should_cache = effective_mode == CacheMode::IgnoreRules
1928            || (is_get_head && effective_mode != CacheMode::NoStore);
1929
1930        let cache_key = self.create_cache_key(parts, None);
1931
1932        let cache_bust_keys = if let Some(cache_bust) = &self.cache_bust {
1933            cache_bust(parts, &self.cache_key, &cache_key)
1934        } else {
1935            Vec::new()
1936        };
1937
1938        Ok(CacheAnalysis {
1939            cache_key,
1940            should_cache,
1941            cache_mode: effective_mode,
1942            cache_bust_keys,
1943            request_parts: parts.clone(),
1944            is_get_head,
1945        })
1946    }
1947}
1948
1949/// Caches requests according to http spec.
1950#[derive(Debug, Clone)]
1951pub struct HttpCache<T: CacheManager> {
1952    /// Determines the manager behavior.
1953    pub mode: CacheMode,
1954    /// Manager instance that implements the [`CacheManager`] trait.
1955    /// By default, a manager implementation with [`cacache`](https://github.com/zkat/cacache-rs)
1956    /// as the backend has been provided, see [`CACacheManager`].
1957    pub manager: T,
1958    /// Override the default cache options.
1959    pub options: HttpCacheOptions,
1960}
1961
1962/// Wrapper for user metadata stored in response extensions during cache reads.
1963/// Used to preserve metadata through 304 re-cache operations so that
1964/// `StreamingCacheManager::put` receives the original metadata instead of
1965/// regenerating it (which may produce different or empty results).
1966#[derive(Debug, Clone)]
1967pub(crate) struct CachedUserMetadata(pub Option<Vec<u8>>);
1968
1969/// Streaming version of HTTP cache that supports streaming request/response bodies
1970/// without buffering them in memory.
1971#[derive(Debug, Clone)]
1972pub struct HttpStreamingCache<T: StreamingCacheManager> {
1973    /// Determines the manager behavior.
1974    pub mode: CacheMode,
1975    /// Manager instance that implements the [`StreamingCacheManager`] trait.
1976    pub manager: T,
1977    /// Override the default cache options.
1978    pub options: HttpCacheOptions,
1979}
1980
1981// ============================================================================
1982// Helper functions for working with warning headers on http::Response
1983// ============================================================================
1984
1985/// Extracts the warning code from an `http::Response`'s warning header, if
1986/// present.  Returns the 3-digit warn-code as a `usize`.
1987fn response_warning_code<B>(response: &Response<B>) -> Option<usize> {
1988    response
1989        .headers()
1990        .get(WARNING)
1991        .and_then(|hdr| hdr.to_str().ok())
1992        .and_then(|s| s.chars().take(3).collect::<String>().parse().ok())
1993}
1994
1995/// Adds an RFC 2616 §14.46 warning header to an `http::Response`.
1996fn response_add_warning<B>(
1997    response: &mut Response<B>,
1998    url: &Url,
1999    code: usize,
2000    message: &str,
2001) {
2002    let host = url_host_str(url);
2003    let escaped_message = message.replace('"', "'").replace(['\n', '\r'], " ");
2004    let value = format!(
2005        "{} {} \"{}\" \"{}\"",
2006        code,
2007        host,
2008        escaped_message,
2009        httpdate::fmt_http_date(SystemTime::now()),
2010    );
2011    if let Ok(hv) = HeaderValue::from_str(&value) {
2012        response.headers_mut().insert(WARNING, hv);
2013    }
2014}
2015
2016/// Removes the warning header from an `http::Response`.
2017fn response_remove_warning<B>(response: &mut Response<B>) {
2018    response.headers_mut().remove(WARNING);
2019}
2020
2021/// Returns `true` if the `cache-control` header of the response contains the
2022/// `must-revalidate` directive.
2023fn response_must_revalidate<B>(response: &Response<B>) -> bool {
2024    response
2025        .headers()
2026        .get(CACHE_CONTROL)
2027        .and_then(|v| v.to_str().ok())
2028        .is_some_and(|val| val.to_lowercase().contains("must-revalidate"))
2029}
2030
2031/// Adds the custom `x-cache` status header to an `http::Response`.
2032fn response_cache_status<B>(
2033    response: &mut Response<B>,
2034    hit_or_miss: HitOrMiss,
2035) {
2036    if let Ok(hv) = HeaderValue::from_str(&hit_or_miss.to_string()) {
2037        response.headers_mut().insert(XCACHE, hv);
2038    }
2039}
2040
2041/// Adds the custom `x-cache-lookup` status header to an `http::Response`.
2042fn response_cache_lookup_status<B>(
2043    response: &mut Response<B>,
2044    hit_or_miss: HitOrMiss,
2045) {
2046    if let Ok(hv) = HeaderValue::from_str(&hit_or_miss.to_string()) {
2047        response.headers_mut().insert(XCACHELOOKUP, hv);
2048    }
2049}
2050
2051/// Applies [`HttpCacheOptions::modify_response_before_caching`] to a
2052/// streaming `Response<B>`.  The callback expects `&mut HttpResponse`, so we
2053/// build a temporary shim with the response's headers/status (empty body),
2054/// call the callback, and copy any header or status changes back.
2055///
2056/// Body and metadata modifications made by the callback are not reflected
2057/// because the streaming body is not buffered.
2058fn apply_modify_response_shim<B>(
2059    options: &HttpCacheOptions,
2060    response: &mut Response<B>,
2061    url: &Url,
2062) {
2063    let modify = match &options.modify_response {
2064        Some(f) => f,
2065        None => return,
2066    };
2067    let mut shim = HttpResponse {
2068        body: Vec::new(),
2069        headers: HttpHeaders::from(response.headers()),
2070        status: response.status().as_u16(),
2071        url: url.clone(),
2072        version: response.version().try_into().unwrap_or(HttpVersion::Http11),
2073        metadata: None,
2074    };
2075    modify(&mut shim);
2076    // Apply header changes back
2077    response.headers_mut().clear();
2078    for (name, value) in shim.headers.iter() {
2079        if let (Ok(hn), Ok(hv)) = (
2080            http::header::HeaderName::from_bytes(name.as_bytes()),
2081            HeaderValue::from_str(value),
2082        ) {
2083            response.headers_mut().append(hn, hv);
2084        }
2085    }
2086    // Apply status change back
2087    if let Ok(new_status) = StatusCode::from_u16(shim.status) {
2088        *response.status_mut() = new_status;
2089    }
2090}
2091
2092// ============================================================================
2093// HttpStreamingCache orchestrator methods
2094// ============================================================================
2095
2096impl<T: StreamingCacheManager> HttpStreamingCache<T>
2097where
2098    <T::Body as http_body::Body>::Data: Send,
2099    <T::Body as http_body::Body>::Error:
2100        Into<StreamingError> + Send + Sync + 'static,
2101{
2102    /// Determines if the request described by `parts` should be cached,
2103    /// taking into account any `mode_override`.
2104    pub fn can_cache_request(
2105        &self,
2106        parts: &request::Parts,
2107        mode_override: Option<CacheMode>,
2108    ) -> Result<bool> {
2109        let analysis = <Self as HttpCacheStreamInterface>::analyze_request(
2110            self,
2111            parts,
2112            mode_override,
2113        )?;
2114        Ok(analysis.should_cache)
2115    }
2116
2117    /// Apply rate limiting if enabled in options.
2118    #[cfg(feature = "rate-limiting")]
2119    async fn apply_rate_limiting(&self, url: &Url) {
2120        if let Some(rate_limiter) = &self.options.rate_limiter {
2121            let rate_limit_key = url_hostname(url).unwrap_or("unknown");
2122            rate_limiter.until_key_ready(rate_limit_key).await;
2123        }
2124    }
2125
2126    /// Apply rate limiting if enabled in options (no-op without
2127    /// rate-limiting feature).
2128    #[cfg(not(feature = "rate-limiting"))]
2129    async fn apply_rate_limiting(&self, _url: &Url) {
2130        // No-op when rate limiting feature is not enabled
2131    }
2132
2133    /// Performs cache-busting housekeeping for requests that should not be
2134    /// cached.  Mirrors [`HttpCache::run_no_cache`].
2135    pub async fn run_no_cache(&self, parts: &request::Parts) -> Result<()> {
2136        self.manager
2137            .delete(&self.options.create_cache_key(parts, Some("GET")))
2138            .await
2139            .ok();
2140        self.manager
2141            .delete(&self.options.create_cache_key(parts, Some("HEAD")))
2142            .await
2143            .ok();
2144
2145        let cache_key = self.options.create_cache_key(parts, None);
2146
2147        if let Some(cache_bust) = &self.options.cache_bust {
2148            for key_to_cache_bust in
2149                cache_bust(parts, &self.options.cache_key, &cache_key)
2150            {
2151                self.manager.delete(&key_to_cache_bust).await?;
2152            }
2153        }
2154
2155        Ok(())
2156    }
2157
2158    /// The main streaming cache orchestrator.
2159    ///
2160    /// This mirrors the logic of [`HttpCache::run`] but operates on
2161    /// streaming `Response<B>` bodies and delegates upstream fetching to a
2162    /// caller-supplied callback instead of an `impl Middleware`.
2163    ///
2164    /// # Arguments
2165    ///
2166    /// * `parts` - The request parts to evaluate.
2167    /// * `mode_override` - Optional per-request cache mode override.
2168    /// * `fetch` - A callback that performs the actual HTTP request.  It
2169    ///   receives a [`FetchRequest`] indicating whether to issue a fresh or
2170    ///   conditional request, and must return the upstream `Response<B>`.
2171    ///   Called at most once per request.
2172    pub async fn run<B, F, Fut>(
2173        &self,
2174        parts: &request::Parts,
2175        mode_override: Option<CacheMode>,
2176        fetch: F,
2177    ) -> Result<Response<T::Body>>
2178    where
2179        B: http_body::Body + Send + 'static,
2180        B::Data: Send,
2181        B::Error: Into<StreamingError>,
2182        F: FnOnce(FetchRequest) -> Fut,
2183        Fut: Future<Output = Result<Response<B>>>,
2184    {
2185        // 1. Analyze the request
2186        let analysis = <Self as HttpCacheStreamInterface>::analyze_request(
2187            self,
2188            parts,
2189            mode_override,
2190        )?;
2191
2192        // 2. If the request should not be cached, fetch and process as a
2193        //    remote miss.
2194        if !analysis.should_cache {
2195            let url = extract_url_from_request_parts(parts)?;
2196            self.apply_rate_limiting(&url).await;
2197            let response = fetch(FetchRequest::Fresh).await?;
2198            return self.remote_fetch_and_cache(analysis, response).await;
2199        }
2200
2201        // 3. Bust cache keys if needed
2202        for key in &analysis.cache_bust_keys {
2203            self.manager.delete(key).await?;
2204        }
2205
2206        // 4. Look up cached response
2207        if let Some((mut cached_response, policy)) =
2208            <Self as HttpCacheStreamInterface>::lookup_cached_response(
2209                self,
2210                &analysis.cache_key,
2211            )
2212            .await?
2213        {
2214            if self.options.cache_status_headers {
2215                response_cache_lookup_status(
2216                    &mut cached_response,
2217                    HitOrMiss::HIT,
2218                );
2219            }
2220
2221            // Handle warning headers per RFC 7234 §4.3.4
2222            if let Some(warning_code) = response_warning_code(&cached_response)
2223            {
2224                if (100..200).contains(&warning_code) {
2225                    response_remove_warning(&mut cached_response);
2226                }
2227            }
2228
2229            // 5. Branch on cache mode
2230            match analysis.cache_mode {
2231                CacheMode::Default => {
2232                    self.conditional_fetch(
2233                        &analysis,
2234                        fetch,
2235                        cached_response,
2236                        policy,
2237                    )
2238                    .await
2239                }
2240                CacheMode::NoCache => {
2241                    // Force a fresh fetch with no-cache directive, but
2242                    // note that we had a cache lookup hit.
2243                    let url = extract_url_from_request_parts(parts)?;
2244                    self.apply_rate_limiting(&url).await;
2245                    let response = fetch(FetchRequest::FreshNoCache).await?;
2246                    let mut res =
2247                        self.remote_fetch_and_cache(analysis, response).await?;
2248                    if self.options.cache_status_headers {
2249                        response_cache_lookup_status(&mut res, HitOrMiss::HIT);
2250                    }
2251                    Ok(res)
2252                }
2253                CacheMode::ForceCache
2254                | CacheMode::OnlyIfCached
2255                | CacheMode::IgnoreRules => {
2256                    //   112 Disconnected operation
2257                    // SHOULD be included if the cache is intentionally
2258                    // disconnected from the rest of the network for a
2259                    // period of time.
2260                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
2261                    let url = extract_url_from_request_parts(parts)?;
2262                    response_add_warning(
2263                        &mut cached_response,
2264                        &url,
2265                        112,
2266                        "Disconnected operation",
2267                    );
2268                    if self.options.cache_status_headers {
2269                        response_cache_status(
2270                            &mut cached_response,
2271                            HitOrMiss::HIT,
2272                        );
2273                    }
2274                    Ok(cached_response)
2275                }
2276                CacheMode::Reload => {
2277                    let url = extract_url_from_request_parts(parts)?;
2278                    self.apply_rate_limiting(&url).await;
2279                    let response = fetch(FetchRequest::Fresh).await?;
2280                    let mut res =
2281                        self.remote_fetch_and_cache(analysis, response).await?;
2282                    if self.options.cache_status_headers {
2283                        response_cache_lookup_status(&mut res, HitOrMiss::HIT);
2284                    }
2285                    Ok(res)
2286                }
2287                _ => {
2288                    let url = extract_url_from_request_parts(parts)?;
2289                    self.apply_rate_limiting(&url).await;
2290                    let response = fetch(FetchRequest::Fresh).await?;
2291                    self.remote_fetch_and_cache(analysis, response).await
2292                }
2293            }
2294        } else {
2295            // 6. No cached response found
2296            match analysis.cache_mode {
2297                CacheMode::OnlyIfCached => {
2298                    // ENOTCACHED — return 504 Gateway Timeout
2299                    let mut res = Response::builder()
2300                        .status(StatusCode::GATEWAY_TIMEOUT)
2301                        .body(self.manager.empty_body())
2302                        .map_err(|e| -> BoxError { e.into() })?;
2303                    if self.options.cache_status_headers {
2304                        response_cache_status(&mut res, HitOrMiss::MISS);
2305                        response_cache_lookup_status(&mut res, HitOrMiss::MISS);
2306                    }
2307                    Ok(res)
2308                }
2309                _ => {
2310                    let url = extract_url_from_request_parts(parts)?;
2311                    self.apply_rate_limiting(&url).await;
2312                    let response = fetch(FetchRequest::Fresh).await?;
2313                    self.remote_fetch_and_cache(analysis, response).await
2314                }
2315            }
2316        }
2317    }
2318
2319    /// Processes a fresh upstream response and potentially caches it.
2320    ///
2321    /// Mirrors [`HttpCache::remote_fetch`] but receives the response
2322    /// directly rather than calling middleware.  Rate limiting is performed
2323    /// by the caller before invoking `fetch`.
2324    async fn remote_fetch_and_cache<B>(
2325        &self,
2326        analysis: CacheAnalysis,
2327        response: Response<B>,
2328    ) -> Result<Response<T::Body>>
2329    where
2330        B: http_body::Body + Send + 'static,
2331        B::Data: Send,
2332        B::Error: Into<StreamingError>,
2333    {
2334        // Delegate to process_response which handles:
2335        //   - response-based cache mode override evaluation
2336        //   - policy creation
2337        //   - should_cache_response check
2338        //   - cache busting for non-GET/HEAD
2339        //   - storing via manager.put or converting via manager.convert_body
2340        //   - adding cache status headers (MISS/MISS)
2341        //   - applying modify_response_before_caching shim before put
2342        let res = <Self as HttpCacheStreamInterface>::process_response(
2343            self,
2344            analysis.clone(),
2345            response,
2346            None,
2347        )
2348        .await?;
2349
2350        Ok(res)
2351    }
2352
2353    /// Performs a conditional fetch (revalidation) against the origin,
2354    /// returning either the still-valid cached response or the fresh
2355    /// upstream response.
2356    ///
2357    /// Mirrors [`HttpCache::conditional_fetch`].
2358    async fn conditional_fetch<B, F, Fut>(
2359        &self,
2360        analysis: &CacheAnalysis,
2361        fetch: F,
2362        mut cached_res: Response<T::Body>,
2363        mut policy: CachePolicy,
2364    ) -> Result<Response<T::Body>>
2365    where
2366        B: http_body::Body + Send + 'static,
2367        B::Data: Send,
2368        B::Error: Into<StreamingError>,
2369        F: FnOnce(FetchRequest) -> Fut,
2370        Fut: Future<Output = Result<Response<B>>>,
2371    {
2372        let parts = &analysis.request_parts;
2373        let before_req = policy.before_request(parts, SystemTime::now());
2374        match before_req {
2375            BeforeRequest::Fresh(fresh_parts) => {
2376                // Update headers from the policy result, preserving multi-valued headers
2377                for name in fresh_parts.headers.keys() {
2378                    cached_res.headers_mut().remove(name);
2379                }
2380                for (name, value) in fresh_parts.headers.iter() {
2381                    cached_res
2382                        .headers_mut()
2383                        .append(name.clone(), value.clone());
2384                }
2385                if self.options.cache_status_headers {
2386                    response_cache_status(&mut cached_res, HitOrMiss::HIT);
2387                    response_cache_lookup_status(
2388                        &mut cached_res,
2389                        HitOrMiss::HIT,
2390                    );
2391                }
2392                Ok(cached_res)
2393            }
2394            BeforeRequest::Stale { request: stale_parts, matches } => {
2395                let req_url = extract_url_from_request_parts(parts)?;
2396                // Apply rate limiting before revalidation request
2397                self.apply_rate_limiting(&req_url).await;
2398
2399                // Only send conditional headers when matches is true
2400                // (matching reference behavior at HttpCache::conditional_fetch)
2401                let fetch_result = if matches {
2402                    fetch(FetchRequest::Conditional(Box::new(stale_parts)))
2403                        .await
2404                } else {
2405                    fetch(FetchRequest::Fresh).await
2406                };
2407
2408                match fetch_result {
2409                    Ok(cond_res) => {
2410                        let status = cond_res.status();
2411
2412                        if status.is_server_error()
2413                            && response_must_revalidate(&cached_res)
2414                        {
2415                            //   111 Revalidation failed
2416                            //   MUST be included if a cache returns a
2417                            //   stale response because an attempt to
2418                            //   revalidate the response failed, due to an
2419                            //   inability to reach the server.
2420                            // (https://tools.ietf.org/html/rfc2616#section-14.46)
2421                            response_add_warning(
2422                                &mut cached_res,
2423                                &req_url,
2424                                111,
2425                                "Revalidation failed",
2426                            );
2427                            if self.options.cache_status_headers {
2428                                response_cache_status(
2429                                    &mut cached_res,
2430                                    HitOrMiss::HIT,
2431                                );
2432                            }
2433                            Ok(cached_res)
2434                        } else if status == StatusCode::NOT_MODIFIED {
2435                            // 304 Not Modified — update cached response
2436                            // headers using policy.after_response
2437                            let (cond_parts, _cond_body) =
2438                                cond_res.into_parts();
2439                            let after_res = policy.after_response(
2440                                parts,
2441                                &cond_parts,
2442                                SystemTime::now(),
2443                            );
2444                            match after_res {
2445                                AfterResponse::Modified(
2446                                    new_policy,
2447                                    new_parts,
2448                                )
2449                                | AfterResponse::NotModified(
2450                                    new_policy,
2451                                    new_parts,
2452                                ) => {
2453                                    policy = new_policy;
2454                                    // Update cached response headers, preserving multi-valued headers
2455                                    for name in new_parts.headers.keys() {
2456                                        cached_res.headers_mut().remove(name);
2457                                    }
2458                                    for (name, value) in
2459                                        new_parts.headers.iter()
2460                                    {
2461                                        cached_res.headers_mut().append(
2462                                            name.clone(),
2463                                            value.clone(),
2464                                        );
2465                                    }
2466                                }
2467                            }
2468                            if self.options.cache_status_headers {
2469                                response_cache_status(
2470                                    &mut cached_res,
2471                                    HitOrMiss::HIT,
2472                                );
2473                                response_cache_lookup_status(
2474                                    &mut cached_res,
2475                                    HitOrMiss::HIT,
2476                                );
2477                            }
2478
2479                            apply_modify_response_shim(
2480                                &self.options,
2481                                &mut cached_res,
2482                                &req_url,
2483                            );
2484
2485                            // Re-cache the updated response.  Preserve
2486                            // original user metadata from the cached
2487                            // response instead of regenerating it (which
2488                            // may produce different or empty results).
2489                            let request_url =
2490                                extract_url_from_request_parts(parts)?;
2491                            let metadata = cached_res
2492                                .extensions()
2493                                .get::<CachedUserMetadata>()
2494                                .and_then(|m| m.0.clone());
2495
2496                            let res = self
2497                                .manager
2498                                .put(
2499                                    self.options.create_cache_key(parts, None),
2500                                    cached_res,
2501                                    policy,
2502                                    request_url,
2503                                    metadata,
2504                                )
2505                                .await?;
2506                            Ok(res)
2507                        } else if status == StatusCode::OK {
2508                            // 200 OK — fresh response, create new policy
2509                            // and cache
2510                            let (cond_parts, cond_body) = cond_res.into_parts();
2511                            let new_policy = self
2512                                .options
2513                                .create_cache_policy(parts, &cond_parts);
2514                            let metadata = self
2515                                .options
2516                                .generate_metadata(parts, &cond_parts);
2517                            let cond_res =
2518                                Response::from_parts(cond_parts, cond_body);
2519
2520                            let request_url =
2521                                extract_url_from_request_parts(parts)?;
2522
2523                            // Apply modify_response BEFORE cacheability checks
2524                            // (matches non-streaming reference order)
2525                            let mut cond_res = cond_res;
2526                            apply_modify_response_shim(
2527                                &self.options,
2528                                &mut cond_res,
2529                                &request_url,
2530                            );
2531
2532                            // Build HttpResponse shim from modified response
2533                            let http_response_shim = HttpResponse {
2534                                body: vec![],
2535                                headers: cond_res.headers().into(),
2536                                status: cond_res.status().as_u16(),
2537                                url: request_url.clone(),
2538                                version: cond_res
2539                                    .version()
2540                                    .try_into()
2541                                    .unwrap_or(HttpVersion::Http11),
2542                                metadata: metadata.clone(),
2543                            };
2544                            // Apply response-based cache mode override
2545                            let effective_mode =
2546                                self.options.evaluate_response_cache_mode(
2547                                    parts,
2548                                    &http_response_shim,
2549                                    analysis.cache_mode,
2550                                );
2551                            let is_cacheable =
2552                                self.options.should_cache_response(
2553                                    effective_mode,
2554                                    &http_response_shim,
2555                                    analysis.is_get_head,
2556                                    &new_policy,
2557                                );
2558
2559                            // Set cache status headers
2560                            if self.options.cache_status_headers {
2561                                response_cache_status(
2562                                    &mut cond_res,
2563                                    HitOrMiss::MISS,
2564                                );
2565                                response_cache_lookup_status(
2566                                    &mut cond_res,
2567                                    HitOrMiss::HIT,
2568                                );
2569                            }
2570
2571                            if is_cacheable {
2572                                let res = self
2573                                    .manager
2574                                    .put(
2575                                        self.options
2576                                            .create_cache_key(parts, None),
2577                                        cond_res,
2578                                        new_policy,
2579                                        request_url,
2580                                        metadata,
2581                                    )
2582                                    .await?;
2583                                Ok(res)
2584                            } else {
2585                                let res =
2586                                    self.manager.convert_body(cond_res).await?;
2587                                Ok(res)
2588                            }
2589                        } else {
2590                            // Any other status — return fresh response
2591                            let mut res =
2592                                self.manager.convert_body(cond_res).await?;
2593                            if self.options.cache_status_headers {
2594                                response_cache_status(
2595                                    &mut res,
2596                                    HitOrMiss::MISS,
2597                                );
2598                                response_cache_lookup_status(
2599                                    &mut res,
2600                                    HitOrMiss::HIT,
2601                                );
2602                            }
2603                            Ok(res)
2604                        }
2605                    }
2606                    Err(e) => {
2607                        if response_must_revalidate(&cached_res) {
2608                            Err(e)
2609                        } else {
2610                            //   111 Revalidation failed
2611                            //   MUST be included if a cache returns a
2612                            //   stale response because an attempt to
2613                            //   revalidate the response failed, due to an
2614                            //   inability to reach the server.
2615                            // (https://tools.ietf.org/html/rfc2616#section-14.46)
2616                            response_add_warning(
2617                                &mut cached_res,
2618                                &req_url,
2619                                111,
2620                                "Revalidation failed",
2621                            );
2622                            if self.options.cache_status_headers {
2623                                response_cache_status(
2624                                    &mut cached_res,
2625                                    HitOrMiss::HIT,
2626                                );
2627                            }
2628                            Ok(cached_res)
2629                        }
2630                    }
2631                }
2632            }
2633        }
2634    }
2635}
2636
2637impl<T: CacheManager> HttpCache<T> {
2638    /// Determines if the request should be cached
2639    pub fn can_cache_request(
2640        &self,
2641        middleware: &impl Middleware,
2642    ) -> Result<bool> {
2643        let analysis = self.analyze_request(
2644            &middleware.parts()?,
2645            middleware.overridden_cache_mode(),
2646        )?;
2647        Ok(analysis.should_cache)
2648    }
2649
2650    /// Apply rate limiting if enabled in options
2651    #[cfg(feature = "rate-limiting")]
2652    async fn apply_rate_limiting(&self, url: &Url) {
2653        if let Some(rate_limiter) = &self.options.rate_limiter {
2654            let rate_limit_key = url_hostname(url).unwrap_or("unknown");
2655            rate_limiter.until_key_ready(rate_limit_key).await;
2656        }
2657    }
2658
2659    /// Apply rate limiting if enabled in options (no-op without rate-limiting feature)
2660    #[cfg(not(feature = "rate-limiting"))]
2661    async fn apply_rate_limiting(&self, _url: &Url) {
2662        // No-op when rate limiting feature is not enabled
2663    }
2664
2665    /// Cache-busting for non-cacheable requests, taking pre-extracted parts.
2666    pub async fn run_no_cache_from_parts(
2667        &self,
2668        parts: &request::Parts,
2669    ) -> Result<()> {
2670        self.manager
2671            .delete(&self.options.create_cache_key(parts, Some("GET")))
2672            .await
2673            .ok();
2674        self.manager
2675            .delete(&self.options.create_cache_key(parts, Some("HEAD")))
2676            .await
2677            .ok();
2678
2679        let cache_key = self.options.create_cache_key(parts, None);
2680
2681        if let Some(cache_bust) = &self.options.cache_bust {
2682            for key_to_cache_bust in
2683                cache_bust(parts, &self.options.cache_key, &cache_key)
2684            {
2685                self.manager.delete(&key_to_cache_bust).await?;
2686            }
2687        }
2688
2689        Ok(())
2690    }
2691
2692    /// Runs the actions to perform when the client middleware is running without the cache
2693    pub async fn run_no_cache(
2694        &self,
2695        middleware: &mut impl Middleware,
2696    ) -> Result<()> {
2697        let parts = middleware.parts()?;
2698        self.run_no_cache_from_parts(&parts).await
2699    }
2700
2701    /// Attempts to run the passed middleware along with the cache
2702    pub async fn run(
2703        &self,
2704        mut middleware: impl Middleware,
2705    ) -> Result<HttpResponse> {
2706        // Use the HttpCacheInterface to analyze the request
2707        let analysis = self.analyze_request(
2708            &middleware.parts()?,
2709            middleware.overridden_cache_mode(),
2710        )?;
2711
2712        if !analysis.should_cache {
2713            return self.remote_fetch(&mut middleware).await;
2714        }
2715
2716        // Bust cache keys if needed
2717        for key in &analysis.cache_bust_keys {
2718            self.manager.delete(key).await?;
2719        }
2720
2721        // Look up cached response
2722        if let Some((mut cached_response, policy)) =
2723            self.lookup_cached_response(&analysis.cache_key).await?
2724        {
2725            if self.options.cache_status_headers {
2726                cached_response.cache_lookup_status(HitOrMiss::HIT);
2727            }
2728
2729            // Handle warning headers
2730            if let Some(warning_code) = cached_response.warning_code() {
2731                // https://tools.ietf.org/html/rfc7234#section-4.3.4
2732                //
2733                // If a stored response is selected for update, the cache MUST:
2734                //
2735                // * delete any warning header fields in the stored response with
2736                //   warn-code 1xx (see Section 5.5);
2737                //
2738                // * retain any warning header fields in the stored response with
2739                //   warn-code 2xx;
2740                //
2741                if (100..200).contains(&warning_code) {
2742                    cached_response.remove_warning();
2743                }
2744            }
2745
2746            match analysis.cache_mode {
2747                CacheMode::Default => {
2748                    self.conditional_fetch(middleware, cached_response, policy)
2749                        .await
2750                }
2751                CacheMode::NoCache => {
2752                    middleware.force_no_cache()?;
2753                    let mut res = self.remote_fetch(&mut middleware).await?;
2754                    if self.options.cache_status_headers {
2755                        res.cache_lookup_status(HitOrMiss::HIT);
2756                    }
2757                    Ok(res)
2758                }
2759                CacheMode::ForceCache
2760                | CacheMode::OnlyIfCached
2761                | CacheMode::IgnoreRules => {
2762                    //   112 Disconnected operation
2763                    // SHOULD be included if the cache is intentionally disconnected from
2764                    // the rest of the network for a period of time.
2765                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
2766                    cached_response.add_warning(
2767                        &cached_response.url.clone(),
2768                        112,
2769                        "Disconnected operation",
2770                    );
2771                    if self.options.cache_status_headers {
2772                        cached_response.cache_status(HitOrMiss::HIT);
2773                    }
2774                    Ok(cached_response)
2775                }
2776                CacheMode::Reload => {
2777                    let mut res = self.remote_fetch(&mut middleware).await?;
2778                    if self.options.cache_status_headers {
2779                        res.cache_lookup_status(HitOrMiss::HIT);
2780                    }
2781                    Ok(res)
2782                }
2783                _ => self.remote_fetch(&mut middleware).await,
2784            }
2785        } else {
2786            match analysis.cache_mode {
2787                CacheMode::OnlyIfCached => {
2788                    // ENOTCACHED
2789                    let mut res = HttpResponse {
2790                        body: Vec::new(),
2791                        headers: HttpHeaders::default(),
2792                        status: 504,
2793                        url: middleware.url()?,
2794                        version: HttpVersion::Http11,
2795                        metadata: None,
2796                    };
2797                    if self.options.cache_status_headers {
2798                        res.cache_status(HitOrMiss::MISS);
2799                        res.cache_lookup_status(HitOrMiss::MISS);
2800                    }
2801                    Ok(res)
2802                }
2803                _ => self.remote_fetch(&mut middleware).await,
2804            }
2805        }
2806    }
2807
2808    fn cache_mode(&self, middleware: &impl Middleware) -> Result<CacheMode> {
2809        Ok(if let Some(mode) = middleware.overridden_cache_mode() {
2810            mode
2811        } else if let Some(cache_mode_fn) = &self.options.cache_mode_fn {
2812            cache_mode_fn(&middleware.parts()?)
2813        } else {
2814            self.mode
2815        })
2816    }
2817
2818    async fn remote_fetch(
2819        &self,
2820        middleware: &mut impl Middleware,
2821    ) -> Result<HttpResponse> {
2822        // Apply rate limiting before making the network request
2823        let url = middleware.url()?;
2824        self.apply_rate_limiting(&url).await;
2825
2826        let mut res = middleware.remote_fetch().await?;
2827        if self.options.cache_status_headers {
2828            res.cache_status(HitOrMiss::MISS);
2829            res.cache_lookup_status(HitOrMiss::MISS);
2830        }
2831        let policy = match self.options.cache_options {
2832            Some(options) => middleware.policy_with_options(&res, options)?,
2833            None => middleware.policy(&res)?,
2834        };
2835        let is_get_head = middleware.is_method_get_head();
2836        let mut mode = self.cache_mode(middleware)?;
2837        let parts = middleware.parts()?;
2838
2839        // Allow response-based cache mode override
2840        if let Some(response_cache_mode_fn) =
2841            &self.options.response_cache_mode_fn
2842        {
2843            if let Some(override_mode) = response_cache_mode_fn(&parts, &res) {
2844                mode = override_mode;
2845            }
2846        }
2847
2848        let is_cacheable = self.options.should_cache_response(
2849            mode,
2850            &res,
2851            is_get_head,
2852            &policy,
2853        );
2854
2855        if is_cacheable {
2856            // Generate metadata using the provider callback if configured
2857            let response_parts = res.parts()?;
2858            res.metadata =
2859                self.options.generate_metadata(&parts, &response_parts);
2860
2861            self.options.modify_response_before_caching(&mut res);
2862            let res = self
2863                .manager
2864                .put(self.options.create_cache_key(&parts, None), res, policy)
2865                .await?;
2866            // RFC 7234 s4.4: invalidate GET and HEAD keys for non-GET/HEAD successful responses
2867            if !is_get_head {
2868                let status = StatusCode::from_u16(res.status)?;
2869                if status.is_success() || status.is_redirection() {
2870                    self.manager
2871                        .delete(
2872                            &self.options.create_cache_key(&parts, Some("GET")),
2873                        )
2874                        .await
2875                        .ok();
2876                    self.manager
2877                        .delete(
2878                            &self
2879                                .options
2880                                .create_cache_key(&parts, Some("HEAD")),
2881                        )
2882                        .await
2883                        .ok();
2884                }
2885            }
2886            Ok(res)
2887        } else if !is_get_head {
2888            let status = StatusCode::from_u16(res.status)?;
2889            if status.is_success() || status.is_redirection() {
2890                self.manager
2891                    .delete(&self.options.create_cache_key(&parts, Some("GET")))
2892                    .await
2893                    .ok();
2894                self.manager
2895                    .delete(
2896                        &self.options.create_cache_key(&parts, Some("HEAD")),
2897                    )
2898                    .await
2899                    .ok();
2900            }
2901            Ok(res)
2902        } else {
2903            Ok(res)
2904        }
2905    }
2906
2907    async fn conditional_fetch(
2908        &self,
2909        mut middleware: impl Middleware,
2910        mut cached_res: HttpResponse,
2911        mut policy: CachePolicy,
2912    ) -> Result<HttpResponse> {
2913        let parts = middleware.parts()?;
2914        let before_req = policy.before_request(&parts, SystemTime::now());
2915        match before_req {
2916            BeforeRequest::Fresh(parts) => {
2917                cached_res.update_headers(&parts)?;
2918                if self.options.cache_status_headers {
2919                    cached_res.cache_status(HitOrMiss::HIT);
2920                    cached_res.cache_lookup_status(HitOrMiss::HIT);
2921                }
2922                return Ok(cached_res);
2923            }
2924            BeforeRequest::Stale { request: parts, matches } => {
2925                if matches {
2926                    middleware.update_headers(&parts)?;
2927                }
2928            }
2929        }
2930        let req_url = middleware.url()?;
2931        // Apply rate limiting before revalidation request
2932        self.apply_rate_limiting(&req_url).await;
2933        match middleware.remote_fetch().await {
2934            Ok(mut cond_res) => {
2935                let status = StatusCode::from_u16(cond_res.status)?;
2936                if status.is_server_error() && cached_res.must_revalidate() {
2937                    //   111 Revalidation failed
2938                    //   MUST be included if a cache returns a stale response
2939                    //   because an attempt to revalidate the response failed,
2940                    //   due to an inability to reach the server.
2941                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
2942                    cached_res.add_warning(
2943                        &req_url,
2944                        111,
2945                        "Revalidation failed",
2946                    );
2947                    if self.options.cache_status_headers {
2948                        cached_res.cache_status(HitOrMiss::HIT);
2949                    }
2950                    Ok(cached_res)
2951                } else if cond_res.status == 304 {
2952                    let after_res = policy.after_response(
2953                        &parts,
2954                        &cond_res.parts()?,
2955                        SystemTime::now(),
2956                    );
2957                    match after_res {
2958                        AfterResponse::Modified(new_policy, parts)
2959                        | AfterResponse::NotModified(new_policy, parts) => {
2960                            policy = new_policy;
2961                            cached_res.update_headers(&parts)?;
2962                        }
2963                    }
2964                    if self.options.cache_status_headers {
2965                        cached_res.cache_status(HitOrMiss::HIT);
2966                        cached_res.cache_lookup_status(HitOrMiss::HIT);
2967                    }
2968                    self.options
2969                        .modify_response_before_caching(&mut cached_res);
2970                    let res = self
2971                        .manager
2972                        .put(
2973                            self.options.create_cache_key(&parts, None),
2974                            cached_res,
2975                            policy,
2976                        )
2977                        .await?;
2978                    Ok(res)
2979                } else if cond_res.status == 200 {
2980                    let policy = match self.options.cache_options {
2981                        Some(options) => middleware
2982                            .policy_with_options(&cond_res, options)?,
2983                        None => middleware.policy(&cond_res)?,
2984                    };
2985                    if self.options.cache_status_headers {
2986                        cond_res.cache_status(HitOrMiss::MISS);
2987                        cond_res.cache_lookup_status(HitOrMiss::HIT);
2988                    }
2989                    // Generate metadata using the provider callback if configured
2990                    let response_parts = cond_res.parts()?;
2991                    cond_res.metadata =
2992                        self.options.generate_metadata(&parts, &response_parts);
2993
2994                    self.options.modify_response_before_caching(&mut cond_res);
2995
2996                    let mode = self.cache_mode(&middleware)?;
2997                    // Apply response-based cache mode override if configured
2998                    let mode = self
2999                        .options
3000                        .evaluate_response_cache_mode(&parts, &cond_res, mode);
3001                    let is_get_head = middleware.is_method_get_head();
3002                    let is_cacheable = self.options.should_cache_response(
3003                        mode,
3004                        &cond_res,
3005                        is_get_head,
3006                        &policy,
3007                    );
3008
3009                    if is_cacheable {
3010                        let res = self
3011                            .manager
3012                            .put(
3013                                self.options.create_cache_key(&parts, None),
3014                                cond_res,
3015                                policy,
3016                            )
3017                            .await?;
3018                        Ok(res)
3019                    } else {
3020                        Ok(cond_res)
3021                    }
3022                } else {
3023                    // Return fresh response for any status other than 304 or 200
3024                    if self.options.cache_status_headers {
3025                        cond_res.cache_status(HitOrMiss::MISS);
3026                        cond_res.cache_lookup_status(HitOrMiss::HIT);
3027                    }
3028                    Ok(cond_res)
3029                }
3030            }
3031            Err(e) => {
3032                if cached_res.must_revalidate() {
3033                    Err(e)
3034                } else {
3035                    //   111 Revalidation failed
3036                    //   MUST be included if a cache returns a stale response
3037                    //   because an attempt to revalidate the response failed,
3038                    //   due to an inability to reach the server.
3039                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
3040                    cached_res.add_warning(
3041                        &req_url,
3042                        111,
3043                        "Revalidation failed",
3044                    );
3045                    if self.options.cache_status_headers {
3046                        cached_res.cache_status(HitOrMiss::HIT);
3047                    }
3048                    Ok(cached_res)
3049                }
3050            }
3051        }
3052    }
3053}
3054
3055impl<T: StreamingCacheManager> HttpCacheStreamInterface
3056    for HttpStreamingCache<T>
3057where
3058    <T::Body as http_body::Body>::Data: Send,
3059    <T::Body as http_body::Body>::Error:
3060        Into<StreamingError> + Send + Sync + 'static,
3061{
3062    type Body = T::Body;
3063
3064    fn analyze_request(
3065        &self,
3066        parts: &request::Parts,
3067        mode_override: Option<CacheMode>,
3068    ) -> Result<CacheAnalysis> {
3069        self.options.analyze_request_internal(parts, mode_override, self.mode)
3070    }
3071
3072    async fn lookup_cached_response(
3073        &self,
3074        key: &str,
3075    ) -> Result<Option<(Response<Self::Body>, CachePolicy)>> {
3076        self.manager.get(key).await
3077    }
3078
3079    async fn process_response<B>(
3080        &self,
3081        analysis: CacheAnalysis,
3082        response: Response<B>,
3083        metadata: Option<Vec<u8>>,
3084    ) -> Result<Response<Self::Body>>
3085    where
3086        B: http_body::Body + Send + 'static,
3087        B::Data: Send,
3088        B::Error: Into<StreamingError>,
3089        <T::Body as http_body::Body>::Data: Send,
3090        <T::Body as http_body::Body>::Error:
3091            Into<StreamingError> + Send + Sync + 'static,
3092    {
3093        // For non-cacheable requests based on initial analysis, convert them to manager's body type
3094        if !analysis.should_cache {
3095            // RFC 7234 s4.4: invalidate GET and HEAD cache for non-GET/HEAD on success/redirect
3096            if !analysis.is_get_head {
3097                let status = response.status();
3098                if status.is_success() || status.is_redirection() {
3099                    self.manager
3100                        .delete(&self.options.create_cache_key(
3101                            &analysis.request_parts,
3102                            Some("GET"),
3103                        ))
3104                        .await
3105                        .ok();
3106                    self.manager
3107                        .delete(&self.options.create_cache_key(
3108                            &analysis.request_parts,
3109                            Some("HEAD"),
3110                        ))
3111                        .await
3112                        .ok();
3113                }
3114            }
3115            let mut converted_response =
3116                self.manager.convert_body(response).await?;
3117            // Add cache miss headers
3118            if self.options.cache_status_headers {
3119                converted_response.headers_mut().insert(
3120                    XCACHE,
3121                    "MISS".parse().map_err(StreamingError::new)?,
3122                );
3123                converted_response.headers_mut().insert(
3124                    XCACHELOOKUP,
3125                    "MISS".parse().map_err(StreamingError::new)?,
3126                );
3127            }
3128            return Ok(converted_response);
3129        }
3130
3131        // Bust cache keys if needed
3132        for key in &analysis.cache_bust_keys {
3133            self.manager.delete(key).await?;
3134        }
3135
3136        // Convert response to HttpResponse format for response-based cache mode evaluation
3137        let (parts, body) = response.into_parts();
3138        // Use provided metadata or generate from provider
3139        let effective_metadata = metadata.or_else(|| {
3140            self.options.generate_metadata(&analysis.request_parts, &parts)
3141        });
3142        let http_response = self.options.parts_to_http_response(
3143            &parts,
3144            &analysis.request_parts,
3145            effective_metadata.clone(),
3146        )?;
3147
3148        // Check for response-based cache mode override
3149        let effective_cache_mode = self.options.evaluate_response_cache_mode(
3150            &analysis.request_parts,
3151            &http_response,
3152            analysis.cache_mode,
3153        );
3154
3155        // Reconstruct response for further processing
3156        let response = Response::from_parts(parts, body);
3157
3158        // If response-based override says NoStore, don't cache
3159        if effective_cache_mode == CacheMode::NoStore {
3160            // RFC 7234 s4.4: invalidate GET and HEAD cache for non-GET/HEAD on success/redirect
3161            if !analysis.is_get_head {
3162                let status_code = StatusCode::from_u16(http_response.status)?;
3163                if status_code.is_success() || status_code.is_redirection() {
3164                    self.manager
3165                        .delete(&self.options.create_cache_key(
3166                            &analysis.request_parts,
3167                            Some("GET"),
3168                        ))
3169                        .await
3170                        .ok();
3171                    self.manager
3172                        .delete(&self.options.create_cache_key(
3173                            &analysis.request_parts,
3174                            Some("HEAD"),
3175                        ))
3176                        .await
3177                        .ok();
3178                }
3179            }
3180            let mut converted_response =
3181                self.manager.convert_body(response).await?;
3182            // Add cache miss headers
3183            if self.options.cache_status_headers {
3184                converted_response.headers_mut().insert(
3185                    XCACHE,
3186                    "MISS".parse().map_err(StreamingError::new)?,
3187                );
3188                converted_response.headers_mut().insert(
3189                    XCACHELOOKUP,
3190                    "MISS".parse().map_err(StreamingError::new)?,
3191                );
3192            }
3193            return Ok(converted_response);
3194        }
3195
3196        // Create policy for the response
3197        let (parts, body) = response.into_parts();
3198        let policy =
3199            self.options.create_cache_policy(&analysis.request_parts, &parts);
3200
3201        // Reconstruct response for caching
3202        let response = Response::from_parts(parts, body);
3203
3204        let should_cache_response = self.options.should_cache_response(
3205            effective_cache_mode,
3206            &http_response,
3207            analysis.is_get_head,
3208            &policy,
3209        );
3210
3211        if should_cache_response {
3212            // Extract URL from request parts for caching
3213            let request_url =
3214                extract_url_from_request_parts(&analysis.request_parts)?;
3215
3216            // Apply modify_response_before_caching shim before storing
3217            let mut response = response;
3218            apply_modify_response_shim(
3219                &self.options,
3220                &mut response,
3221                &request_url,
3222            );
3223
3224            // Cache the response using the streaming manager
3225            let mut cached_response = self
3226                .manager
3227                .put(
3228                    analysis.cache_key,
3229                    response,
3230                    policy,
3231                    request_url,
3232                    effective_metadata,
3233                )
3234                .await?;
3235
3236            // RFC 7234 s4.4: invalidate GET and HEAD cache for non-GET/HEAD on success/redirect
3237            if !analysis.is_get_head {
3238                let status_code = StatusCode::from_u16(http_response.status)?;
3239                if status_code.is_success() || status_code.is_redirection() {
3240                    self.manager
3241                        .delete(&self.options.create_cache_key(
3242                            &analysis.request_parts,
3243                            Some("GET"),
3244                        ))
3245                        .await
3246                        .ok();
3247                    self.manager
3248                        .delete(&self.options.create_cache_key(
3249                            &analysis.request_parts,
3250                            Some("HEAD"),
3251                        ))
3252                        .await
3253                        .ok();
3254                }
3255            }
3256
3257            // Add cache miss headers (response is being stored for first time)
3258            if self.options.cache_status_headers {
3259                cached_response.headers_mut().insert(
3260                    XCACHE,
3261                    "MISS".parse().map_err(StreamingError::new)?,
3262                );
3263                cached_response.headers_mut().insert(
3264                    XCACHELOOKUP,
3265                    "MISS".parse().map_err(StreamingError::new)?,
3266                );
3267            }
3268            Ok(cached_response)
3269        } else {
3270            // RFC 7234 s4.4: invalidate GET and HEAD cache for non-GET/HEAD on success/redirect
3271            if !analysis.is_get_head {
3272                let status_code = StatusCode::from_u16(http_response.status)?;
3273                if status_code.is_success() || status_code.is_redirection() {
3274                    self.manager
3275                        .delete(&self.options.create_cache_key(
3276                            &analysis.request_parts,
3277                            Some("GET"),
3278                        ))
3279                        .await
3280                        .ok();
3281                    self.manager
3282                        .delete(&self.options.create_cache_key(
3283                            &analysis.request_parts,
3284                            Some("HEAD"),
3285                        ))
3286                        .await
3287                        .ok();
3288                }
3289            }
3290            // Don't cache, just convert to manager's body type
3291            let mut converted_response =
3292                self.manager.convert_body(response).await?;
3293            // Add cache miss headers
3294            if self.options.cache_status_headers {
3295                converted_response.headers_mut().insert(
3296                    XCACHE,
3297                    "MISS".parse().map_err(StreamingError::new)?,
3298                );
3299                converted_response.headers_mut().insert(
3300                    XCACHELOOKUP,
3301                    "MISS".parse().map_err(StreamingError::new)?,
3302                );
3303            }
3304            Ok(converted_response)
3305        }
3306    }
3307
3308    fn prepare_conditional_request(
3309        &self,
3310        parts: &mut request::Parts,
3311        _cached_response: &Response<Self::Body>,
3312        policy: &CachePolicy,
3313    ) -> Result<()> {
3314        let before_req = policy.before_request(parts, SystemTime::now());
3315        if let BeforeRequest::Stale { request, .. } = before_req {
3316            parts.headers.extend(request.headers);
3317        }
3318        Ok(())
3319    }
3320
3321    async fn handle_not_modified(
3322        &self,
3323        cached_response: Response<Self::Body>,
3324        fresh_parts: &response::Parts,
3325    ) -> Result<Response<Self::Body>> {
3326        let (mut parts, body) = cached_response.into_parts();
3327
3328        // Update headers from the 304 response, preserving multi-valued
3329        // headers correctly. `HeaderMap::extend` from another `HeaderMap`
3330        // appends rather than replaces, so naive `extend` would double
3331        // same-named headers. Mirror the clear-then-append pattern used by
3332        // `HttpResponse::update_headers` and the streaming conditional-fetch
3333        // header merges.
3334        for name in fresh_parts.headers.keys() {
3335            parts.headers.remove(name);
3336        }
3337        for (name, value) in fresh_parts.headers.iter() {
3338            parts.headers.append(name.clone(), value.clone());
3339        }
3340
3341        let mut response = Response::from_parts(parts, body);
3342        if self.options.cache_status_headers {
3343            response_cache_status(&mut response, HitOrMiss::HIT);
3344            response_cache_lookup_status(&mut response, HitOrMiss::HIT);
3345        }
3346        Ok(response)
3347    }
3348}
3349
3350impl<T: CacheManager> HttpCacheInterface for HttpCache<T> {
3351    fn analyze_request(
3352        &self,
3353        parts: &request::Parts,
3354        mode_override: Option<CacheMode>,
3355    ) -> Result<CacheAnalysis> {
3356        self.options.analyze_request_internal(parts, mode_override, self.mode)
3357    }
3358
3359    async fn lookup_cached_response(
3360        &self,
3361        key: &str,
3362    ) -> Result<Option<(HttpResponse, CachePolicy)>> {
3363        self.manager.get(key).await
3364    }
3365
3366    async fn process_response(
3367        &self,
3368        analysis: CacheAnalysis,
3369        response: Response<Vec<u8>>,
3370        metadata: Option<Vec<u8>>,
3371    ) -> Result<Response<Vec<u8>>> {
3372        if !analysis.should_cache {
3373            // RFC 7234 s4.4: invalidate GET and HEAD cache for non-GET/HEAD on success/redirect
3374            if !analysis.is_get_head {
3375                let status = response.status();
3376                if status.is_success() || status.is_redirection() {
3377                    self.manager
3378                        .delete(&self.options.create_cache_key(
3379                            &analysis.request_parts,
3380                            Some("GET"),
3381                        ))
3382                        .await
3383                        .ok();
3384                    self.manager
3385                        .delete(&self.options.create_cache_key(
3386                            &analysis.request_parts,
3387                            Some("HEAD"),
3388                        ))
3389                        .await
3390                        .ok();
3391                }
3392            }
3393            return Ok(response);
3394        }
3395
3396        // Bust cache keys if needed
3397        for key in &analysis.cache_bust_keys {
3398            self.manager.delete(key).await?;
3399        }
3400
3401        // Convert response to HttpResponse format
3402        let (parts, body) = response.into_parts();
3403        // Use provided metadata or generate from provider
3404        let effective_metadata = metadata.or_else(|| {
3405            self.options.generate_metadata(&analysis.request_parts, &parts)
3406        });
3407        let mut http_response = self.options.parts_to_http_response(
3408            &parts,
3409            &analysis.request_parts,
3410            effective_metadata,
3411        )?;
3412        http_response.body = body.clone(); // Include the body for buffered cache managers
3413
3414        // Check for response-based cache mode override
3415        let effective_cache_mode = self.options.evaluate_response_cache_mode(
3416            &analysis.request_parts,
3417            &http_response,
3418            analysis.cache_mode,
3419        );
3420
3421        // If response-based override says NoStore, don't cache
3422        if effective_cache_mode == CacheMode::NoStore {
3423            // RFC 7234 s4.4: invalidate GET and HEAD cache for non-GET/HEAD on success/redirect
3424            if !analysis.is_get_head {
3425                let status = StatusCode::from_u16(http_response.status)?;
3426                if status.is_success() || status.is_redirection() {
3427                    self.manager
3428                        .delete(&self.options.create_cache_key(
3429                            &analysis.request_parts,
3430                            Some("GET"),
3431                        ))
3432                        .await
3433                        .ok();
3434                    self.manager
3435                        .delete(&self.options.create_cache_key(
3436                            &analysis.request_parts,
3437                            Some("HEAD"),
3438                        ))
3439                        .await
3440                        .ok();
3441                }
3442            }
3443            let response = Response::from_parts(parts, body);
3444            return Ok(response);
3445        }
3446
3447        // Create policy and determine if we should cache based on response-based mode
3448        let policy = self.options.create_cache_policy(
3449            &analysis.request_parts,
3450            &http_response.parts()?,
3451        );
3452
3453        let should_cache_response = self.options.should_cache_response(
3454            effective_cache_mode,
3455            &http_response,
3456            analysis.is_get_head,
3457            &policy,
3458        );
3459
3460        if should_cache_response {
3461            self.options.modify_response_before_caching(&mut http_response);
3462            let cached_response = self
3463                .manager
3464                .put(analysis.cache_key, http_response, policy)
3465                .await?;
3466
3467            // RFC 7234 s4.4: invalidate GET and HEAD cache for non-GET/HEAD on success/redirect
3468            if !analysis.is_get_head {
3469                let status = StatusCode::from_u16(cached_response.status)?;
3470                if status.is_success() || status.is_redirection() {
3471                    self.manager
3472                        .delete(&self.options.create_cache_key(
3473                            &analysis.request_parts,
3474                            Some("GET"),
3475                        ))
3476                        .await
3477                        .ok();
3478                    self.manager
3479                        .delete(&self.options.create_cache_key(
3480                            &analysis.request_parts,
3481                            Some("HEAD"),
3482                        ))
3483                        .await
3484                        .ok();
3485                }
3486            }
3487
3488            // Convert back to standard Response
3489            let response_parts = cached_response.parts()?;
3490            let mut response = Response::builder()
3491                .status(response_parts.status)
3492                .version(response_parts.version)
3493                .body(cached_response.body)?;
3494
3495            // Copy headers from the response parts
3496            *response.headers_mut() = response_parts.headers;
3497
3498            Ok(response)
3499        } else {
3500            // RFC 7234 s4.4: invalidate GET and HEAD cache for non-GET/HEAD on success/redirect
3501            if !analysis.is_get_head {
3502                let status = StatusCode::from_u16(http_response.status)?;
3503                if status.is_success() || status.is_redirection() {
3504                    self.manager
3505                        .delete(&self.options.create_cache_key(
3506                            &analysis.request_parts,
3507                            Some("GET"),
3508                        ))
3509                        .await
3510                        .ok();
3511                    self.manager
3512                        .delete(&self.options.create_cache_key(
3513                            &analysis.request_parts,
3514                            Some("HEAD"),
3515                        ))
3516                        .await
3517                        .ok();
3518                }
3519            }
3520            // Don't cache, return original response
3521            let response = Response::from_parts(parts, body);
3522            Ok(response)
3523        }
3524    }
3525
3526    fn prepare_conditional_request(
3527        &self,
3528        parts: &mut request::Parts,
3529        _cached_response: &HttpResponse,
3530        policy: &CachePolicy,
3531    ) -> Result<()> {
3532        let before_req = policy.before_request(parts, SystemTime::now());
3533        if let BeforeRequest::Stale { request, .. } = before_req {
3534            parts.headers.extend(request.headers);
3535        }
3536        Ok(())
3537    }
3538
3539    async fn handle_not_modified(
3540        &self,
3541        mut cached_response: HttpResponse,
3542        fresh_parts: &response::Parts,
3543    ) -> Result<HttpResponse> {
3544        cached_response.update_headers(fresh_parts)?;
3545        if self.options.cache_status_headers {
3546            cached_response.cache_status(HitOrMiss::HIT);
3547            cached_response.cache_lookup_status(HitOrMiss::HIT);
3548        }
3549        Ok(cached_response)
3550    }
3551}
3552
3553#[cfg(test)]
3554mod test;