Skip to main content

hitbox_core/
key.rs

1//! Cache key types and construction.
2//!
3//! This module provides types for building and representing cache keys:
4//!
5//! - [`CacheKey`] - The complete cache key with prefix, version, and parts
6//! - [`KeyPart`] - A single key-value component of a cache key
7//! - [`KeyParts`] - Builder for accumulating key parts during extraction
8//!
9//! ## Key Structure
10//!
11//! Cache keys have three components:
12//!
13//! 1. **Prefix** - Optional namespace for grouping related keys
14//! 2. **Version** - Numeric version for cache invalidation
15//! 3. **Parts** - List of key-value pairs extracted from requests
16//!
17//! ## Format
18//!
19//! When serialized to string, keys follow this format:
20//! `{prefix}:v{version}:key1=value1&key2=value2`
21//!
22//! - Prefix is omitted if empty
23//! - Version is omitted if zero
24//!
25//! ```
26//! use hitbox_core::{CacheKey, KeyPart};
27//!
28//! // Full format: prefix + version + parts
29//! let key = CacheKey::new("api", 1, vec![KeyPart::new("id", Some("42"))]);
30//! assert_eq!(format!("{}", key), "api:v1:id=42");
31//!
32//! // No prefix
33//! let key = CacheKey::new("", 2, vec![KeyPart::new("id", Some("42"))]);
34//! assert_eq!(format!("{}", key), "v2:id=42");
35//!
36//! // No version (v0)
37//! let key = CacheKey::new("cache", 0, vec![KeyPart::new("id", Some("42"))]);
38//! assert_eq!(format!("{}", key), "cache:id=42");
39//!
40//! // No prefix, no version
41//! let key = CacheKey::new("", 0, vec![KeyPart::new("id", Some("42"))]);
42//! assert_eq!(format!("{}", key), "id=42");
43//!
44//! // KeyPart with None value (key only, no value)
45//! let key = CacheKey::new("", 0, vec![KeyPart::new("flag", None::<&str>)]);
46//! assert_eq!(format!("{}", key), "flag");
47//!
48//! // Mixed: Some and None values
49//! let key = CacheKey::new("api", 1, vec![
50//!     KeyPart::new("method", Some("GET")),
51//!     KeyPart::new("cached", None::<&str>),
52//! ]);
53//! assert_eq!(format!("{}", key), "api:v1:method=GET&cached");
54//! ```
55//!
56//! ## Performance
57//!
58//! [`CacheKey`] uses `Arc` internally for cheap cloning - copying a key
59//! only increments a reference count rather than cloning all parts.
60//!
61//! [`KeyPart`] uses [`SmolStr`] for small string optimization - short
62//! strings (≤23 bytes) are stored inline without heap allocation.
63
64use bitcode::__private::{
65    Buffer, Decoder, Encoder, Result, VariantDecoder, VariantEncoder, Vec, View,
66};
67use bitcode::{Decode, Encode};
68use core::num::NonZeroUsize;
69use smol_str::SmolStr;
70use std::fmt;
71use std::hash::{Hash, Hasher};
72use std::sync::Arc;
73
74/// Inner structure containing the actual cache key data.
75/// Wrapped in Arc for cheap cloning.
76#[derive(Debug, Eq, PartialEq, Hash, serde::Serialize)]
77struct CacheKeyInner {
78    parts: Vec<KeyPart>,
79    version: u32,
80    prefix: SmolStr,
81    /// Precalculated size of heap-allocated string content.
82    /// Only counts strings >23 bytes (SmolStr's inline threshold).
83    #[serde(skip)]
84    content_size: usize,
85}
86
87/// A cache key identifying a cached entry.
88///
89/// Cache keys are composed of:
90/// - A **prefix** for namespacing (e.g., "api", "users")
91/// - A **version** number for cache invalidation
92/// - A list of **parts** (key-value pairs) extracted from requests
93///
94/// # Cheap Cloning
95///
96/// `CacheKey` wraps its data in [`Arc`], making `clone()` an O(1) operation
97/// that only increments a reference count. This is important because keys
98/// are frequently passed around during cache operations.
99///
100/// # Example
101///
102/// ```
103/// use hitbox_core::{CacheKey, KeyPart};
104///
105/// let key = CacheKey::new(
106///     "api",
107///     1,
108///     vec![
109///         KeyPart::new("method", Some("GET")),
110///         KeyPart::new("path", Some("/users/123")),
111///     ],
112/// );
113///
114/// assert_eq!(key.prefix(), "api");
115/// assert_eq!(key.version(), 1);
116/// assert_eq!(format!("{}", key), "api:v1:method=GET&path=/users/123");
117/// ```
118#[derive(Clone, Debug, serde::Serialize)]
119#[serde(into = "CacheKeyInner")]
120pub struct CacheKey {
121    inner: Arc<CacheKeyInner>,
122}
123
124impl PartialEq for CacheKey {
125    fn eq(&self, other: &Self) -> bool {
126        // Fast path: same Arc pointer
127        Arc::ptr_eq(&self.inner, &other.inner) || self.inner == other.inner
128    }
129}
130
131impl Eq for CacheKey {}
132
133impl Hash for CacheKey {
134    fn hash<H: Hasher>(&self, state: &mut H) {
135        self.inner.hash(state);
136    }
137}
138
139impl From<CacheKey> for CacheKeyInner {
140    fn from(key: CacheKey) -> Self {
141        // Try to unwrap Arc, or clone if shared
142        Arc::try_unwrap(key.inner).unwrap_or_else(|arc| (*arc).clone())
143    }
144}
145
146impl Clone for CacheKeyInner {
147    fn clone(&self) -> Self {
148        CacheKeyInner {
149            parts: self.parts.clone(),
150            version: self.version,
151            prefix: self.prefix.clone(),
152            content_size: self.content_size,
153        }
154    }
155}
156
157impl CacheKeyInner {
158    /// Calculate the size of heap-allocated string content.
159    ///
160    /// SmolStr stores strings ≤23 bytes inline (already counted in struct size).
161    /// Only strings >23 bytes allocate on heap and need additional counting.
162    fn calculate_content_size(prefix: &SmolStr, parts: &[KeyPart]) -> usize {
163        let heap_size = |len: usize| len.saturating_sub(23);
164
165        heap_size(prefix.len())
166            + parts
167                .iter()
168                .map(|p| heap_size(p.key().len()) + p.value().map_or(0, |v| heap_size(v.len())))
169                .sum::<usize>()
170    }
171}
172
173impl fmt::Display for CacheKey {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        // Compact format: prefix:v{version}:key=value&key2=value2
176        if !self.inner.prefix.is_empty() {
177            write!(f, "{}:", self.inner.prefix)?;
178        }
179        if self.inner.version > 0 {
180            write!(f, "v{}:", self.inner.version)?;
181        }
182        for (i, part) in self.inner.parts.iter().enumerate() {
183            if i > 0 {
184                write!(f, "&")?;
185            }
186            write!(f, "{}", part)?;
187        }
188        Ok(())
189    }
190}
191
192impl Encode for CacheKey {
193    type Encoder = CacheKeyEncoder;
194}
195
196impl<'de> Decode<'de> for CacheKey {
197    type Decoder = CacheKeyDecoder<'de>;
198}
199
200#[doc(hidden)]
201#[derive(Default)]
202pub struct CacheKeyEncoder {
203    parts: <Vec<KeyPart> as Encode>::Encoder,
204    version: <u32 as Encode>::Encoder,
205    prefix: <str as Encode>::Encoder,
206}
207
208impl Encoder<CacheKey> for CacheKeyEncoder {
209    fn encode(&mut self, value: &CacheKey) {
210        self.parts.encode(&value.inner.parts);
211        self.version.encode(&value.inner.version);
212        self.prefix.encode(value.inner.prefix.as_str());
213    }
214}
215
216impl Buffer for CacheKeyEncoder {
217    fn collect_into(&mut self, out: &mut Vec<u8>) {
218        self.parts.collect_into(out);
219        self.version.collect_into(out);
220        self.prefix.collect_into(out);
221    }
222
223    fn reserve(&mut self, additional: NonZeroUsize) {
224        self.parts.reserve(additional);
225        self.version.reserve(additional);
226        self.prefix.reserve(additional);
227    }
228}
229
230#[doc(hidden)]
231#[derive(Default)]
232pub struct CacheKeyDecoder<'de> {
233    parts: <Vec<KeyPart> as Decode<'de>>::Decoder,
234    version: <u32 as Decode<'de>>::Decoder,
235    prefix: <&'de str as Decode<'de>>::Decoder,
236}
237
238impl<'de> View<'de> for CacheKeyDecoder<'de> {
239    fn populate(&mut self, input: &mut &'de [u8], length: usize) -> Result<()> {
240        self.parts.populate(input, length)?;
241        self.version.populate(input, length)?;
242        self.prefix.populate(input, length)?;
243        Ok(())
244    }
245}
246
247impl<'de> Decoder<'de, CacheKey> for CacheKeyDecoder<'de> {
248    fn decode(&mut self) -> CacheKey {
249        let prefix_str: &str = self.prefix.decode();
250        let prefix = SmolStr::new(prefix_str);
251        let parts: Vec<KeyPart> = self.parts.decode();
252        let content_size = CacheKeyInner::calculate_content_size(&prefix, &parts);
253        CacheKey {
254            inner: Arc::new(CacheKeyInner {
255                parts,
256                version: self.version.decode(),
257                prefix,
258                content_size,
259            }),
260        }
261    }
262}
263
264impl CacheKey {
265    /// Returns an iterator over the key parts.
266    pub fn parts(&self) -> impl Iterator<Item = &KeyPart> {
267        self.inner.parts.iter()
268    }
269
270    /// Returns the cache key version number.
271    pub fn version(&self) -> u32 {
272        self.inner.version
273    }
274
275    /// Returns the cache key prefix.
276    pub fn prefix(&self) -> &str {
277        &self.inner.prefix
278    }
279
280    /// Returns the estimated memory usage of this cache key in bytes.
281    ///
282    /// This includes:
283    /// - Arc heap allocation (control block + CacheKeyInner)
284    /// - Vec heap allocation (KeyPart elements)
285    /// - SmolStr heap allocations (strings >23 bytes)
286    pub fn memory_size(&self) -> usize {
287        use std::mem::size_of;
288
289        // Arc heap allocation: strong count + weak count + data
290        let arc_overhead = 2 * size_of::<usize>() + size_of::<CacheKeyInner>();
291
292        // Vec heap allocation: each KeyPart element
293        let vec_overhead = self.inner.parts.len() * size_of::<KeyPart>();
294
295        // SmolStr heap allocations: only strings >23 bytes
296        let heap_strings = self.inner.content_size;
297
298        arc_overhead + vec_overhead + heap_strings
299    }
300
301    /// Creates a new cache key with the given components.
302    ///
303    /// # Arguments
304    ///
305    /// * `prefix` - Namespace prefix for the key
306    /// * `version` - Version number for cache invalidation
307    /// * `parts` - List of key-value parts
308    pub fn new(prefix: impl Into<SmolStr>, version: u32, parts: Vec<KeyPart>) -> Self {
309        let prefix = prefix.into();
310        let content_size = CacheKeyInner::calculate_content_size(&prefix, &parts);
311        CacheKey {
312            inner: Arc::new(CacheKeyInner {
313                parts,
314                version,
315                prefix,
316                content_size,
317            }),
318        }
319    }
320
321    /// Creates a simple cache key with a single key-value part.
322    ///
323    /// The prefix is empty and version is 0.
324    pub fn from_str(key: &str, value: &str) -> Self {
325        let prefix = SmolStr::default();
326        let parts = vec![KeyPart::new(key, Some(value))];
327        let content_size = CacheKeyInner::calculate_content_size(&prefix, &parts);
328        CacheKey {
329            inner: Arc::new(CacheKeyInner {
330                parts,
331                version: 0,
332                prefix,
333                content_size,
334            }),
335        }
336    }
337
338    /// Creates a cache key from a slice of key-value pairs.
339    ///
340    /// The prefix is empty and version is 0.
341    pub fn from_slice(parts: &[(&str, Option<&str>)]) -> Self {
342        let prefix = SmolStr::default();
343        let parts: Vec<KeyPart> = parts
344            .iter()
345            .map(|(key, value)| KeyPart::new(key, *value))
346            .collect();
347        let content_size = CacheKeyInner::calculate_content_size(&prefix, &parts);
348        CacheKey {
349            inner: Arc::new(CacheKeyInner {
350                parts,
351                version: 0,
352                prefix,
353                content_size,
354            }),
355        }
356    }
357}
358
359/// A single component of a cache key.
360///
361/// Each part represents a key-value pair extracted from a request.
362/// The value is optional - some parts may be key-only (flags).
363///
364/// # String Optimization
365///
366/// Both key and value use [`SmolStr`] which stores small strings (≤23 bytes)
367/// inline without heap allocation. This is efficient for typical cache key
368/// components like "method", "path", "GET", etc.
369///
370/// # Example
371///
372/// ```
373/// use hitbox_core::KeyPart;
374///
375/// // Key with value
376/// let method = KeyPart::new("method", Some("GET"));
377/// assert_eq!(method.key(), "method");
378/// assert_eq!(method.value(), Some("GET"));
379///
380/// // Key without value (flag)
381/// let flag = KeyPart::new("cached", None::<&str>);
382/// assert_eq!(flag.key(), "cached");
383/// assert_eq!(flag.value(), None);
384/// ```
385#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
386pub struct KeyPart {
387    key: SmolStr,
388    value: Option<SmolStr>,
389}
390
391impl fmt::Display for KeyPart {
392    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393        write!(f, "{}", self.key)?;
394        if let Some(ref value) = self.value {
395            write!(f, "={}", value)?;
396        }
397        Ok(())
398    }
399}
400
401impl Encode for KeyPart {
402    type Encoder = KeyPartEncoder;
403}
404
405impl<'de> Decode<'de> for KeyPart {
406    type Decoder = KeyPartDecoder<'de>;
407}
408
409#[doc(hidden)]
410#[derive(Default)]
411pub struct KeyPartEncoder {
412    key: <str as Encode>::Encoder,
413    // Manual Option encoding: variant (0=None, 1=Some) + value
414    value_variant: VariantEncoder<2>,
415    value_str: <str as Encode>::Encoder,
416}
417
418impl Encoder<KeyPart> for KeyPartEncoder {
419    fn encode(&mut self, value: &KeyPart) {
420        self.key.encode(value.key.as_str());
421        // Manually encode Option<SmolStr> as variant + str
422        self.value_variant.encode(&(value.value.is_some() as u8));
423        if let Some(ref v) = value.value {
424            self.value_str.reserve(NonZeroUsize::MIN);
425            self.value_str.encode(v.as_str());
426        }
427    }
428}
429
430impl Buffer for KeyPartEncoder {
431    fn collect_into(&mut self, out: &mut Vec<u8>) {
432        self.key.collect_into(out);
433        self.value_variant.collect_into(out);
434        self.value_str.collect_into(out);
435    }
436
437    fn reserve(&mut self, additional: NonZeroUsize) {
438        self.key.reserve(additional);
439        self.value_variant.reserve(additional);
440        // Don't reserve for value_str - we don't know how many are Some
441    }
442}
443
444#[doc(hidden)]
445#[derive(Default)]
446pub struct KeyPartDecoder<'de> {
447    key: <&'de str as Decode<'de>>::Decoder,
448    value_variant: VariantDecoder<'de, 2, false>,
449    value_str: <&'de str as Decode<'de>>::Decoder,
450}
451
452impl<'de> View<'de> for KeyPartDecoder<'de> {
453    fn populate(&mut self, input: &mut &'de [u8], length: usize) -> Result<()> {
454        self.key.populate(input, length)?;
455        self.value_variant.populate(input, length)?;
456        // Get the count of Some values from variant decoder
457        let some_count = self.value_variant.length(1);
458        self.value_str.populate(input, some_count)?;
459        Ok(())
460    }
461}
462
463impl<'de> Decoder<'de, KeyPart> for KeyPartDecoder<'de> {
464    fn decode(&mut self) -> KeyPart {
465        let key_str: &str = self.key.decode();
466        let value = if self.value_variant.decode() != 0 {
467            let value_str: &str = self.value_str.decode();
468            Some(SmolStr::new(value_str))
469        } else {
470            None
471        };
472        KeyPart {
473            key: SmolStr::new(key_str),
474            value,
475        }
476    }
477}
478
479impl KeyPart {
480    /// Creates a new key part.
481    ///
482    /// # Arguments
483    ///
484    /// * `key` - The key name
485    /// * `value` - Optional value associated with the key
486    pub fn new<K: AsRef<str>, V: AsRef<str>>(key: K, value: Option<V>) -> Self {
487        KeyPart {
488            key: SmolStr::new(key),
489            value: value.map(SmolStr::new),
490        }
491    }
492
493    /// Returns the key name.
494    pub fn key(&self) -> &str {
495        &self.key
496    }
497
498    /// Returns the optional value.
499    pub fn value(&self) -> Option<&str> {
500        self.value.as_deref()
501    }
502}
503
504/// Builder for accumulating cache key parts during extraction.
505///
506/// `KeyParts` carries both the subject being processed and the accumulated
507/// key parts. This allows extractors to be chained while building up the
508/// complete cache key.
509///
510/// # Type Parameter
511///
512/// * `T` - The subject type (usually a request type)
513///
514/// # Usage
515///
516/// Extractors receive a `KeyParts<T>`, add their parts, and return it for
517/// the next extractor in the chain. Finally, `into_cache_key()` is called
518/// to produce the final [`CacheKey`].
519#[derive(Debug)]
520pub struct KeyParts<T: Sized> {
521    subject: T,
522    parts: Vec<KeyPart>,
523}
524
525impl<T> KeyParts<T> {
526    /// Creates a new `KeyParts` wrapping the given subject.
527    pub fn new(subject: T) -> Self {
528        KeyParts {
529            subject,
530            parts: Vec::new(),
531        }
532    }
533
534    /// Adds a single key part.
535    pub fn push(&mut self, part: KeyPart) {
536        self.parts.push(part)
537    }
538
539    /// Appends multiple key parts from a vector.
540    pub fn append(&mut self, parts: &mut Vec<KeyPart>) {
541        self.parts.append(parts)
542    }
543
544    /// Consumes the builder and returns the subject with its cache key.
545    ///
546    /// The returned cache key has an empty prefix and version 0.
547    pub fn into_cache_key(self) -> (T, CacheKey) {
548        let prefix = SmolStr::default();
549        let content_size = CacheKeyInner::calculate_content_size(&prefix, &self.parts);
550        (
551            self.subject,
552            CacheKey {
553                inner: Arc::new(CacheKeyInner {
554                    version: 0,
555                    prefix,
556                    parts: self.parts,
557                    content_size,
558                }),
559            },
560        )
561    }
562}