http_types_rs/cache/cache_control/
cache_directive.rs

1use crate::headers::HeaderValue;
2use crate::Status;
3
4use std::time::Duration;
5
6/// An HTTP `Cache-Control` directive.
7#[non_exhaustive]
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum CacheDirective {
10    /// The response body will not change over time.
11    Immutable,
12    /// The maximum amount of time a resource is considered fresh.
13    MaxAge(Duration),
14    /// Indicates the client will accept a stale response.
15    MaxStale(Option<Duration>),
16    /// A response that will still be fresh for at least the specified duration.
17    MinFresh(Duration),
18    /// Once a response is stale, a fresh response must be retrieved.
19    MustRevalidate,
20    /// The response may be cached, but must always be revalidated before being used.
21    NoCache,
22    /// The response may not be cached.
23    NoStore,
24    /// An intermediate cache or proxy should not edit the response body,
25    /// Content-Encoding, Content-Range, or Content-Type.
26    NoTransform,
27    /// Do not use the network for a response.
28    OnlyIfCached,
29    /// The response may be stored only by a browser's cache, even if the
30    /// response is normally non-cacheable.
31    Private,
32    /// Like must-revalidate, but only for shared caches (e.g., proxies).
33    ProxyRevalidate,
34    /// The response may be stored by any cache, even if the response is normally
35    /// non-cacheable.
36    Public,
37    /// Overrides max-age or the Expires header, but only for shared caches.
38    SMaxAge(Duration),
39    /// The client will accept a stale response if retrieving a fresh one fails.
40    StaleIfError(Duration),
41    /// Indicates the client will accept a stale response, while asynchronously
42    /// checking in the background for a fresh one.
43    StaleWhileRevalidate(Duration),
44}
45
46impl CacheDirective {
47    /// Check whether this directive is valid in an HTTP request.
48    pub fn valid_in_req(&self) -> bool {
49        use CacheDirective::*;
50        matches!(
51            self,
52            MaxAge(_) | MaxStale(_) | MinFresh(_) | NoCache | NoStore | NoTransform | OnlyIfCached
53        )
54    }
55
56    /// Check whether this directive is valid in an HTTP response.
57    pub fn valid_in_res(&self) -> bool {
58        use CacheDirective::*;
59        matches!(
60            self,
61            MustRevalidate
62                | NoCache
63                | NoStore
64                | NoTransform
65                | Public
66                | Private
67                | ProxyRevalidate
68                | MaxAge(_)
69                | SMaxAge(_)
70                | StaleIfError(_)
71                | StaleWhileRevalidate(_)
72        )
73    }
74
75    /// Create an instance from a string slice.
76    //
77    // This is a private method rather than a trait because we assume the
78    // input string is a single-value only. This is upheld by the calling
79    // function, but we cannot guarantee this to be true in the general
80    // sense.
81    pub(crate) fn from_str(s: &str) -> crate::Result<Option<Self>> {
82        use CacheDirective::*;
83
84        let s = s.trim();
85
86        // We're dealing with an empty string.
87        if s.is_empty() {
88            return Ok(None);
89        }
90
91        let s = s.to_lowercase();
92        let mut parts = s.split('=');
93        let next = parts.next().unwrap();
94
95        let mut get_dur = || -> crate::Result<Duration> {
96            let dur = parts.next().status(400)?;
97            let dur: u64 = dur.parse::<u64>().status(400)?;
98            Ok(Duration::new(dur, 0))
99        };
100
101        // This won't panic because each input string has at least one part.
102        let res = match next {
103            "immutable" => Some(Immutable),
104            "no-cache" => Some(NoCache),
105            "no-store" => Some(NoStore),
106            "no-transform" => Some(NoTransform),
107            "only-if-cached" => Some(OnlyIfCached),
108            "must-revalidate" => Some(MustRevalidate),
109            "public" => Some(Public),
110            "private" => Some(Private),
111            "proxy-revalidate" => Some(ProxyRevalidate),
112            "max-age" => Some(MaxAge(get_dur()?)),
113            "max-stale" => match parts.next() {
114                Some(secs) => {
115                    let dur: u64 = secs.parse::<u64>().status(400)?;
116                    Some(MaxStale(Some(Duration::new(dur, 0))))
117                }
118                None => Some(MaxStale(None)),
119            },
120            "min-fresh" => Some(MinFresh(get_dur()?)),
121            "s-maxage" => Some(SMaxAge(get_dur()?)),
122            "stale-if-error" => Some(StaleIfError(get_dur()?)),
123            "stale-while-revalidate" => Some(StaleWhileRevalidate(get_dur()?)),
124            _ => None,
125        };
126        Ok(res)
127    }
128}
129
130impl From<CacheDirective> for HeaderValue {
131    fn from(directive: CacheDirective) -> Self {
132        use CacheDirective::*;
133        let h = |s: String| unsafe { HeaderValue::from_bytes_unchecked(s.into_bytes()) };
134
135        match directive {
136            Immutable => h("immutable".to_string()),
137            MaxAge(dur) => h(format!("max-age={}", dur.as_secs())),
138            MaxStale(dur) => match dur {
139                Some(dur) => h(format!("max-stale={}", dur.as_secs())),
140                None => h("max-stale".to_string()),
141            },
142            MinFresh(dur) => h(format!("min-fresh={}", dur.as_secs())),
143            MustRevalidate => h("must-revalidate".to_string()),
144            NoCache => h("no-cache".to_string()),
145            NoStore => h("no-store".to_string()),
146            NoTransform => h("no-transform".to_string()),
147            OnlyIfCached => h("only-if-cached".to_string()),
148            Private => h("private".to_string()),
149            ProxyRevalidate => h("proxy-revalidate".to_string()),
150            Public => h("public".to_string()),
151            SMaxAge(dur) => h(format!("s-max-age={}", dur.as_secs())),
152            StaleIfError(dur) => h(format!("stale-if-error={}", dur.as_secs())),
153            StaleWhileRevalidate(dur) => h(format!("stale-while-revalidate={}", dur.as_secs())),
154        }
155    }
156}