Skip to main content

trillium_cache/
storage.rs

1//! Cache storage trait.
2//!
3//! [`CacheStorage`] is the persistence boundary for a cache: lookup,
4//! streaming insert, and invalidate, all keyed by [`CacheKey`] (URL +
5//! method). A key may hold multiple entries — one per `Vary` signature
6//! — and [`get`] returns the full list under a key. [`CachePolicy`] is
7//! opaque to storage backends; use
8//! [`CachePolicy::same_variant_as`] to dedupe by `Vary` signature when
9//! finalizing an insert.
10//!
11//! ## Streaming
12//!
13//! [`put`] returns a [`PutHandle`] — an [`AsyncWrite`] sink the handler
14//! writes body bytes into as they arrive from the origin. On EOF the
15//! handler calls [`PutHandle::finalize`] with any trailers from the
16//! body source. Dropping a `PutHandle` without finalizing aborts the
17//! store; the partial data is discarded.
18//!
19//! On hit, [`StoredEntry::open`] returns a [`Body`] for replay. The
20//! entry's stored trailers (if any) are attached to the returned Body
21//! via [`Body::new_with_trailers`], so consumers see them by calling
22//! [`Body::trailers`] / [`BodySource::trailers`] on the response body
23//! after reaching EOF.
24//!
25//! [`get`]: CacheStorage::get
26//! [`put`]: CacheStorage::put
27//! [`AsyncWrite`]: futures_lite::AsyncWrite
28//! [`Body::new_with_trailers`]: trillium_http::Body::new_with_trailers
29//! [`Body::trailers`]: trillium_http::Body::trailers
30//! [`BodySource::trailers`]: trillium_http::BodySource::trailers
31
32use crate::CachePolicy;
33use futures_lite::AsyncWrite;
34use std::{
35    fmt::{self, Debug, Display, Formatter},
36    io,
37};
38use trillium_http::{Body, Headers, Method};
39use url::Url;
40
41/// Cache lookup key. Two responses share a key when they share method
42/// + URL; `Vary` distinguishes variants within the entry list.
43#[derive(Clone, PartialEq, Eq, Hash)]
44pub struct CacheKey {
45    method: Method,
46    url: Url,
47}
48
49impl Display for CacheKey {
50    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
51        write!(f, "{} {}", self.method, self.url)
52    }
53}
54
55impl Debug for CacheKey {
56    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
57        f.debug_tuple("CacheKey")
58            .field(&format_args!("{self}"))
59            .finish()
60    }
61}
62
63impl CacheKey {
64    /// Construct a cache key.
65    pub fn new(method: Method, url: Url) -> Self {
66        Self { method, url }
67    }
68
69    /// Method this key was constructed with.
70    pub fn method(&self) -> Method {
71        self.method
72    }
73
74    /// URL this key was constructed with.
75    pub fn url(&self) -> &Url {
76        &self.url
77    }
78}
79
80/// Storage backend for cached responses.
81///
82/// A key may carry multiple entries, one per `Vary` signature. [`get`]
83/// returns the full list under a key; the cache handler picks among
84/// them internally.
85///
86/// Writes are streaming — [`put`] returns a [`PutHandle`] that the
87/// caller writes bytes into as they arrive. The handler signals end-of-
88/// stream by calling [`PutHandle::finalize`]; dropping the handle
89/// without finalizing aborts the write.
90///
91/// [`get`]: CacheStorage::get
92/// [`put`]: CacheStorage::put
93pub trait CacheStorage: Debug + Send + Sync + 'static {
94    /// Concrete entry type returned by [`get`][Self::get].
95    type StoredEntry: StoredEntry;
96
97    /// Streaming writer returned by [`put`][Self::put].
98    type PutHandle: PutHandle;
99
100    /// Fetch all entries stored under `key`. Returns an empty vec when
101    /// the key has no entries.
102    fn get(&self, key: &CacheKey) -> impl Future<Output = Vec<Self::StoredEntry>> + Send;
103
104    /// Open a streaming insert for `key` with the supplied policy.
105    /// Returns a [`PutHandle`] that the caller writes body bytes into,
106    /// then closes with [`PutHandle::finalize`]. If an existing entry
107    /// has the same `Vary` signature, finalize replaces it; otherwise
108    /// the new entry is appended.
109    ///
110    /// Returning `Err` aborts the cache write — the caller passes the
111    /// origin response through to the user but does not cache it.
112    fn put(
113        &self,
114        key: CacheKey,
115        policy: CachePolicy,
116    ) -> impl Future<Output = io::Result<Self::PutHandle>> + Send;
117
118    /// Remove all entries stored under `key`.
119    fn invalidate(&self, key: &CacheKey) -> impl Future<Output = ()> + Send;
120}
121
122/// One stored response.
123///
124/// Returned by [`CacheStorage::get`]. Cheap to hold and pass around —
125/// in-memory backends share underlying buffers via [`Arc`][std::sync::Arc],
126/// and other backends typically hold only metadata until
127/// [`open`][Self::open] is called. The [`Clone`] bound supports the cache
128/// handler's stale-while-revalidate flow, which needs one handle to serve
129/// the stale entry to the user and another to drive background
130/// revalidation; for typical backends `clone` is a cheap pointer copy.
131pub trait StoredEntry: Clone + Debug + Send + Sync + 'static {
132    /// Borrow the [`CachePolicy`] this entry was stored with.
133    fn policy(&self) -> &CachePolicy;
134
135    /// Replace the stored policy without rewriting the body.
136    ///
137    /// Used on a successful 304 revalidation (RFC 9111 §3.2) to refresh
138    /// validators and freshness directives while keeping the previously
139    /// stored body bytes. The supplied policy carries the merged
140    /// stored+304 headers and a fresh `response_time`.
141    fn refresh_policy(
142        &mut self,
143        new_policy: CachePolicy,
144    ) -> impl Future<Output = io::Result<()>> + Send;
145
146    /// Open the stored response body for replay.
147    ///
148    /// Consumes the entry and returns a [`Body`] that yields the stored
149    /// bytes when read. If trailers were captured on store, they are
150    /// attached via [`Body::new_with_trailers`] and surface to readers
151    /// after EOF via [`BodySource::trailers`][trillium_http::BodySource::trailers].
152    fn open(self) -> impl Future<Output = io::Result<Body>> + Send;
153}
154
155/// Streaming writer returned by [`CacheStorage::put`].
156///
157/// Write body bytes via the [`AsyncWrite`] impl, then call
158/// [`finalize`][Self::finalize] once the body is fully consumed.
159/// Dropping a `PutHandle` without finalizing aborts the write; partial
160/// data MUST NOT be exposed by a subsequent [`CacheStorage::get`].
161pub trait PutHandle: AsyncWrite + Send + Unpin + 'static {
162    /// Commit the buffered bytes to storage with any trailers from the
163    /// body source.
164    ///
165    /// `trailers` is `Some` when the body source produced a trailers
166    /// section (a `BodySource` whose `trailers()` returned `Some`
167    /// after EOF), `None` otherwise — distinguishing "no trailers
168    /// section" from "empty trailers section."
169    fn finalize(self, trailers: Option<Headers>) -> impl Future<Output = io::Result<()>> + Send;
170}