Skip to main content

use_key_value_store/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5
6macro_rules! string_newtype {
7    ($(#[$meta:meta])* $name:ident) => {
8        $(#[$meta])*
9        #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
10        pub struct $name(String);
11
12        impl $name {
13            /// Creates a new string-backed primitive.
14            pub fn new(value: impl Into<String>) -> Self {
15                Self(value.into())
16            }
17
18            /// Returns the stored string value.
19            pub fn as_str(&self) -> &str {
20                &self.0
21            }
22        }
23
24        impl AsRef<str> for $name {
25            fn as_ref(&self) -> &str {
26                self.as_str()
27            }
28        }
29
30        impl From<String> for $name {
31            fn from(value: String) -> Self {
32                Self::new(value)
33            }
34        }
35
36        impl From<&str> for $name {
37            fn from(value: &str) -> Self {
38                Self::new(value)
39            }
40        }
41
42        impl fmt::Display for $name {
43            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
44                formatter.write_str(self.as_str())
45            }
46        }
47    };
48}
49
50string_newtype! {
51    /// A key-value store key.
52    Key
53}
54string_newtype! {
55    /// A key-value store value payload.
56    Value
57}
58string_newtype! {
59    /// A reusable key prefix.
60    KeyPrefix
61}
62string_newtype! {
63    /// A key namespace.
64    KeyNamespace
65}
66string_newtype! {
67    /// A bucket or logical storage name.
68    BucketName
69}
70string_newtype! {
71    /// A simple key pattern label.
72    KeyPattern
73}
74
75impl Key {
76    /// Builds a key by joining non-empty segments with `:`.
77    pub fn from_segments(segments: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
78        let joined = segments
79            .into_iter()
80            .map(|segment| segment.as_ref().to_owned())
81            .filter(|segment| !segment.is_empty())
82            .collect::<Vec<_>>()
83            .join(":");
84        Self::new(joined)
85    }
86
87    /// Builds a key under a prefix.
88    pub fn with_prefix(prefix: &KeyPrefix, segment: impl AsRef<str>) -> Self {
89        Self::from_segments([prefix.as_str(), segment.as_ref()])
90    }
91}
92
93/// A key-value entry.
94#[derive(Clone, Debug, Eq, Hash, PartialEq)]
95pub struct KeyValueEntry {
96    key: Key,
97    value: Value,
98}
99
100impl KeyValueEntry {
101    /// Creates a key-value entry.
102    pub fn new(key: Key, value: Value) -> Self {
103        Self { key, value }
104    }
105
106    /// Returns the entry key.
107    pub const fn key(&self) -> &Key {
108        &self.key
109    }
110
111    /// Returns the entry value.
112    pub const fn value(&self) -> &Value {
113        &self.value
114    }
115}
116
117/// A half-open or closed key range description.
118#[derive(Clone, Debug, Default, Eq, PartialEq)]
119pub struct KeyRange {
120    start: Option<Key>,
121    end: Option<Key>,
122}
123
124impl KeyRange {
125    /// Creates a key range from optional bounds.
126    pub const fn new(start: Option<Key>, end: Option<Key>) -> Self {
127        Self { start, end }
128    }
129
130    /// Creates a range starting at `start`.
131    pub fn starting_at(start: Key) -> Self {
132        Self::new(Some(start), None)
133    }
134
135    /// Creates a range ending at `end`.
136    pub fn ending_at(end: Key) -> Self {
137        Self::new(None, Some(end))
138    }
139
140    /// Returns the start bound.
141    pub const fn start(&self) -> Option<&Key> {
142        self.start.as_ref()
143    }
144
145    /// Returns the end bound.
146    pub const fn end(&self) -> Option<&Key> {
147        self.end.as_ref()
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::{BucketName, Key, KeyPrefix, KeyRange, KeyValueEntry, Value};
154    use std::collections::hash_map::DefaultHasher;
155    use std::hash::{Hash, Hasher};
156
157    #[test]
158    fn constructs_and_displays_keys() {
159        let key = Key::from_segments(["tenant", "customer", "123"]);
160        assert_eq!(key.to_string(), "tenant:customer:123");
161        assert_eq!(key.as_ref(), "tenant:customer:123");
162        assert_eq!(BucketName::new("cache").to_string(), "cache");
163    }
164
165    #[test]
166    fn composes_keys_with_prefixes() {
167        let key = Key::with_prefix(&KeyPrefix::new("tenant:acme"), "profile");
168        let entry = KeyValueEntry::new(key.clone(), Value::new("payload"));
169        let range = KeyRange::starting_at(key.clone());
170
171        assert_eq!(key.as_str(), "tenant:acme:profile");
172        assert_eq!(entry.key(), &key);
173        assert_eq!(range.start(), Some(&key));
174    }
175
176    #[test]
177    fn hashes_equal_keys() {
178        let mut left = DefaultHasher::new();
179        let mut right = DefaultHasher::new();
180        Key::new("same").hash(&mut left);
181        Key::new("same").hash(&mut right);
182        assert_eq!(left.finish(), right.finish());
183    }
184}