Skip to main content

hitbox_core/
label.rs

1//! Backend label type for identifying cache backends.
2//!
3//! `BackendLabel` is a newtype wrapper around `SmolStr` that provides type safety
4//! for backend identifiers used in metrics, source tracking, and composition.
5
6use smol_str::SmolStr;
7use std::fmt;
8
9/// A label identifying a cache backend.
10///
11/// Used for:
12/// - Backend identification in `Backend::label()`
13/// - Response source tracking in `ResponseSource::Backend`
14/// - Metrics labels for composed backends (e.g., "composition.moka")
15///
16/// # Example
17/// ```
18/// use hitbox_core::BackendLabel;
19///
20/// let label = BackendLabel::new("moka");
21/// let composed = label.compose(&BackendLabel::new("inner"));
22/// assert_eq!(composed.as_str(), "moka.inner");
23/// ```
24#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
25pub struct BackendLabel(SmolStr);
26
27impl BackendLabel {
28    /// Creates a new backend label.
29    #[inline]
30    pub fn new(s: impl Into<SmolStr>) -> Self {
31        Self(s.into())
32    }
33
34    /// Creates a backend label from a static string (no allocation).
35    #[inline]
36    pub const fn new_static(s: &'static str) -> Self {
37        Self(SmolStr::new_static(s))
38    }
39
40    /// Returns the label as a string slice.
41    #[inline]
42    pub fn as_str(&self) -> &str {
43        &self.0
44    }
45
46    /// Returns a reference to the inner `SmolStr`.
47    #[inline]
48    pub fn as_smol_str(&self) -> &SmolStr {
49        &self.0
50    }
51
52    /// Composes two labels with a dot separator: "self.other".
53    ///
54    /// Used for hierarchical naming in composition backends,
55    /// e.g., "composition.moka" or "outer.inner.redis".
56    #[inline]
57    pub fn compose(&self, other: &BackendLabel) -> Self {
58        Self(SmolStr::from(format!("{}.{}", self.0, other.0)))
59    }
60}
61
62impl fmt::Display for BackendLabel {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        self.0.fmt(f)
65    }
66}
67
68impl From<&str> for BackendLabel {
69    #[inline]
70    fn from(s: &str) -> Self {
71        Self(SmolStr::new(s))
72    }
73}
74
75impl From<String> for BackendLabel {
76    #[inline]
77    fn from(s: String) -> Self {
78        Self(SmolStr::from(s))
79    }
80}
81
82impl From<SmolStr> for BackendLabel {
83    #[inline]
84    fn from(s: SmolStr) -> Self {
85        Self(s)
86    }
87}
88
89impl AsRef<str> for BackendLabel {
90    #[inline]
91    fn as_ref(&self) -> &str {
92        &self.0
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_new() {
102        let label = BackendLabel::new("moka");
103        assert_eq!(label.as_str(), "moka");
104    }
105
106    #[test]
107    fn test_new_static() {
108        let label = BackendLabel::new_static("redis");
109        assert_eq!(label.as_str(), "redis");
110    }
111
112    #[test]
113    fn test_compose() {
114        let outer = BackendLabel::new("composition");
115        let inner = BackendLabel::new("moka");
116        let composed = outer.compose(&inner);
117        assert_eq!(composed.as_str(), "composition.moka");
118    }
119
120    #[test]
121    fn test_compose_nested() {
122        let outer = BackendLabel::new("outer");
123        let inner = BackendLabel::new("inner");
124        let leaf = BackendLabel::new("moka");
125
126        let composed = outer.compose(&inner).compose(&leaf);
127        assert_eq!(composed.as_str(), "outer.inner.moka");
128    }
129
130    #[test]
131    fn test_from_str() {
132        let label: BackendLabel = "test".into();
133        assert_eq!(label.as_str(), "test");
134    }
135
136    #[test]
137    fn test_display() {
138        let label = BackendLabel::new("display_test");
139        assert_eq!(format!("{}", label), "display_test");
140    }
141
142    #[test]
143    fn test_equality() {
144        let a = BackendLabel::new("same");
145        let b = BackendLabel::new("same");
146        let c = BackendLabel::new("different");
147
148        assert_eq!(a, b);
149        assert_ne!(a, c);
150    }
151}