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//! use http_cache::{CACacheManager, HttpCache, CacheMode, HttpCacheOptions};
35//!
36//! // Create a cache manager with disk storage
37//! let manager = CACacheManager::new("./cache".into(), true);
38//!
39//! // Create an HTTP cache with default behavior
40//! let cache = HttpCache {
41//!     mode: CacheMode::Default,
42//!     manager,
43//!     options: HttpCacheOptions::default(),
44//! };
45//! ```
46//!
47//! ## Cache Modes
48//!
49//! Different cache modes provide different behaviors:
50//!
51//! ```rust
52//! use http_cache::{CacheMode, HttpCache, CACacheManager, HttpCacheOptions};
53//!
54//! let manager = CACacheManager::new("./cache".into(), true);
55//!
56//! // Default mode: follows HTTP caching rules
57//! let default_cache = HttpCache {
58//!     mode: CacheMode::Default,
59//!     manager: manager.clone(),
60//!     options: HttpCacheOptions::default(),
61//! };
62//!
63//! // NoStore mode: never caches responses
64//! let no_store_cache = HttpCache {
65//!     mode: CacheMode::NoStore,
66//!     manager: manager.clone(),
67//!     options: HttpCacheOptions::default(),
68//! };
69//!
70//! // ForceCache mode: caches responses even if headers suggest otherwise
71//! let force_cache = HttpCache {
72//!     mode: CacheMode::ForceCache,
73//!     manager,
74//!     options: HttpCacheOptions::default(),
75//! };
76//! ```
77//!
78//! ## Custom Cache Keys
79//!
80//! You can customize how cache keys are generated:
81//!
82//! ```rust
83//! use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode};
84//! use std::sync::Arc;
85//! use http::request::Parts;
86//!
87//! let manager = CACacheManager::new("./cache".into(), true);
88//!
89//! let options = HttpCacheOptions {
90//!     cache_key: Some(Arc::new(|req: &Parts| {
91//!         // Custom cache key that includes query parameters
92//!         format!("{}:{}", req.method, req.uri)
93//!     })),
94//!     ..Default::default()
95//! };
96//!
97//! let cache = HttpCache {
98//!     mode: CacheMode::Default,
99//!     manager,
100//!     options,
101//! };
102//! ```
103//!
104//! ## Maximum TTL Control
105//!
106//! Set a maximum time-to-live for cached responses, particularly useful with `CacheMode::IgnoreRules`:
107//!
108//! ```rust
109//! use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode};
110//! use std::time::Duration;
111//!
112//! let manager = CACacheManager::new("./cache".into(), true);
113//!
114//! // Limit cache duration to 5 minutes regardless of server headers
115//! let options = HttpCacheOptions {
116//!     max_ttl: Some(Duration::from_secs(300)), // 5 minutes
117//!     ..Default::default()
118//! };
119//!
120//! let cache = HttpCache {
121//!     mode: CacheMode::IgnoreRules, // Ignore server cache-control headers
122//!     manager,
123//!     options,
124//! };
125//! ```
126//!
127//! ## Response-Based Cache Mode Override
128//!
129//! Override cache behavior based on the response you receive. This is useful for scenarios like
130//! forcing cache for successful responses even when headers say not to cache, or never caching
131//! error responses like rate limits:
132//!
133//! ```rust
134//! use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode};
135//! use std::sync::Arc;
136//!
137//! let manager = CACacheManager::new("./cache".into(), true);
138//!
139//! let options = HttpCacheOptions {
140//!     response_cache_mode_fn: Some(Arc::new(|_request_parts, response| {
141//!         match response.status {
142//!             // Force cache successful responses even if headers say not to cache
143//!             200..=299 => Some(CacheMode::ForceCache),
144//!             // Never cache rate-limited responses  
145//!             429 => Some(CacheMode::NoStore),
146//!             // Use default behavior for everything else
147//!             _ => None,
148//!         }
149//!     })),
150//!     ..Default::default()
151//! };
152//!
153//! let cache = HttpCache {
154//!     mode: CacheMode::Default,
155//!     manager,
156//!     options,
157//! };
158//! ```
159//!
160//! ## Content-Type Based Caching
161//!
162//! You can implement selective caching based on response content types using `response_cache_mode_fn`.
163//! This is useful when you only want to cache certain types of content:
164//!
165//! ```rust
166//! use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode};
167//! use std::sync::Arc;
168//!
169//! let manager = CACacheManager::new("./cache".into(), true);
170//!
171//! let options = HttpCacheOptions {
172//!     response_cache_mode_fn: Some(Arc::new(|_request_parts, response| {
173//!         // Check the Content-Type header to decide caching behavior
174//!         if let Some(content_type) = response.headers.get("content-type") {
175//!             match content_type.as_str() {
176//!                 // Cache JSON APIs aggressively
177//!                 ct if ct.starts_with("application/json") => Some(CacheMode::ForceCache),
178//!                 // Cache images with default rules
179//!                 ct if ct.starts_with("image/") => Some(CacheMode::Default),
180//!                 // Cache static assets
181//!                 ct if ct.starts_with("text/css") => Some(CacheMode::ForceCache),
182//!                 ct if ct.starts_with("application/javascript") => Some(CacheMode::ForceCache),
183//!                 // Don't cache HTML pages (dynamic content)
184//!                 ct if ct.starts_with("text/html") => Some(CacheMode::NoStore),
185//!                 // Don't cache unknown content types
186//!                 _ => Some(CacheMode::NoStore),
187//!             }
188//!         } else {
189//!             // No Content-Type header - don't cache
190//!             Some(CacheMode::NoStore)
191//!         }
192//!     })),
193//!     ..Default::default()
194//! };
195//!
196//! let cache = HttpCache {
197//!     mode: CacheMode::Default, // This gets overridden by response_cache_mode_fn
198//!     manager,
199//!     options,
200//! };
201//! ```
202//!
203//! ## Streaming Support
204//!
205//! For handling large responses without full buffering, use the `StreamingManager`:
206//!
207//! ```rust
208//! # #[cfg(feature = "streaming")]
209//! # {
210//! use http_cache::{StreamingBody, HttpStreamingCache, StreamingManager};
211//! use bytes::Bytes;
212//! use std::path::PathBuf;
213//! use http_body::Body;
214//! use http_body_util::Full;
215//!
216//! // Create a file-based streaming cache manager
217//! let manager = StreamingManager::new(PathBuf::from("./streaming-cache"));
218//!
219//! // StreamingBody can handle both buffered and streaming scenarios
220//! let body: StreamingBody<Full<Bytes>> = StreamingBody::buffered(Bytes::from("cached content"));
221//! println!("Body size: {:?}", body.size_hint());
222//! # }
223//! ```
224//!
225//! **Note**: Streaming support requires the `StreamingManager` with the `streaming` feature.
226//! Other cache managers (CACacheManager, MokaManager, QuickManager) do not support streaming
227//! and will buffer response bodies in memory.
228//!
229//! ## Features
230//!
231//! The following features are available. By default `manager-cacache` and `cacache-smol` are enabled.
232//!
233//! - `manager-cacache` (default): enable [cacache](https://github.com/zkat/cacache-rs),
234//! a disk cache, backend manager.
235//! - `cacache-smol` (default): enable [smol](https://github.com/smol-rs/smol) runtime support for cacache.
236//! - `cacache-tokio` (disabled): enable [tokio](https://github.com/tokio-rs/tokio) runtime support for cacache.
237//! - `manager-moka` (disabled): enable [moka](https://github.com/moka-rs/moka),
238//! an in-memory cache, backend manager.
239//! - `streaming` (disabled): enable the `StreamingManager` for streaming cache support.
240//! - `streaming-tokio` (disabled): enable streaming with tokio runtime support.
241//! - `streaming-smol` (disabled): enable streaming with smol runtime support.
242//! - `with-http-types` (disabled): enable [http-types](https://github.com/http-rs/http-types)
243//! type conversion support
244//!
245//! **Note**: Only `StreamingManager` (via the `streaming` feature) provides streaming support.
246//! Other managers will buffer response bodies in memory even when used with `StreamingManager`.
247//!
248//! ## Integration
249//!
250//! This crate is designed to be used as a foundation for HTTP client and server middleware.
251//! See the companion crates for specific integrations:
252//!
253//! - [`http-cache-reqwest`](https://docs.rs/http-cache-reqwest) for reqwest client middleware
254//! - [`http-cache-surf`](https://docs.rs/http-cache-surf) for surf client middleware  
255//! - [`http-cache-tower`](https://docs.rs/http-cache-tower) for tower service middleware
256mod body;
257mod error;
258mod managers;
259
260#[cfg(feature = "streaming")]
261mod runtime;
262
263#[cfg(feature = "rate-limiting")]
264pub mod rate_limiting;
265
266use std::{
267    collections::HashMap,
268    convert::TryFrom,
269    fmt::{self, Debug},
270    str::FromStr,
271    sync::Arc,
272    time::{Duration, SystemTime},
273};
274
275use http::{
276    header::CACHE_CONTROL, request, response, HeaderValue, Response, StatusCode,
277};
278use http_cache_semantics::{AfterResponse, BeforeRequest, CachePolicy};
279use serde::{Deserialize, Serialize};
280use url::Url;
281
282pub use body::StreamingBody;
283pub use error::{
284    BadHeader, BadRequest, BadVersion, BoxError, ClientStreamingError,
285    HttpCacheError, HttpCacheResult, Result, StreamingError,
286};
287
288#[cfg(feature = "manager-cacache")]
289pub use managers::cacache::CACacheManager;
290
291#[cfg(feature = "streaming")]
292pub use managers::streaming_cache::StreamingManager;
293
294#[cfg(feature = "manager-moka")]
295pub use managers::moka::MokaManager;
296
297#[cfg(feature = "rate-limiting")]
298pub use rate_limiting::{
299    CacheAwareRateLimiter, DirectRateLimiter, DomainRateLimiter,
300};
301
302#[cfg(feature = "rate-limiting")]
303pub use rate_limiting::Quota;
304
305// Exposing the moka cache for convenience, renaming to avoid naming conflicts
306#[cfg(feature = "manager-moka")]
307#[cfg_attr(docsrs, doc(cfg(feature = "manager-moka")))]
308pub use moka::future::{Cache as MokaCache, CacheBuilder as MokaCacheBuilder};
309
310// Custom headers used to indicate cache status (hit or miss)
311/// `x-cache` header: Value will be HIT if the response was served from cache, MISS if not
312pub const XCACHE: &str = "x-cache";
313/// `x-cache-lookup` header: Value will be HIT if a response existed in cache, MISS if not
314pub const XCACHELOOKUP: &str = "x-cache-lookup";
315/// `warning` header: HTTP warning header as per RFC 7234
316const WARNING: &str = "warning";
317
318/// Represents a basic cache status
319/// Used in the custom headers `x-cache` and `x-cache-lookup`
320#[derive(Debug, Copy, Clone)]
321pub enum HitOrMiss {
322    /// Yes, there was a hit
323    HIT,
324    /// No, there was no hit
325    MISS,
326}
327
328impl fmt::Display for HitOrMiss {
329    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
330        match self {
331            Self::HIT => write!(f, "HIT"),
332            Self::MISS => write!(f, "MISS"),
333        }
334    }
335}
336
337/// Represents an HTTP version
338#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
339#[non_exhaustive]
340pub enum HttpVersion {
341    /// HTTP Version 0.9
342    #[serde(rename = "HTTP/0.9")]
343    Http09,
344    /// HTTP Version 1.0
345    #[serde(rename = "HTTP/1.0")]
346    Http10,
347    /// HTTP Version 1.1
348    #[serde(rename = "HTTP/1.1")]
349    Http11,
350    /// HTTP Version 2.0
351    #[serde(rename = "HTTP/2.0")]
352    H2,
353    /// HTTP Version 3.0
354    #[serde(rename = "HTTP/3.0")]
355    H3,
356}
357
358impl fmt::Display for HttpVersion {
359    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
360        match *self {
361            HttpVersion::Http09 => write!(f, "HTTP/0.9"),
362            HttpVersion::Http10 => write!(f, "HTTP/1.0"),
363            HttpVersion::Http11 => write!(f, "HTTP/1.1"),
364            HttpVersion::H2 => write!(f, "HTTP/2.0"),
365            HttpVersion::H3 => write!(f, "HTTP/3.0"),
366        }
367    }
368}
369
370/// Extract a URL from HTTP request parts for cache key generation
371///
372/// This function reconstructs the full URL from the request parts, handling both
373/// HTTP and HTTPS schemes based on the connection type or explicit headers.
374fn extract_url_from_request_parts(parts: &request::Parts) -> Result<Url> {
375    // First check if the URI is already absolute
376    if let Some(_scheme) = parts.uri.scheme() {
377        // URI is absolute, use it directly
378        return Url::parse(&parts.uri.to_string())
379            .map_err(|_| -> BoxError { BadHeader.into() });
380    }
381
382    // Get the host header
383    let host = parts
384        .headers
385        .get("host")
386        .ok_or(BadHeader)?
387        .to_str()
388        .map_err(|_| BadHeader)?;
389
390    // Determine scheme based on host and headers
391    let scheme = determine_scheme(host, &parts.headers)?;
392
393    // Create base URL using url crate's builder pattern for safety
394    let mut base_url = Url::parse(&format!("{}://{}/", &scheme, host))
395        .map_err(|_| -> BoxError { BadHeader.into() })?;
396
397    // Set the path and query from the URI
398    if let Some(path_and_query) = parts.uri.path_and_query() {
399        base_url.set_path(path_and_query.path());
400        if let Some(query) = path_and_query.query() {
401            base_url.set_query(Some(query));
402        }
403    }
404
405    Ok(base_url)
406}
407
408/// Determine the appropriate scheme for URL construction
409fn determine_scheme(host: &str, headers: &http::HeaderMap) -> Result<String> {
410    // Check for explicit protocol forwarding header first
411    if let Some(forwarded_proto) = headers.get("x-forwarded-proto") {
412        let proto = forwarded_proto.to_str().map_err(|_| BadHeader)?;
413        return match proto {
414            "http" | "https" => Ok(proto.to_string()),
415            _ => Ok("https".to_string()), // Default to secure for unknown protocols
416        };
417    }
418
419    // Check if this looks like a local development host
420    if host.starts_with("localhost") || host.starts_with("127.0.0.1") {
421        Ok("http".to_string())
422    } else {
423        Ok("https".to_string()) // Default to secure for all other hosts
424    }
425}
426
427/// A basic generic type that represents an HTTP response
428#[derive(Debug, Clone, Deserialize, Serialize)]
429pub struct HttpResponse {
430    /// HTTP response body
431    pub body: Vec<u8>,
432    /// HTTP response headers
433    pub headers: HashMap<String, String>,
434    /// HTTP response status code
435    pub status: u16,
436    /// HTTP response url
437    pub url: Url,
438    /// HTTP response version
439    pub version: HttpVersion,
440}
441
442impl HttpResponse {
443    /// Returns `http::response::Parts`
444    pub fn parts(&self) -> Result<response::Parts> {
445        let mut converted =
446            response::Builder::new().status(self.status).body(())?;
447        {
448            let headers = converted.headers_mut();
449            for header in &self.headers {
450                headers.insert(
451                    http::header::HeaderName::from_str(header.0.as_str())?,
452                    HeaderValue::from_str(header.1.as_str())?,
453                );
454            }
455        }
456        Ok(converted.into_parts().0)
457    }
458
459    /// Returns the status code of the warning header if present
460    #[must_use]
461    fn warning_code(&self) -> Option<usize> {
462        self.headers.get(WARNING).and_then(|hdr| {
463            hdr.as_str().chars().take(3).collect::<String>().parse().ok()
464        })
465    }
466
467    /// Adds a warning header to a response
468    fn add_warning(&mut self, url: &Url, code: usize, message: &str) {
469        // warning    = "warning" ":" 1#warning-value
470        // warning-value = warn-code SP warn-agent SP warn-text [SP warn-date]
471        // warn-code  = 3DIGIT
472        // warn-agent = ( host [ ":" port ] ) | pseudonym
473        //                 ; the name or pseudonym of the server adding
474        //                 ; the warning header, for use in debugging
475        // warn-text  = quoted-string
476        // warn-date  = <"> HTTP-date <">
477        // (https://tools.ietf.org/html/rfc2616#section-14.46)
478        let host = url
479            .host()
480            .map(|h| h.to_string())
481            .unwrap_or_else(|| "unknown".to_string());
482        // Escape message to prevent header injection and ensure valid HTTP format
483        let escaped_message =
484            message.replace('"', "'").replace(['\n', '\r'], " ");
485        self.headers.insert(
486            WARNING.to_string(),
487            format!(
488                "{} {} \"{}\" \"{}\"",
489                code,
490                host,
491                escaped_message,
492                httpdate::fmt_http_date(SystemTime::now())
493            ),
494        );
495    }
496
497    /// Removes a warning header from a response
498    fn remove_warning(&mut self) {
499        self.headers.remove(WARNING);
500    }
501
502    /// Update the headers from `http::response::Parts`
503    pub fn update_headers(&mut self, parts: &response::Parts) -> Result<()> {
504        for header in parts.headers.iter() {
505            self.headers.insert(
506                header.0.as_str().to_string(),
507                header.1.to_str()?.to_string(),
508            );
509        }
510        Ok(())
511    }
512
513    /// Checks if the Cache-Control header contains the must-revalidate directive
514    #[must_use]
515    fn must_revalidate(&self) -> bool {
516        self.headers.get(CACHE_CONTROL.as_str()).is_some_and(|val| {
517            val.as_str().to_lowercase().contains("must-revalidate")
518        })
519    }
520
521    /// Adds the custom `x-cache` header to the response
522    pub fn cache_status(&mut self, hit_or_miss: HitOrMiss) {
523        self.headers.insert(XCACHE.to_string(), hit_or_miss.to_string());
524    }
525
526    /// Adds the custom `x-cache-lookup` header to the response
527    pub fn cache_lookup_status(&mut self, hit_or_miss: HitOrMiss) {
528        self.headers.insert(XCACHELOOKUP.to_string(), hit_or_miss.to_string());
529    }
530}
531
532/// A trait providing methods for storing, reading, and removing cache records.
533#[async_trait::async_trait]
534pub trait CacheManager: Send + Sync + 'static {
535    /// Attempts to pull a cached response and related policy from cache.
536    async fn get(
537        &self,
538        cache_key: &str,
539    ) -> Result<Option<(HttpResponse, CachePolicy)>>;
540    /// Attempts to cache a response and related policy.
541    async fn put(
542        &self,
543        cache_key: String,
544        res: HttpResponse,
545        policy: CachePolicy,
546    ) -> Result<HttpResponse>;
547    /// Attempts to remove a record from cache.
548    async fn delete(&self, cache_key: &str) -> Result<()>;
549}
550
551/// A streaming cache manager that supports streaming request/response bodies
552/// without buffering them in memory. This is ideal for large responses.
553#[async_trait::async_trait]
554pub trait StreamingCacheManager: Send + Sync + 'static {
555    /// The body type used by this cache manager
556    type Body: http_body::Body + Send + 'static;
557
558    /// Attempts to pull a cached response and related policy from cache with streaming body.
559    async fn get(
560        &self,
561        cache_key: &str,
562    ) -> Result<Option<(Response<Self::Body>, CachePolicy)>>
563    where
564        <Self::Body as http_body::Body>::Data: Send,
565        <Self::Body as http_body::Body>::Error:
566            Into<StreamingError> + Send + Sync + 'static;
567
568    /// Attempts to cache a response with a streaming body and related policy.
569    async fn put<B>(
570        &self,
571        cache_key: String,
572        response: Response<B>,
573        policy: CachePolicy,
574        request_url: Url,
575    ) -> Result<Response<Self::Body>>
576    where
577        B: http_body::Body + Send + 'static,
578        B::Data: Send,
579        B::Error: Into<StreamingError>,
580        <Self::Body as http_body::Body>::Data: Send,
581        <Self::Body as http_body::Body>::Error:
582            Into<StreamingError> + Send + Sync + 'static;
583
584    /// Converts a generic body to the manager's body type for non-cacheable responses.
585    /// This is called when a response should not be cached but still needs to be returned
586    /// with the correct body type.
587    async fn convert_body<B>(
588        &self,
589        response: Response<B>,
590    ) -> Result<Response<Self::Body>>
591    where
592        B: http_body::Body + Send + 'static,
593        B::Data: Send,
594        B::Error: Into<StreamingError>,
595        <Self::Body as http_body::Body>::Data: Send,
596        <Self::Body as http_body::Body>::Error:
597            Into<StreamingError> + Send + Sync + 'static;
598
599    /// Attempts to remove a record from cache.
600    async fn delete(&self, cache_key: &str) -> Result<()>;
601
602    /// Convert the manager's body type to a reqwest-compatible bytes stream.
603    /// This enables efficient streaming without collecting the entire body.
604    #[cfg(feature = "streaming")]
605    fn body_to_bytes_stream(
606        body: Self::Body,
607    ) -> impl futures_util::Stream<
608        Item = std::result::Result<
609            bytes::Bytes,
610            Box<dyn std::error::Error + Send + Sync>,
611        >,
612    > + Send
613    where
614        <Self::Body as http_body::Body>::Data: Send,
615        <Self::Body as http_body::Body>::Error: Send + Sync + 'static;
616}
617
618/// Describes the functionality required for interfacing with HTTP client middleware
619#[async_trait::async_trait]
620pub trait Middleware: Send {
621    /// Allows the cache mode to be overridden.
622    ///
623    /// This overrides any cache mode set in the configuration, including cache_mode_fn.
624    fn overridden_cache_mode(&self) -> Option<CacheMode> {
625        None
626    }
627    /// Determines if the request method is either GET or HEAD
628    fn is_method_get_head(&self) -> bool;
629    /// Returns a new cache policy with default options
630    fn policy(&self, response: &HttpResponse) -> Result<CachePolicy>;
631    /// Returns a new cache policy with custom options
632    fn policy_with_options(
633        &self,
634        response: &HttpResponse,
635        options: CacheOptions,
636    ) -> Result<CachePolicy>;
637    /// Attempts to update the request headers with the passed `http::request::Parts`
638    fn update_headers(&mut self, parts: &request::Parts) -> Result<()>;
639    /// Attempts to force the "no-cache" directive on the request
640    fn force_no_cache(&mut self) -> Result<()>;
641    /// Attempts to construct `http::request::Parts` from the request
642    fn parts(&self) -> Result<request::Parts>;
643    /// Attempts to determine the requested url
644    fn url(&self) -> Result<Url>;
645    /// Attempts to determine the request method
646    fn method(&self) -> Result<String>;
647    /// Attempts to fetch an upstream resource and return an [`HttpResponse`]
648    async fn remote_fetch(&mut self) -> Result<HttpResponse>;
649}
650
651/// An interface for HTTP caching that works with composable middleware patterns
652/// like Tower. This trait separates the concerns of request analysis, cache lookup,
653/// and response processing into discrete steps.
654pub trait HttpCacheInterface<B = Vec<u8>>: Send + Sync {
655    /// Analyze a request to determine cache behavior
656    fn analyze_request(
657        &self,
658        parts: &request::Parts,
659        mode_override: Option<CacheMode>,
660    ) -> Result<CacheAnalysis>;
661
662    /// Look up a cached response for the given cache key
663    #[allow(async_fn_in_trait)]
664    async fn lookup_cached_response(
665        &self,
666        key: &str,
667    ) -> Result<Option<(HttpResponse, CachePolicy)>>;
668
669    /// Process a fresh response from upstream and potentially cache it
670    #[allow(async_fn_in_trait)]
671    async fn process_response(
672        &self,
673        analysis: CacheAnalysis,
674        response: Response<B>,
675    ) -> Result<Response<B>>;
676
677    /// Update request headers for conditional requests (e.g., If-None-Match)
678    fn prepare_conditional_request(
679        &self,
680        parts: &mut request::Parts,
681        cached_response: &HttpResponse,
682        policy: &CachePolicy,
683    ) -> Result<()>;
684
685    /// Handle a 304 Not Modified response by returning the cached response
686    #[allow(async_fn_in_trait)]
687    async fn handle_not_modified(
688        &self,
689        cached_response: HttpResponse,
690        fresh_parts: &response::Parts,
691    ) -> Result<HttpResponse>;
692}
693
694/// Streaming version of the HTTP cache interface that supports streaming request/response bodies
695/// without buffering them in memory. This is ideal for large responses or when memory usage
696/// is a concern.
697pub trait HttpCacheStreamInterface: Send + Sync {
698    /// The body type used by this cache implementation
699    type Body: http_body::Body + Send + 'static;
700
701    /// Analyze a request to determine cache behavior
702    fn analyze_request(
703        &self,
704        parts: &request::Parts,
705        mode_override: Option<CacheMode>,
706    ) -> Result<CacheAnalysis>;
707
708    /// Look up a cached response for the given cache key, returning a streaming body
709    #[allow(async_fn_in_trait)]
710    async fn lookup_cached_response(
711        &self,
712        key: &str,
713    ) -> Result<Option<(Response<Self::Body>, CachePolicy)>>
714    where
715        <Self::Body as http_body::Body>::Data: Send,
716        <Self::Body as http_body::Body>::Error:
717            Into<StreamingError> + Send + Sync + 'static;
718
719    /// Process a fresh response from upstream and potentially cache it with streaming support
720    #[allow(async_fn_in_trait)]
721    async fn process_response<B>(
722        &self,
723        analysis: CacheAnalysis,
724        response: Response<B>,
725    ) -> Result<Response<Self::Body>>
726    where
727        B: http_body::Body + Send + 'static,
728        B::Data: Send,
729        B::Error: Into<StreamingError>,
730        <Self::Body as http_body::Body>::Data: Send,
731        <Self::Body as http_body::Body>::Error:
732            Into<StreamingError> + Send + Sync + 'static;
733
734    /// Update request headers for conditional requests (e.g., If-None-Match)
735    fn prepare_conditional_request(
736        &self,
737        parts: &mut request::Parts,
738        cached_response: &Response<Self::Body>,
739        policy: &CachePolicy,
740    ) -> Result<()>;
741
742    /// Handle a 304 Not Modified response by returning the cached response
743    #[allow(async_fn_in_trait)]
744    async fn handle_not_modified(
745        &self,
746        cached_response: Response<Self::Body>,
747        fresh_parts: &response::Parts,
748    ) -> Result<Response<Self::Body>>
749    where
750        <Self::Body as http_body::Body>::Data: Send,
751        <Self::Body as http_body::Body>::Error:
752            Into<StreamingError> + Send + Sync + 'static;
753}
754
755/// Analysis result for a request, containing cache key and caching decisions
756#[derive(Debug, Clone)]
757pub struct CacheAnalysis {
758    /// The cache key for this request
759    pub cache_key: String,
760    /// Whether this request should be cached
761    pub should_cache: bool,
762    /// The effective cache mode for this request
763    pub cache_mode: CacheMode,
764    /// Keys to bust from cache before processing
765    pub cache_bust_keys: Vec<String>,
766    /// The request parts for policy creation
767    pub request_parts: request::Parts,
768    /// Whether this is a GET or HEAD request
769    pub is_get_head: bool,
770}
771
772/// Cache mode determines how the HTTP cache behaves for requests.
773///
774/// These modes are similar to [make-fetch-happen cache options](https://github.com/npm/make-fetch-happen#--optscache)
775/// and provide fine-grained control over caching behavior.
776///
777/// # Examples
778///
779/// ```rust
780/// use http_cache::{CacheMode, HttpCache, CACacheManager, HttpCacheOptions};
781///
782/// let manager = CACacheManager::new("./cache".into(), true);
783///
784/// // Use different cache modes for different scenarios
785/// let default_cache = HttpCache {
786///     mode: CacheMode::Default,        // Standard HTTP caching rules
787///     manager: manager.clone(),
788///     options: HttpCacheOptions::default(),
789/// };
790///
791/// let force_cache = HttpCache {
792///     mode: CacheMode::ForceCache,     // Cache everything, ignore staleness
793///     manager: manager.clone(),
794///     options: HttpCacheOptions::default(),
795/// };
796///
797/// let no_cache = HttpCache {
798///     mode: CacheMode::NoStore,        // Never cache anything
799///     manager,
800///     options: HttpCacheOptions::default(),
801/// };
802/// ```
803#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
804pub enum CacheMode {
805    /// Standard HTTP caching behavior (recommended for most use cases).
806    ///
807    /// This mode:
808    /// - Checks the cache for fresh responses and uses them
809    /// - Makes conditional requests for stale responses (revalidation)
810    /// - Makes normal requests when no cached response exists
811    /// - Updates the cache with new responses
812    /// - Falls back to stale responses if revalidation fails
813    ///
814    /// This is the most common mode and follows HTTP caching standards closely.
815    #[default]
816    Default,
817
818    /// Completely bypasses the cache.
819    ///
820    /// This mode:
821    /// - Never reads from the cache
822    /// - Never writes to the cache
823    /// - Always makes fresh network requests
824    ///
825    /// Use this when you need to ensure every request goes to the origin server.
826    NoStore,
827
828    /// Bypasses cache on request but updates cache with response.
829    ///
830    /// This mode:
831    /// - Ignores any cached responses
832    /// - Always makes a fresh network request
833    /// - Updates the cache with the response
834    ///
835    /// Equivalent to a "hard refresh" - useful when you know the cache is stale.
836    Reload,
837
838    /// Always revalidates cached responses.
839    ///
840    /// This mode:
841    /// - Makes conditional requests if a cached response exists
842    /// - Makes normal requests if no cached response exists
843    /// - Updates the cache with responses
844    ///
845    /// Use this when you want to ensure content freshness while still benefiting
846    /// from conditional requests (304 Not Modified responses).
847    NoCache,
848
849    /// Uses cached responses regardless of staleness.
850    ///
851    /// This mode:
852    /// - Uses any cached response, even if stale
853    /// - Makes network requests only when no cached response exists
854    /// - Updates the cache with new responses
855    ///
856    /// Useful for offline scenarios or when performance is more important than freshness.
857    ForceCache,
858
859    /// Only serves from cache, never makes network requests.
860    ///
861    /// This mode:
862    /// - Uses any cached response, even if stale
863    /// - Returns an error if no cached response exists
864    /// - Never makes network requests
865    ///
866    /// Use this for offline-only scenarios or when you want to guarantee
867    /// no network traffic.
868    OnlyIfCached,
869
870    /// Ignores HTTP caching rules and caches everything.
871    ///
872    /// This mode:
873    /// - Caches all 200 responses regardless of cache-control headers
874    /// - Uses cached responses regardless of staleness
875    /// - Makes network requests when no cached response exists
876    ///
877    /// Use this when you want aggressive caching and don't want to respect
878    /// server cache directives.
879    IgnoreRules,
880}
881
882impl TryFrom<http::Version> for HttpVersion {
883    type Error = BoxError;
884
885    fn try_from(value: http::Version) -> Result<Self> {
886        Ok(match value {
887            http::Version::HTTP_09 => Self::Http09,
888            http::Version::HTTP_10 => Self::Http10,
889            http::Version::HTTP_11 => Self::Http11,
890            http::Version::HTTP_2 => Self::H2,
891            http::Version::HTTP_3 => Self::H3,
892            _ => return Err(Box::new(BadVersion)),
893        })
894    }
895}
896
897impl From<HttpVersion> for http::Version {
898    fn from(value: HttpVersion) -> Self {
899        match value {
900            HttpVersion::Http09 => Self::HTTP_09,
901            HttpVersion::Http10 => Self::HTTP_10,
902            HttpVersion::Http11 => Self::HTTP_11,
903            HttpVersion::H2 => Self::HTTP_2,
904            HttpVersion::H3 => Self::HTTP_3,
905        }
906    }
907}
908
909#[cfg(feature = "http-types")]
910impl TryFrom<http_types::Version> for HttpVersion {
911    type Error = BoxError;
912
913    fn try_from(value: http_types::Version) -> Result<Self> {
914        Ok(match value {
915            http_types::Version::Http0_9 => Self::Http09,
916            http_types::Version::Http1_0 => Self::Http10,
917            http_types::Version::Http1_1 => Self::Http11,
918            http_types::Version::Http2_0 => Self::H2,
919            http_types::Version::Http3_0 => Self::H3,
920            _ => return Err(Box::new(BadVersion)),
921        })
922    }
923}
924
925#[cfg(feature = "http-types")]
926impl From<HttpVersion> for http_types::Version {
927    fn from(value: HttpVersion) -> Self {
928        match value {
929            HttpVersion::Http09 => Self::Http0_9,
930            HttpVersion::Http10 => Self::Http1_0,
931            HttpVersion::Http11 => Self::Http1_1,
932            HttpVersion::H2 => Self::Http2_0,
933            HttpVersion::H3 => Self::Http3_0,
934        }
935    }
936}
937
938/// Options struct provided by
939/// [`http-cache-semantics`](https://github.com/kornelski/rusty-http-cache-semantics).
940pub use http_cache_semantics::CacheOptions;
941
942/// A closure that takes [`http::request::Parts`] and returns a [`String`].
943/// By default, the cache key is a combination of the request method and uri with a colon in between.
944pub type CacheKey = Arc<dyn Fn(&request::Parts) -> String + Send + Sync>;
945
946/// A closure that takes [`http::request::Parts`] and returns a [`CacheMode`]
947pub type CacheModeFn = Arc<dyn Fn(&request::Parts) -> CacheMode + Send + Sync>;
948
949/// A closure that takes [`http::request::Parts`], [`HttpResponse`] and returns a [`CacheMode`] to override caching behavior based on the response
950pub type ResponseCacheModeFn = Arc<
951    dyn Fn(&request::Parts, &HttpResponse) -> Option<CacheMode> + Send + Sync,
952>;
953
954/// 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.
955/// An empty vector means that no cache busting will be performed.
956pub type CacheBust = Arc<
957    dyn Fn(&request::Parts, &Option<CacheKey>, &str) -> Vec<String>
958        + Send
959        + Sync,
960>;
961
962/// Configuration options for customizing HTTP cache behavior on a per-request basis.
963///
964/// This struct allows you to override default caching behavior for individual requests
965/// by providing custom cache options, cache keys, cache modes, and cache busting logic.
966///
967/// # Examples
968///
969/// ## Basic Custom Cache Key
970/// ```rust
971/// use http_cache::{HttpCacheOptions, CacheKey};
972/// use http::request::Parts;
973/// use std::sync::Arc;
974///
975/// let options = HttpCacheOptions {
976///     cache_key: Some(Arc::new(|parts: &Parts| {
977///         format!("custom:{}:{}", parts.method, parts.uri.path())
978///     })),
979///     ..Default::default()
980/// };
981/// ```
982///
983/// ## Custom Cache Mode per Request
984/// ```rust
985/// use http_cache::{HttpCacheOptions, CacheMode, CacheModeFn};
986/// use http::request::Parts;
987/// use std::sync::Arc;
988///
989/// let options = HttpCacheOptions {
990///     cache_mode_fn: Some(Arc::new(|parts: &Parts| {
991///         if parts.headers.contains_key("x-no-cache") {
992///             CacheMode::NoStore
993///         } else {
994///             CacheMode::Default
995///         }
996///     })),
997///     ..Default::default()
998/// };
999/// ```
1000///
1001/// ## Response-Based Cache Mode Override
1002/// ```rust
1003/// use http_cache::{HttpCacheOptions, ResponseCacheModeFn, CacheMode};
1004/// use http::request::Parts;
1005/// use http_cache::HttpResponse;
1006/// use std::sync::Arc;
1007///
1008/// let options = HttpCacheOptions {
1009///     response_cache_mode_fn: Some(Arc::new(|_parts: &Parts, response: &HttpResponse| {
1010///         // Force cache 2xx responses even if headers say not to cache
1011///         if response.status >= 200 && response.status < 300 {
1012///             Some(CacheMode::ForceCache)
1013///         } else if response.status == 429 { // Rate limited
1014///             Some(CacheMode::NoStore) // Don't cache rate limit responses
1015///         } else {
1016///             None // Use default behavior
1017///         }
1018///     })),
1019///     ..Default::default()
1020/// };
1021/// ```
1022///
1023/// ## Content-Type Based Cache Mode Override
1024/// ```rust
1025/// use http_cache::{HttpCacheOptions, ResponseCacheModeFn, CacheMode};
1026/// use http::request::Parts;
1027/// use http_cache::HttpResponse;
1028/// use std::sync::Arc;
1029///
1030/// let options = HttpCacheOptions {
1031///     response_cache_mode_fn: Some(Arc::new(|_parts: &Parts, response: &HttpResponse| {
1032///         // Cache different content types with different strategies
1033///         if let Some(content_type) = response.headers.get("content-type") {
1034///             match content_type.as_str() {
1035///                 ct if ct.starts_with("application/json") => Some(CacheMode::ForceCache),
1036///                 ct if ct.starts_with("image/") => Some(CacheMode::Default),
1037///                 ct if ct.starts_with("text/html") => Some(CacheMode::NoStore),
1038///                 _ => None, // Use default behavior for other types
1039///             }
1040///         } else {
1041///             Some(CacheMode::NoStore) // No content-type = don't cache
1042///         }
1043///     })),
1044///     ..Default::default()
1045/// };
1046/// ```
1047///
1048/// ## Cache Busting for Related Resources
1049/// ```rust
1050/// use http_cache::{HttpCacheOptions, CacheBust, CacheKey};
1051/// use http::request::Parts;
1052/// use std::sync::Arc;
1053///
1054/// let options = HttpCacheOptions {
1055///     cache_bust: Some(Arc::new(|parts: &Parts, _cache_key: &Option<CacheKey>, _uri: &str| {
1056///         if parts.method == "POST" && parts.uri.path().starts_with("/api/users") {
1057///             vec![
1058///                 "GET:/api/users".to_string(),
1059///                 "GET:/api/users/list".to_string(),
1060///             ]
1061///         } else {
1062///             vec![]
1063///         }
1064///     })),
1065///     ..Default::default()
1066/// };
1067/// ```
1068#[derive(Clone)]
1069pub struct HttpCacheOptions {
1070    /// Override the default cache options.
1071    pub cache_options: Option<CacheOptions>,
1072    /// Override the default cache key generator.
1073    pub cache_key: Option<CacheKey>,
1074    /// Override the default cache mode.
1075    pub cache_mode_fn: Option<CacheModeFn>,
1076    /// Override cache behavior based on the response received.
1077    /// This function is called after receiving a response and can override
1078    /// the cache mode for that specific response. Returning `None` means
1079    /// use the default cache mode. This allows fine-grained control over
1080    /// caching behavior based on response status, headers, or content.
1081    pub response_cache_mode_fn: Option<ResponseCacheModeFn>,
1082    /// Bust the caches of the returned keys.
1083    pub cache_bust: Option<CacheBust>,
1084    /// Determines if the cache status headers should be added to the response.
1085    pub cache_status_headers: bool,
1086    /// Maximum time-to-live for cached responses.
1087    /// When set, this overrides any longer cache durations specified by the server.
1088    /// Particularly useful with `CacheMode::IgnoreRules` to provide expiration control.
1089    pub max_ttl: Option<Duration>,
1090    /// Rate limiter that applies only on cache misses.
1091    /// When enabled, requests that result in cache hits are returned immediately,
1092    /// while cache misses are rate limited before making network requests.
1093    /// This provides the optimal behavior for web scrapers and similar applications.
1094    #[cfg(feature = "rate-limiting")]
1095    pub rate_limiter: Option<Arc<dyn CacheAwareRateLimiter>>,
1096}
1097
1098impl Default for HttpCacheOptions {
1099    fn default() -> Self {
1100        Self {
1101            cache_options: None,
1102            cache_key: None,
1103            cache_mode_fn: None,
1104            response_cache_mode_fn: None,
1105            cache_bust: None,
1106            cache_status_headers: true,
1107            max_ttl: None,
1108            #[cfg(feature = "rate-limiting")]
1109            rate_limiter: None,
1110        }
1111    }
1112}
1113
1114impl Debug for HttpCacheOptions {
1115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1116        #[cfg(feature = "rate-limiting")]
1117        {
1118            f.debug_struct("HttpCacheOptions")
1119                .field("cache_options", &self.cache_options)
1120                .field("cache_key", &"Fn(&request::Parts) -> String")
1121                .field("cache_mode_fn", &"Fn(&request::Parts) -> CacheMode")
1122                .field(
1123                    "response_cache_mode_fn",
1124                    &"Fn(&request::Parts, &HttpResponse) -> Option<CacheMode>",
1125                )
1126                .field("cache_bust", &"Fn(&request::Parts) -> Vec<String>")
1127                .field("cache_status_headers", &self.cache_status_headers)
1128                .field("max_ttl", &self.max_ttl)
1129                .field("rate_limiter", &"Option<CacheAwareRateLimiter>")
1130                .finish()
1131        }
1132
1133        #[cfg(not(feature = "rate-limiting"))]
1134        {
1135            f.debug_struct("HttpCacheOptions")
1136                .field("cache_options", &self.cache_options)
1137                .field("cache_key", &"Fn(&request::Parts) -> String")
1138                .field("cache_mode_fn", &"Fn(&request::Parts) -> CacheMode")
1139                .field(
1140                    "response_cache_mode_fn",
1141                    &"Fn(&request::Parts, &HttpResponse) -> Option<CacheMode>",
1142                )
1143                .field("cache_bust", &"Fn(&request::Parts) -> Vec<String>")
1144                .field("cache_status_headers", &self.cache_status_headers)
1145                .field("max_ttl", &self.max_ttl)
1146                .finish()
1147        }
1148    }
1149}
1150
1151impl HttpCacheOptions {
1152    fn create_cache_key(
1153        &self,
1154        parts: &request::Parts,
1155        override_method: Option<&str>,
1156    ) -> String {
1157        if let Some(cache_key) = &self.cache_key {
1158            cache_key(parts)
1159        } else {
1160            format!(
1161                "{}:{}",
1162                override_method.unwrap_or_else(|| parts.method.as_str()),
1163                parts.uri
1164            )
1165        }
1166    }
1167
1168    /// Helper function for other crates to generate cache keys for invalidation
1169    /// This ensures consistent cache key generation across all implementations
1170    pub fn create_cache_key_for_invalidation(
1171        &self,
1172        parts: &request::Parts,
1173        method_override: &str,
1174    ) -> String {
1175        self.create_cache_key(parts, Some(method_override))
1176    }
1177
1178    /// Converts http::HeaderMap to HashMap<String, String> for HttpResponse
1179    fn headers_to_hashmap(
1180        headers: &http::HeaderMap,
1181    ) -> HashMap<String, String> {
1182        headers
1183            .iter()
1184            .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
1185            .collect()
1186    }
1187
1188    /// Converts HttpResponse to http::Response with the given body type
1189    pub fn http_response_to_response<B>(
1190        http_response: &HttpResponse,
1191        body: B,
1192    ) -> Result<Response<B>> {
1193        let mut response_builder = Response::builder()
1194            .status(http_response.status)
1195            .version(http_response.version.into());
1196
1197        for (name, value) in &http_response.headers {
1198            if let (Ok(header_name), Ok(header_value)) =
1199                (name.parse::<http::HeaderName>(), value.parse::<HeaderValue>())
1200            {
1201                response_builder =
1202                    response_builder.header(header_name, header_value);
1203            }
1204        }
1205
1206        Ok(response_builder.body(body)?)
1207    }
1208
1209    /// Converts response parts to HttpResponse format for cache mode evaluation
1210    fn parts_to_http_response(
1211        &self,
1212        parts: &response::Parts,
1213        request_parts: &request::Parts,
1214    ) -> Result<HttpResponse> {
1215        Ok(HttpResponse {
1216            body: vec![], // We don't need the full body for cache mode decision
1217            headers: Self::headers_to_hashmap(&parts.headers),
1218            status: parts.status.as_u16(),
1219            url: extract_url_from_request_parts(request_parts)?,
1220            version: parts.version.try_into()?,
1221        })
1222    }
1223
1224    /// Evaluates response-based cache mode override
1225    fn evaluate_response_cache_mode(
1226        &self,
1227        request_parts: &request::Parts,
1228        http_response: &HttpResponse,
1229        original_mode: CacheMode,
1230    ) -> CacheMode {
1231        if let Some(response_cache_mode_fn) = &self.response_cache_mode_fn {
1232            if let Some(override_mode) =
1233                response_cache_mode_fn(request_parts, http_response)
1234            {
1235                return override_mode;
1236            }
1237        }
1238        original_mode
1239    }
1240
1241    /// Creates a cache policy for the given request and response
1242    fn create_cache_policy(
1243        &self,
1244        request_parts: &request::Parts,
1245        response_parts: &response::Parts,
1246    ) -> CachePolicy {
1247        let cache_options = self.cache_options.unwrap_or_default();
1248
1249        // If max_ttl is specified, we need to modify the response headers to enforce it
1250        if let Some(max_ttl) = self.max_ttl {
1251            // Parse existing cache-control header
1252            let cache_control = response_parts
1253                .headers
1254                .get("cache-control")
1255                .and_then(|v| v.to_str().ok())
1256                .unwrap_or("");
1257
1258            // Extract existing max-age if present
1259            let existing_max_age =
1260                cache_control.split(',').find_map(|directive| {
1261                    let directive = directive.trim();
1262                    if directive.starts_with("max-age=") {
1263                        directive.strip_prefix("max-age=")?.parse::<u64>().ok()
1264                    } else {
1265                        None
1266                    }
1267                });
1268
1269            // Convert max_ttl to seconds
1270            let max_ttl_seconds = max_ttl.as_secs();
1271
1272            // Apply max_ttl by setting max-age to the minimum of existing max-age and max_ttl
1273            let effective_max_age = match existing_max_age {
1274                Some(existing) => std::cmp::min(existing, max_ttl_seconds),
1275                None => max_ttl_seconds,
1276            };
1277
1278            // Build new cache-control header
1279            let mut new_directives = Vec::new();
1280
1281            // Add non-max-age directives from existing cache-control
1282            for directive in cache_control.split(',').map(|d| d.trim()) {
1283                if !directive.starts_with("max-age=") && !directive.is_empty() {
1284                    new_directives.push(directive.to_string());
1285                }
1286            }
1287
1288            // Add our effective max-age
1289            new_directives.push(format!("max-age={}", effective_max_age));
1290
1291            let new_cache_control = new_directives.join(", ");
1292
1293            // Create modified response parts - we have to clone since response::Parts has private fields
1294            let mut modified_response_parts = response_parts.clone();
1295            modified_response_parts.headers.insert(
1296                "cache-control",
1297                HeaderValue::from_str(&new_cache_control)
1298                    .unwrap_or_else(|_| HeaderValue::from_static("max-age=0")),
1299            );
1300
1301            CachePolicy::new_options(
1302                request_parts,
1303                &modified_response_parts,
1304                SystemTime::now(),
1305                cache_options,
1306            )
1307        } else {
1308            CachePolicy::new_options(
1309                request_parts,
1310                response_parts,
1311                SystemTime::now(),
1312                cache_options,
1313            )
1314        }
1315    }
1316
1317    /// Determines if a response should be cached based on cache mode and HTTP semantics
1318    fn should_cache_response(
1319        &self,
1320        effective_cache_mode: CacheMode,
1321        http_response: &HttpResponse,
1322        is_get_head: bool,
1323        policy: &CachePolicy,
1324    ) -> bool {
1325        // HTTP status codes that are cacheable by default (RFC 7234)
1326        let is_cacheable_status = matches!(
1327            http_response.status,
1328            200 | 203 | 204 | 206 | 300 | 301 | 404 | 405 | 410 | 414 | 501
1329        );
1330
1331        if is_cacheable_status {
1332            match effective_cache_mode {
1333                CacheMode::ForceCache => is_get_head,
1334                CacheMode::IgnoreRules => true,
1335                CacheMode::NoStore => false,
1336                _ => is_get_head && policy.is_storable(),
1337            }
1338        } else {
1339            false
1340        }
1341    }
1342
1343    /// Common request analysis logic shared between streaming and non-streaming implementations
1344    fn analyze_request_internal(
1345        &self,
1346        parts: &request::Parts,
1347        mode_override: Option<CacheMode>,
1348        default_mode: CacheMode,
1349    ) -> Result<CacheAnalysis> {
1350        let effective_mode = mode_override
1351            .or_else(|| self.cache_mode_fn.as_ref().map(|f| f(parts)))
1352            .unwrap_or(default_mode);
1353
1354        let is_get_head = parts.method == "GET" || parts.method == "HEAD";
1355        let should_cache = effective_mode == CacheMode::IgnoreRules
1356            || (is_get_head && effective_mode != CacheMode::NoStore);
1357
1358        let cache_key = self.create_cache_key(parts, None);
1359
1360        let cache_bust_keys = if let Some(cache_bust) = &self.cache_bust {
1361            cache_bust(parts, &self.cache_key, &cache_key)
1362        } else {
1363            Vec::new()
1364        };
1365
1366        Ok(CacheAnalysis {
1367            cache_key,
1368            should_cache,
1369            cache_mode: effective_mode,
1370            cache_bust_keys,
1371            request_parts: parts.clone(),
1372            is_get_head,
1373        })
1374    }
1375}
1376
1377/// Caches requests according to http spec.
1378#[derive(Debug, Clone)]
1379pub struct HttpCache<T: CacheManager> {
1380    /// Determines the manager behavior.
1381    pub mode: CacheMode,
1382    /// Manager instance that implements the [`CacheManager`] trait.
1383    /// By default, a manager implementation with [`cacache`](https://github.com/zkat/cacache-rs)
1384    /// as the backend has been provided, see [`CACacheManager`].
1385    pub manager: T,
1386    /// Override the default cache options.
1387    pub options: HttpCacheOptions,
1388}
1389
1390/// Streaming version of HTTP cache that supports streaming request/response bodies
1391/// without buffering them in memory.
1392#[derive(Debug, Clone)]
1393pub struct HttpStreamingCache<T: StreamingCacheManager> {
1394    /// Determines the manager behavior.
1395    pub mode: CacheMode,
1396    /// Manager instance that implements the [`StreamingCacheManager`] trait.
1397    pub manager: T,
1398    /// Override the default cache options.
1399    pub options: HttpCacheOptions,
1400}
1401
1402#[allow(dead_code)]
1403impl<T: CacheManager> HttpCache<T> {
1404    /// Determines if the request should be cached
1405    pub fn can_cache_request(
1406        &self,
1407        middleware: &impl Middleware,
1408    ) -> Result<bool> {
1409        let analysis = self.analyze_request(
1410            &middleware.parts()?,
1411            middleware.overridden_cache_mode(),
1412        )?;
1413        Ok(analysis.should_cache)
1414    }
1415
1416    /// Apply rate limiting if enabled in options
1417    #[cfg(feature = "rate-limiting")]
1418    async fn apply_rate_limiting(&self, url: &Url) {
1419        if let Some(rate_limiter) = &self.options.rate_limiter {
1420            let rate_limit_key = url.host_str().unwrap_or("unknown");
1421            rate_limiter.until_key_ready(rate_limit_key).await;
1422        }
1423    }
1424
1425    /// Apply rate limiting if enabled in options (no-op without rate-limiting feature)
1426    #[cfg(not(feature = "rate-limiting"))]
1427    async fn apply_rate_limiting(&self, _url: &Url) {
1428        // No-op when rate limiting feature is not enabled
1429    }
1430
1431    /// Runs the actions to preform when the client middleware is running without the cache
1432    pub async fn run_no_cache(
1433        &self,
1434        middleware: &mut impl Middleware,
1435    ) -> Result<()> {
1436        let parts = middleware.parts()?;
1437
1438        self.manager
1439            .delete(&self.options.create_cache_key(&parts, Some("GET")))
1440            .await
1441            .ok();
1442
1443        let cache_key = self.options.create_cache_key(&parts, None);
1444
1445        if let Some(cache_bust) = &self.options.cache_bust {
1446            for key_to_cache_bust in
1447                cache_bust(&parts, &self.options.cache_key, &cache_key)
1448            {
1449                self.manager.delete(&key_to_cache_bust).await?;
1450            }
1451        }
1452
1453        Ok(())
1454    }
1455
1456    /// Attempts to run the passed middleware along with the cache
1457    pub async fn run(
1458        &self,
1459        mut middleware: impl Middleware,
1460    ) -> Result<HttpResponse> {
1461        // Use the HttpCacheInterface to analyze the request
1462        let analysis = self.analyze_request(
1463            &middleware.parts()?,
1464            middleware.overridden_cache_mode(),
1465        )?;
1466
1467        if !analysis.should_cache {
1468            return self.remote_fetch(&mut middleware).await;
1469        }
1470
1471        // Bust cache keys if needed
1472        for key in &analysis.cache_bust_keys {
1473            self.manager.delete(key).await?;
1474        }
1475
1476        // Look up cached response
1477        if let Some((mut cached_response, policy)) =
1478            self.lookup_cached_response(&analysis.cache_key).await?
1479        {
1480            if self.options.cache_status_headers {
1481                cached_response.cache_lookup_status(HitOrMiss::HIT);
1482            }
1483
1484            // Handle warning headers
1485            if let Some(warning_code) = cached_response.warning_code() {
1486                // https://tools.ietf.org/html/rfc7234#section-4.3.4
1487                //
1488                // If a stored response is selected for update, the cache MUST:
1489                //
1490                // * delete any warning header fields in the stored response with
1491                //   warn-code 1xx (see Section 5.5);
1492                //
1493                // * retain any warning header fields in the stored response with
1494                //   warn-code 2xx;
1495                //
1496                if (100..200).contains(&warning_code) {
1497                    cached_response.remove_warning();
1498                }
1499            }
1500
1501            match analysis.cache_mode {
1502                CacheMode::Default => {
1503                    self.conditional_fetch(middleware, cached_response, policy)
1504                        .await
1505                }
1506                CacheMode::NoCache => {
1507                    middleware.force_no_cache()?;
1508                    let mut res = self.remote_fetch(&mut middleware).await?;
1509                    if self.options.cache_status_headers {
1510                        res.cache_lookup_status(HitOrMiss::HIT);
1511                    }
1512                    Ok(res)
1513                }
1514                CacheMode::ForceCache
1515                | CacheMode::OnlyIfCached
1516                | CacheMode::IgnoreRules => {
1517                    //   112 Disconnected operation
1518                    // SHOULD be included if the cache is intentionally disconnected from
1519                    // the rest of the network for a period of time.
1520                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
1521                    cached_response.add_warning(
1522                        &cached_response.url.clone(),
1523                        112,
1524                        "Disconnected operation",
1525                    );
1526                    if self.options.cache_status_headers {
1527                        cached_response.cache_status(HitOrMiss::HIT);
1528                    }
1529                    Ok(cached_response)
1530                }
1531                _ => self.remote_fetch(&mut middleware).await,
1532            }
1533        } else {
1534            match analysis.cache_mode {
1535                CacheMode::OnlyIfCached => {
1536                    // ENOTCACHED
1537                    let mut res = HttpResponse {
1538                        body: b"GatewayTimeout".to_vec(),
1539                        headers: HashMap::default(),
1540                        status: 504,
1541                        url: middleware.url()?,
1542                        version: HttpVersion::Http11,
1543                    };
1544                    if self.options.cache_status_headers {
1545                        res.cache_status(HitOrMiss::MISS);
1546                        res.cache_lookup_status(HitOrMiss::MISS);
1547                    }
1548                    Ok(res)
1549                }
1550                _ => self.remote_fetch(&mut middleware).await,
1551            }
1552        }
1553    }
1554
1555    fn cache_mode(&self, middleware: &impl Middleware) -> Result<CacheMode> {
1556        Ok(if let Some(mode) = middleware.overridden_cache_mode() {
1557            mode
1558        } else if let Some(cache_mode_fn) = &self.options.cache_mode_fn {
1559            cache_mode_fn(&middleware.parts()?)
1560        } else {
1561            self.mode
1562        })
1563    }
1564
1565    async fn remote_fetch(
1566        &self,
1567        middleware: &mut impl Middleware,
1568    ) -> Result<HttpResponse> {
1569        // Apply rate limiting before making the network request
1570        let url = middleware.url()?;
1571        self.apply_rate_limiting(&url).await;
1572
1573        let mut res = middleware.remote_fetch().await?;
1574        if self.options.cache_status_headers {
1575            res.cache_status(HitOrMiss::MISS);
1576            res.cache_lookup_status(HitOrMiss::MISS);
1577        }
1578        let policy = match self.options.cache_options {
1579            Some(options) => middleware.policy_with_options(&res, options)?,
1580            None => middleware.policy(&res)?,
1581        };
1582        let is_get_head = middleware.is_method_get_head();
1583        let mut mode = self.cache_mode(middleware)?;
1584        let parts = middleware.parts()?;
1585
1586        // Allow response-based cache mode override
1587        if let Some(response_cache_mode_fn) =
1588            &self.options.response_cache_mode_fn
1589        {
1590            if let Some(override_mode) = response_cache_mode_fn(&parts, &res) {
1591                mode = override_mode;
1592            }
1593        }
1594
1595        let is_cacheable = self.options.should_cache_response(
1596            mode,
1597            &res,
1598            is_get_head,
1599            &policy,
1600        );
1601
1602        if is_cacheable {
1603            Ok(self
1604                .manager
1605                .put(self.options.create_cache_key(&parts, None), res, policy)
1606                .await?)
1607        } else if !is_get_head {
1608            self.manager
1609                .delete(&self.options.create_cache_key(&parts, Some("GET")))
1610                .await
1611                .ok();
1612            Ok(res)
1613        } else {
1614            Ok(res)
1615        }
1616    }
1617
1618    async fn conditional_fetch(
1619        &self,
1620        mut middleware: impl Middleware,
1621        mut cached_res: HttpResponse,
1622        mut policy: CachePolicy,
1623    ) -> Result<HttpResponse> {
1624        let parts = middleware.parts()?;
1625        let before_req = policy.before_request(&parts, SystemTime::now());
1626        match before_req {
1627            BeforeRequest::Fresh(parts) => {
1628                cached_res.update_headers(&parts)?;
1629                if self.options.cache_status_headers {
1630                    cached_res.cache_status(HitOrMiss::HIT);
1631                    cached_res.cache_lookup_status(HitOrMiss::HIT);
1632                }
1633                return Ok(cached_res);
1634            }
1635            BeforeRequest::Stale { request: parts, matches } => {
1636                if matches {
1637                    middleware.update_headers(&parts)?;
1638                }
1639            }
1640        }
1641        let req_url = middleware.url()?;
1642        // Apply rate limiting before revalidation request
1643        self.apply_rate_limiting(&req_url).await;
1644        match middleware.remote_fetch().await {
1645            Ok(mut cond_res) => {
1646                let status = StatusCode::from_u16(cond_res.status)?;
1647                if status.is_server_error() && cached_res.must_revalidate() {
1648                    //   111 Revalidation failed
1649                    //   MUST be included if a cache returns a stale response
1650                    //   because an attempt to revalidate the response failed,
1651                    //   due to an inability to reach the server.
1652                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
1653                    cached_res.add_warning(
1654                        &req_url,
1655                        111,
1656                        "Revalidation failed",
1657                    );
1658                    if self.options.cache_status_headers {
1659                        cached_res.cache_status(HitOrMiss::HIT);
1660                    }
1661                    Ok(cached_res)
1662                } else if cond_res.status == 304 {
1663                    let after_res = policy.after_response(
1664                        &parts,
1665                        &cond_res.parts()?,
1666                        SystemTime::now(),
1667                    );
1668                    match after_res {
1669                        AfterResponse::Modified(new_policy, parts)
1670                        | AfterResponse::NotModified(new_policy, parts) => {
1671                            policy = new_policy;
1672                            cached_res.update_headers(&parts)?;
1673                        }
1674                    }
1675                    if self.options.cache_status_headers {
1676                        cached_res.cache_status(HitOrMiss::HIT);
1677                        cached_res.cache_lookup_status(HitOrMiss::HIT);
1678                    }
1679                    let res = self
1680                        .manager
1681                        .put(
1682                            self.options.create_cache_key(&parts, None),
1683                            cached_res,
1684                            policy,
1685                        )
1686                        .await?;
1687                    Ok(res)
1688                } else if cond_res.status == 200 {
1689                    let policy = match self.options.cache_options {
1690                        Some(options) => middleware
1691                            .policy_with_options(&cond_res, options)?,
1692                        None => middleware.policy(&cond_res)?,
1693                    };
1694                    if self.options.cache_status_headers {
1695                        cond_res.cache_status(HitOrMiss::MISS);
1696                        cond_res.cache_lookup_status(HitOrMiss::HIT);
1697                    }
1698                    let res = self
1699                        .manager
1700                        .put(
1701                            self.options.create_cache_key(&parts, None),
1702                            cond_res,
1703                            policy,
1704                        )
1705                        .await?;
1706                    Ok(res)
1707                } else {
1708                    if self.options.cache_status_headers {
1709                        cached_res.cache_status(HitOrMiss::HIT);
1710                    }
1711                    Ok(cached_res)
1712                }
1713            }
1714            Err(e) => {
1715                if cached_res.must_revalidate() {
1716                    Err(e)
1717                } else {
1718                    //   111 Revalidation failed
1719                    //   MUST be included if a cache returns a stale response
1720                    //   because an attempt to revalidate the response failed,
1721                    //   due to an inability to reach the server.
1722                    // (https://tools.ietf.org/html/rfc2616#section-14.46)
1723                    cached_res.add_warning(
1724                        &req_url,
1725                        111,
1726                        "Revalidation failed",
1727                    );
1728                    if self.options.cache_status_headers {
1729                        cached_res.cache_status(HitOrMiss::HIT);
1730                    }
1731                    Ok(cached_res)
1732                }
1733            }
1734        }
1735    }
1736}
1737
1738impl<T: StreamingCacheManager> HttpCacheStreamInterface
1739    for HttpStreamingCache<T>
1740where
1741    <T::Body as http_body::Body>::Data: Send,
1742    <T::Body as http_body::Body>::Error:
1743        Into<StreamingError> + Send + Sync + 'static,
1744{
1745    type Body = T::Body;
1746
1747    fn analyze_request(
1748        &self,
1749        parts: &request::Parts,
1750        mode_override: Option<CacheMode>,
1751    ) -> Result<CacheAnalysis> {
1752        self.options.analyze_request_internal(parts, mode_override, self.mode)
1753    }
1754
1755    async fn lookup_cached_response(
1756        &self,
1757        key: &str,
1758    ) -> Result<Option<(Response<Self::Body>, CachePolicy)>> {
1759        if let Some((mut response, policy)) = self.manager.get(key).await? {
1760            // Add cache status headers if enabled
1761            if self.options.cache_status_headers {
1762                response.headers_mut().insert(
1763                    XCACHE,
1764                    "HIT".parse().map_err(StreamingError::new)?,
1765                );
1766                response.headers_mut().insert(
1767                    XCACHELOOKUP,
1768                    "HIT".parse().map_err(StreamingError::new)?,
1769                );
1770            }
1771            Ok(Some((response, policy)))
1772        } else {
1773            Ok(None)
1774        }
1775    }
1776
1777    async fn process_response<B>(
1778        &self,
1779        analysis: CacheAnalysis,
1780        response: Response<B>,
1781    ) -> Result<Response<Self::Body>>
1782    where
1783        B: http_body::Body + Send + 'static,
1784        B::Data: Send,
1785        B::Error: Into<StreamingError>,
1786        <T::Body as http_body::Body>::Data: Send,
1787        <T::Body as http_body::Body>::Error:
1788            Into<StreamingError> + Send + Sync + 'static,
1789    {
1790        // For non-cacheable requests based on initial analysis, convert them to manager's body type
1791        if !analysis.should_cache {
1792            let mut converted_response =
1793                self.manager.convert_body(response).await?;
1794            // Add cache miss headers
1795            if self.options.cache_status_headers {
1796                converted_response.headers_mut().insert(
1797                    XCACHE,
1798                    "MISS".parse().map_err(StreamingError::new)?,
1799                );
1800                converted_response.headers_mut().insert(
1801                    XCACHELOOKUP,
1802                    "MISS".parse().map_err(StreamingError::new)?,
1803                );
1804            }
1805            return Ok(converted_response);
1806        }
1807
1808        // Bust cache keys if needed
1809        for key in &analysis.cache_bust_keys {
1810            self.manager.delete(key).await?;
1811        }
1812
1813        // Convert response to HttpResponse format for response-based cache mode evaluation
1814        let (parts, body) = response.into_parts();
1815        let http_response = self
1816            .options
1817            .parts_to_http_response(&parts, &analysis.request_parts)?;
1818
1819        // Check for response-based cache mode override
1820        let effective_cache_mode = self.options.evaluate_response_cache_mode(
1821            &analysis.request_parts,
1822            &http_response,
1823            analysis.cache_mode,
1824        );
1825
1826        // Reconstruct response for further processing
1827        let response = Response::from_parts(parts, body);
1828
1829        // If response-based override says NoStore, don't cache
1830        if effective_cache_mode == CacheMode::NoStore {
1831            let mut converted_response =
1832                self.manager.convert_body(response).await?;
1833            // Add cache miss headers
1834            if self.options.cache_status_headers {
1835                converted_response.headers_mut().insert(
1836                    XCACHE,
1837                    "MISS".parse().map_err(StreamingError::new)?,
1838                );
1839                converted_response.headers_mut().insert(
1840                    XCACHELOOKUP,
1841                    "MISS".parse().map_err(StreamingError::new)?,
1842                );
1843            }
1844            return Ok(converted_response);
1845        }
1846
1847        // Create policy for the response
1848        let (parts, body) = response.into_parts();
1849        let policy =
1850            self.options.create_cache_policy(&analysis.request_parts, &parts);
1851
1852        // Reconstruct response for caching
1853        let response = Response::from_parts(parts, body);
1854
1855        let should_cache_response = self.options.should_cache_response(
1856            effective_cache_mode,
1857            &http_response,
1858            analysis.is_get_head,
1859            &policy,
1860        );
1861
1862        if should_cache_response {
1863            // Extract URL from request parts for caching
1864            let request_url =
1865                extract_url_from_request_parts(&analysis.request_parts)?;
1866
1867            // Cache the response using the streaming manager
1868            let mut cached_response = self
1869                .manager
1870                .put(analysis.cache_key, response, policy, request_url)
1871                .await?;
1872
1873            // Add cache miss headers (response is being stored for first time)
1874            if self.options.cache_status_headers {
1875                cached_response.headers_mut().insert(
1876                    XCACHE,
1877                    "MISS".parse().map_err(StreamingError::new)?,
1878                );
1879                cached_response.headers_mut().insert(
1880                    XCACHELOOKUP,
1881                    "MISS".parse().map_err(StreamingError::new)?,
1882                );
1883            }
1884            Ok(cached_response)
1885        } else {
1886            // Don't cache, just convert to manager's body type
1887            let mut converted_response =
1888                self.manager.convert_body(response).await?;
1889            // Add cache miss headers
1890            if self.options.cache_status_headers {
1891                converted_response.headers_mut().insert(
1892                    XCACHE,
1893                    "MISS".parse().map_err(StreamingError::new)?,
1894                );
1895                converted_response.headers_mut().insert(
1896                    XCACHELOOKUP,
1897                    "MISS".parse().map_err(StreamingError::new)?,
1898                );
1899            }
1900            Ok(converted_response)
1901        }
1902    }
1903
1904    fn prepare_conditional_request(
1905        &self,
1906        parts: &mut request::Parts,
1907        _cached_response: &Response<Self::Body>,
1908        policy: &CachePolicy,
1909    ) -> Result<()> {
1910        let before_req = policy.before_request(parts, SystemTime::now());
1911        if let BeforeRequest::Stale { request, .. } = before_req {
1912            parts.headers.extend(request.headers);
1913        }
1914        Ok(())
1915    }
1916
1917    async fn handle_not_modified(
1918        &self,
1919        cached_response: Response<Self::Body>,
1920        fresh_parts: &response::Parts,
1921    ) -> Result<Response<Self::Body>> {
1922        let (mut parts, body) = cached_response.into_parts();
1923
1924        // Update headers from the 304 response
1925        parts.headers.extend(fresh_parts.headers.clone());
1926
1927        Ok(Response::from_parts(parts, body))
1928    }
1929}
1930
1931impl<T: CacheManager> HttpCacheInterface for HttpCache<T> {
1932    fn analyze_request(
1933        &self,
1934        parts: &request::Parts,
1935        mode_override: Option<CacheMode>,
1936    ) -> Result<CacheAnalysis> {
1937        self.options.analyze_request_internal(parts, mode_override, self.mode)
1938    }
1939
1940    async fn lookup_cached_response(
1941        &self,
1942        key: &str,
1943    ) -> Result<Option<(HttpResponse, CachePolicy)>> {
1944        self.manager.get(key).await
1945    }
1946
1947    async fn process_response(
1948        &self,
1949        analysis: CacheAnalysis,
1950        response: Response<Vec<u8>>,
1951    ) -> Result<Response<Vec<u8>>> {
1952        if !analysis.should_cache {
1953            return Ok(response);
1954        }
1955
1956        // Bust cache keys if needed
1957        for key in &analysis.cache_bust_keys {
1958            self.manager.delete(key).await?;
1959        }
1960
1961        // Convert response to HttpResponse format
1962        let (parts, body) = response.into_parts();
1963        let mut http_response = self
1964            .options
1965            .parts_to_http_response(&parts, &analysis.request_parts)?;
1966        http_response.body = body.clone(); // Include the body for buffered cache managers
1967
1968        // Check for response-based cache mode override
1969        let effective_cache_mode = self.options.evaluate_response_cache_mode(
1970            &analysis.request_parts,
1971            &http_response,
1972            analysis.cache_mode,
1973        );
1974
1975        // If response-based override says NoStore, don't cache
1976        if effective_cache_mode == CacheMode::NoStore {
1977            let response = Response::from_parts(parts, body);
1978            return Ok(response);
1979        }
1980
1981        // Create policy and determine if we should cache based on response-based mode
1982        let policy = self.options.create_cache_policy(
1983            &analysis.request_parts,
1984            &http_response.parts()?,
1985        );
1986
1987        let should_cache_response = self.options.should_cache_response(
1988            effective_cache_mode,
1989            &http_response,
1990            analysis.is_get_head,
1991            &policy,
1992        );
1993
1994        if should_cache_response {
1995            let cached_response = self
1996                .manager
1997                .put(analysis.cache_key, http_response, policy)
1998                .await?;
1999
2000            // Convert back to standard Response
2001            let response_parts = cached_response.parts()?;
2002            let mut response = Response::builder()
2003                .status(response_parts.status)
2004                .version(response_parts.version)
2005                .body(cached_response.body)?;
2006
2007            // Copy headers from the response parts
2008            *response.headers_mut() = response_parts.headers;
2009
2010            Ok(response)
2011        } else {
2012            // Don't cache, return original response
2013            let response = Response::from_parts(parts, body);
2014            Ok(response)
2015        }
2016    }
2017
2018    fn prepare_conditional_request(
2019        &self,
2020        parts: &mut request::Parts,
2021        _cached_response: &HttpResponse,
2022        policy: &CachePolicy,
2023    ) -> Result<()> {
2024        let before_req = policy.before_request(parts, SystemTime::now());
2025        if let BeforeRequest::Stale { request, .. } = before_req {
2026            parts.headers.extend(request.headers);
2027        }
2028        Ok(())
2029    }
2030
2031    async fn handle_not_modified(
2032        &self,
2033        mut cached_response: HttpResponse,
2034        fresh_parts: &response::Parts,
2035    ) -> Result<HttpResponse> {
2036        cached_response.update_headers(fresh_parts)?;
2037        if self.options.cache_status_headers {
2038            cached_response.cache_status(HitOrMiss::HIT);
2039            cached_response.cache_lookup_status(HitOrMiss::HIT);
2040        }
2041        Ok(cached_response)
2042    }
2043}
2044
2045#[allow(dead_code)]
2046#[cfg(test)]
2047mod test;