Skip to main content

hitbox_backend/
key.rs

1//! Cache key serialization formats.
2//!
3//! This module provides different strategies for serializing [`CacheKey`] to bytes
4//! for storage in backends.
5//!
6//! # Available Formats
7//!
8//! | Format | Size | Reversible | Use Case |
9//! |--------|------|------------|----------|
10//! | [`Bitcode`](CacheKeyFormat::Bitcode) | Compact | Yes | Default, most backends |
11//! | [`UrlEncoded`](CacheKeyFormat::UrlEncoded) | Larger | Yes | Debugging, human-readable keys |
12
13use std::{iter::once, str::from_utf8};
14
15use hitbox_core::{CacheKey, KeyPart};
16
17use crate::format::FormatError;
18
19const PREFIX_KEY: &str = "_prefix";
20const VERSION_KEY: &str = "_version";
21
22/// Cache key serialization format.
23///
24/// Determines how [`CacheKey`] is converted to bytes for storage.
25#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
26pub enum CacheKeyFormat {
27    /// Compact binary format using bitcode.
28    ///
29    /// Produces the smallest keys. This is the default and recommended format.
30    #[default]
31    Bitcode,
32
33    /// URL-encoded query string format.
34    ///
35    /// Produces human-readable keys like `_prefix=api&_version=1&user=123`.
36    /// Useful for debugging and human-readable storage keys.
37    UrlEncoded,
38}
39
40impl CacheKeyFormat {
41    /// Serialize a cache key to bytes.
42    pub fn serialize(&self, key: &CacheKey) -> Result<Vec<u8>, FormatError> {
43        match self {
44            CacheKeyFormat::Bitcode => Ok(bitcode::encode(key)),
45            CacheKeyFormat::UrlEncoded => {
46                let pairs = once((PREFIX_KEY, key.prefix().to_string()))
47                    .chain(once((VERSION_KEY, key.version().to_string())))
48                    .chain(
49                        key.parts()
50                            .map(|p| (p.key(), p.value().unwrap_or_default().to_string())),
51                    )
52                    .collect::<Vec<_>>();
53
54                serde_urlencoded::to_string(pairs)
55                    .map(String::into_bytes)
56                    .map_err(|err| FormatError::Serialize(Box::new(err)))
57            }
58        }
59    }
60
61    /// Deserialize bytes back to a cache key.
62    pub fn deserialize(&self, data: &[u8]) -> Result<CacheKey, FormatError> {
63        match self {
64            CacheKeyFormat::Bitcode => {
65                bitcode::decode(data).map_err(|err| FormatError::Deserialize(Box::new(err)))
66            }
67            CacheKeyFormat::UrlEncoded => {
68                let input =
69                    from_utf8(data).map_err(|err| FormatError::Deserialize(Box::new(err)))?;
70
71                let pairs: Vec<(String, String)> = serde_urlencoded::from_str(input)
72                    .map_err(|err| FormatError::Deserialize(Box::new(err)))?;
73
74                let (mut prefix, mut version, mut parts) = (String::new(), 0u32, Vec::new());
75
76                for (key, value) in pairs {
77                    match key.as_str() {
78                        PREFIX_KEY => prefix = value,
79                        VERSION_KEY => {
80                            version = value
81                                .parse()
82                                .map_err(|err| FormatError::Deserialize(Box::new(err)))?;
83                        }
84                        _ => {
85                            let v = if value.is_empty() { None } else { Some(value) };
86                            parts.push(KeyPart::new(key, v));
87                        }
88                    }
89                }
90
91                Ok(CacheKey::new(prefix, version, parts))
92            }
93        }
94    }
95}