File size: 2,850 Bytes
d8ffec9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
use axum::{
    body::Body,
    http::{header, Response},
    response::IntoResponse,
};

// ─── Cache Policy Constants ───────────────────────────────────────────────────
/// Authenticated user data — never cache at CDN layer.
pub const CACHE_PRIVATE: &str = "private, no-store";

/// Short-lived public data (60s). Ideal for dashboard summaries and analytics.
/// `s-maxage` instructs CDN; `stale-while-revalidate` allows background refresh.
pub const CACHE_SHORT: &str = "public, max-age=60, s-maxage=60, stale-while-revalidate=30";

/// Medium-lived public data (5 min). For product listings and storefront pages.
pub const CACHE_MEDIUM: &str = "public, max-age=300, s-maxage=300, stale-while-revalidate=60";

/// Immutable long-lived content (24h). For static assets with content hashes.
pub const CACHE_LONG: &str = "public, max-age=86400, s-maxage=86400, immutable";

/// Always revalidate — for exports and reports that must be fresh.
pub const CACHE_REVALIDATE: &str = "public, max-age=0, must-revalidate";

// ─── Helpers ─────────────────────────────────────────────────────────────────

/// Inject `Cache-Control` and `Vary: Accept-Encoding` headers into any response.
pub fn with_cache<R: IntoResponse>(response: R, policy: &'static str) -> Response<Body> {
    let mut res = response.into_response();
    let headers = res.headers_mut();
    headers.insert(
        header::CACHE_CONTROL,
        header::HeaderValue::from_static(policy),
    );
    headers.insert(
        header::VARY,
        header::HeaderValue::from_static("Accept-Encoding"),
    );
    res
}

/// Add ETag derived from a resource version/timestamp for conditional GETs.
pub fn with_etag<R: IntoResponse>(response: R, version: &str) -> Response<Body> {
    let etag = format!("\"{}\"", version);
    let mut res = response.into_response();
    if let Ok(value) = header::HeaderValue::from_str(&etag) {
        res.headers_mut().insert(header::ETAG, value);
    }
    res
}

/// Combine cache policy and ETag in one call.
pub fn with_cache_and_etag<R: IntoResponse>(
    response: R,
    policy: &'static str,
    version: &str,
) -> Response<Body> {
    let etag = format!("\"{}\"", version);
    let mut res = response.into_response();
    let headers = res.headers_mut();
    headers.insert(
        header::CACHE_CONTROL,
        header::HeaderValue::from_static(policy),
    );
    headers.insert(
        header::VARY,
        header::HeaderValue::from_static("Accept-Encoding"),
    );
    if let Ok(value) = header::HeaderValue::from_str(&etag) {
        headers.insert(header::ETAG, value);
    }
    res
}